Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4307 lines
124 KiB

//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1992 - 1996
//
// File: token.cxx
//
// Contents: Routines building access tokens
//
//
// History: 1-May-1996 Created MikeSw
// 26-Sep-1998 ChandanS
// Added more debugging support etc.
//
//------------------------------------------------------------------------
#include <kerb.hxx>
#include <kerbp.h>
#include <pac.hxx>
#include <utils.hxx>
#ifdef DEBUG_SUPPORT
static TCHAR THIS_FILE[]=TEXT(__FILE__);
#endif
//+-------------------------------------------------------------------------
//
// Function: KerbVerifyAuthData
//
// Synopsis: Verifies that we should not be rejecting the auth data
// Accepted auth data is anything we know about and even values
// Odd values and unknown auth data is rejected
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
BOOLEAN
KerbVerifyAuthData(
IN PKERB_AUTHORIZATION_DATA AuthData
)
{
PKERB_AUTHORIZATION_DATA TempData = AuthData;
while (TempData != NULL)
{
if ((TempData->value.auth_data_type & 1) != 0)
{
switch(TempData->value.auth_data_type)
{
case KERB_AUTH_OSF_DCE:
case KERB_AUTH_SESAME:
case KERB_AUTH_DATA_PAC:
case -KERB_AUTH_DATA_PAC: // obsolete pac id
case KERB_AUTH_PROXY_ANNOTATION:
case KERB_AUTH_DATA_IF_RELEVANT:
case KERB_AUTH_DATA_KDC_ISSUED:
#ifdef RESTRICTED_TOKEN
case KERB_AUTH_DATA_TOKEN_RESTRICTIONS:
#endif
break;
default:
D_DebugLog((DEB_ERROR,"Unknown auth type: %d\n",TempData->value.auth_data_type));
return(FALSE);
}
}
TempData = TempData->next;
}
return(TRUE);
}
//+-------------------------------------------------------------------------
//
// Function: KerbApplyTokenRestrictions
//
// Synopsis: Applies restrictions to a fresh token
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbApplyTokenRestrictions(
IN PKERB_AUTHORIZATION_DATA AuthData,
IN OUT PHANDLE TokenHandle
)
{
PKERB_TOKEN_RESTRICTIONS Restrictions = NULL;
HANDLE RestrictedToken = NULL;
NTSTATUS Status = STATUS_SUCCESS;
Status = PAC_DecodeTokenRestrictions(
AuthData->value.auth_data.value,
AuthData->value.auth_data.length,
&Restrictions
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to decode token restrictions: 0x%x\n",Status));
goto Cleanup;
}
//
// If there are any restrictions, apply them here.
//
if (Restrictions->Flags != 0)
{
Status = NtFilterToken(
*TokenHandle,
0, // no flags,
(Restrictions->Flags & KERB_TOKEN_RESTRICTION_DISABLE_GROUPS) != 0 ? (PTOKEN_GROUPS) Restrictions->GroupsToDisable : NULL,
(Restrictions->Flags & KERB_TOKEN_RESTRICTION_DELETE_PRIVS) != 0 ? (PTOKEN_PRIVILEGES) Restrictions->PrivilegesToDelete : NULL,
(Restrictions->Flags & KERB_TOKEN_RESTRICTION_RESTRICT_SIDS) != 0 ? (PTOKEN_GROUPS) Restrictions->RestrictedSids : NULL,
&RestrictedToken
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to filter token: 0x%x\n",Status));
goto Cleanup;
}
NtClose(*TokenHandle);
*TokenHandle = RestrictedToken;
RestrictedToken = NULL;
}
Cleanup:
if (Restrictions != NULL)
{
MIDL_user_free(Restrictions);
}
if (RestrictedToken != NULL)
{
NtClose(RestrictedToken);
}
return(Status);
}
#ifdef RESTRICTED_TOKEN
//+-------------------------------------------------------------------------
//
// Function: KerbApplyAuthDataRestrictions
//
// Synopsis: Applies any restrictions from the auth data to the to token
// and logon session.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbApplyAuthDataRestrictions(
IN OUT PHANDLE TokenHandle,
IN PKERB_AUTHORIZATION_DATA AuthData
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_AUTHORIZATION_DATA TempData = AuthData;
while (TempData != NULL)
{
if ((TempData->value.auth_data_type & 1) != 0)
{
switch(TempData->value.auth_data_type)
{
case KERB_AUTH_DATA_TOKEN_RESTRICTIONS:
Status = KerbApplyTokenRestrictions(
TempData,
TokenHandle
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to apply token restrictions: 0x%x\n",Status));
goto Cleanup;
break;
}
default:
break;
}
}
TempData = TempData->next;
}
Cleanup:
return(Status);
}
#endif
//+-------------------------------------------------------------------------
//
// Function: KerbVerifyPacSignature
//
// Synopsis: Verifies the server signature on a PAC and if necessary
// calls the KDC to verify the KDC signature.
//
// Effects:
//
// Arguments: Pac - an unmarshalled pac
// EncryptionKey - Key used to decrypt the ticket & verify the pac
//
//
// Requires:
//
// Returns:
//
// Notes: No locks should be held while calling this function
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbVerifyPacSignature(
IN PUNICODE_STRING ServiceDomain,
IN PPACTYPE Pac,
IN ULONG PacSize,
IN PKERB_ENCRYPTION_KEY EncryptionKey,
IN PKERB_ENCRYPTED_TICKET Ticket,
IN BOOLEAN CheckKdcSignature,
OUT PNETLOGON_VALIDATION_SAM_INFO3 * ValidationInfo,
OUT OPTIONAL PS4U_DELEGATION_INFO* S4uDelegationInfo
)
{
NTSTATUS Status = STATUS_SUCCESS;
NTSTATUS SubStatus;
PKERB_VERIFY_PAC_REQUEST VerifyRequest = NULL;
PMSV1_0_PASSTHROUGH_REQUEST PassthroughRequest = NULL;
PMSV1_0_PASSTHROUGH_RESPONSE PassthroughResponse = NULL;
ULONG RequestSize;
ULONG ResponseSize;
PCHECKSUM_FUNCTION Check = NULL ;
PCHECKSUM_BUFFER CheckBuffer = NULL;
PPAC_SIGNATURE_DATA ServerSignature = NULL;
PPAC_SIGNATURE_DATA PrivSvrSignature = NULL;
PPAC_INFO_BUFFER ServerBuffer = NULL;
PPAC_INFO_BUFFER PrivSvrBuffer = NULL;
PPAC_INFO_BUFFER LogonInfo = NULL;
PPAC_INFO_BUFFER ExtraLogonInfo = NULL;
PPAC_INFO_BUFFER ClientBuffer = NULL;
PPAC_INFO_BUFFER DelegationInfoBuffer = NULL;
PPAC_CLIENT_INFO ClientInfo = NULL;
UCHAR LocalChecksum[20];
UCHAR LocalServerChecksum[20];
UCHAR LocalPrivSvrChecksum[20];
SECPKG_CLIENT_INFO LsaClientInfo;
ULONG SignatureSize;
PUCHAR Where;
UNICODE_STRING MsvPackageName = CONSTANT_UNICODE_STRING(TEXT(MSV1_0_PACKAGE_NAME));
ULONG NameType;
UNICODE_STRING ClientName = {0};
*ValidationInfo = NULL;
//
// Get the various pieces we need out of the PAC - the logon information
// and the two signatures.
//
LogonInfo = PAC_Find(
Pac,
PAC_LOGON_INFO,
NULL
);
if (LogonInfo == NULL)
{
D_DebugLog((DEB_ERROR,"Failed to find logon info! %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Verify that we don't have multiple pacs.
//
ExtraLogonInfo = PAC_Find(
Pac,
PAC_LOGON_INFO,
LogonInfo
);
if ( ExtraLogonInfo )
{
DsysAssert(FALSE);
DebugLog((DEB_ERROR, "We have multiple PAC entries \n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// See if the delegation info is in the PAC
//
DelegationInfoBuffer = PAC_Find(
Pac,
PAC_DELEGATION_INFO,
NULL
);
//
// Get the PAC signature(s)
//
ServerBuffer = PAC_Find(
Pac,
PAC_SERVER_CHECKSUM,
NULL
);
PrivSvrBuffer = PAC_Find(
Pac,
PAC_PRIVSVR_CHECKSUM,
NULL
);
if ((ServerBuffer == NULL) || (PrivSvrBuffer == NULL))
{
D_DebugLog((DEB_ERROR, "Pac found with no signature!\n"));
return(STATUS_LOGON_FAILURE);
}
//
// Now verify the server checksum. First compute the checksum
// over the logon info.
//
ServerSignature = (PPAC_SIGNATURE_DATA) ServerBuffer->Data;
if ((sizeof(*ServerSignature) > ServerBuffer->cbBufferSize) ||
(PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize) > sizeof(LocalServerChecksum)))
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
PrivSvrSignature = (PPAC_SIGNATURE_DATA) PrivSvrBuffer->Data;
if ((sizeof(*PrivSvrSignature) > PrivSvrBuffer->cbBufferSize) ||
(PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize) > sizeof(LocalPrivSvrChecksum)))
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Copy out the signature so we can zero the signature fields before
// checksumming
//
RtlCopyMemory(
LocalServerChecksum,
ServerSignature->Signature,
PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize)
);
RtlZeroMemory(
ServerSignature->Signature,
PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize)
);
RtlCopyMemory(
LocalPrivSvrChecksum,
PrivSvrSignature->Signature,
PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize)
);
RtlZeroMemory(
PrivSvrSignature->Signature,
PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize)
);
//
// Now remarshal the PAC before checksumming.
//
if (!PAC_ReMarshal(Pac,PacSize))
{
DsysAssert(!"PAC_Remarhsal failed");
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
//
// Locate the checksum of the logon info & compute it.
//
Status = CDLocateCheckSum(
ServerSignature->SignatureType,
&Check
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (Check->CheckSumSize > sizeof(LocalChecksum)) {
DsysAssert(Check->CheckSumSize > sizeof(LocalChecksum));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// if available use the Ex2 version for keyed checksums where checksum
// must be passed in on verification
//
if (NULL != Check->InitializeEx2)
{
Status = Check->InitializeEx2(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
LocalServerChecksum,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
else
{
Status = Check->InitializeEx(
EncryptionKey->keyvalue.value,
EncryptionKey->keyvalue.length,
KERB_NON_KERB_CKSUM_SALT,
&CheckBuffer
);
}
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Check->Sum(
CheckBuffer,
PacSize,
(PUCHAR) Pac
);
Check->Finalize(
CheckBuffer,
LocalChecksum
);
Check->Finish(&CheckBuffer);
//
// Now compare the local checksum to the supplied checksum.
//
if (Check->CheckSumSize != PAC_CHECKSUM_SIZE(ServerBuffer->cbBufferSize))
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
if (!RtlEqualMemory(
LocalChecksum,
LocalServerChecksum,
Check->CheckSumSize
))
{
DebugLog((DEB_ERROR, "Checksum on the PAC does not match! %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Now unmarshal the PAC so that the caller will have it back the
// way they started.
//
if (!PAC_UnMarshal(Pac,PacSize))
{
DsysAssert(!"PAC_UnMarshal failed");
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
//
// Check the client info, if present,
//
ClientBuffer = PAC_Find(
Pac,
PAC_CLIENT_INFO_TYPE,
NULL
);
if (ClientBuffer != NULL)
{
TimeStamp ClientId;
UNICODE_STRING PacClientName = {0};
if (ClientBuffer->cbBufferSize < sizeof(PAC_CLIENT_INFO))
{
D_DebugLog((DEB_ERROR, "Clientinfo is too small: %d instead of %d. %ws, line %d\n",
ClientBuffer->cbBufferSize, sizeof(PAC_CLIENT_INFO), THIS_FILE, __LINE__));
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
ClientInfo = (PPAC_CLIENT_INFO) ClientBuffer->Data;
if ((ClientInfo->NameLength - ANYSIZE_ARRAY * sizeof(WCHAR) + sizeof(PPAC_CLIENT_INFO)) > ClientBuffer->cbBufferSize)
{
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
KerbConvertGeneralizedTimeToLargeInt(
&ClientId,
&Ticket->authtime,
0 // no usec
);
if (!RtlEqualMemory(
&ClientId,
&ClientInfo->ClientId,
sizeof(TimeStamp)
))
{
D_DebugLog((DEB_ERROR, "Client IDs don't match. %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Check the name now
//
PacClientName.Buffer = ClientInfo->Name;
PacClientName.Length = PacClientName.MaximumLength = ClientInfo->NameLength;
if (KERB_SUCCESS(KerbConvertPrincipalNameToString(
&ClientName,
&NameType,
&Ticket->client_name
)))
{
if (!RtlEqualUnicodeString(
&ClientName,
&PacClientName,
TRUE))
{
D_DebugLog((DEB_ERROR,"Client names don't match: %wZ vs %wZ. %ws, line %d\n",
&PacClientName, &ClientName, THIS_FILE, __LINE__ ));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
}
else
{
//
// We *MUST* have a client info segment. This keeps people from mis-using the pac
//
DebugLog((DEB_ERROR, "Missing client info!\n"));
DsysAssert(FALSE);
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Unmarshall the logon info. We need to do this to get the logon domain
// out to use for the pass-through.
//
Status = PAC_UnmarshallValidationInfo(
ValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (S4uDelegationInfo && DelegationInfoBuffer)
{
Status = UnmarshalS4UDelegationInformation(
DelegationInfoBuffer->cbBufferSize,
DelegationInfoBuffer->Data,
S4uDelegationInfo
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "KerbVerifyPacSignature failed to unmarshall S4U delgation info %#x\n", Status));
goto Cleanup;
}
}
//
// If we don't need to check the KDC signature, bail now. This is for
// tokens that can't be used for impersonation
//
if (!CheckKdcSignature)
{
goto Cleanup;
}
//
// Now check to see if the client has TCB privilege. It it does, we
// are done. Otherwise we need to call the KDC to verify the PAC.
//
Status = LsaFunctions->GetClientInfo(&LsaClientInfo);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (LsaClientInfo.HasTcbPrivilege)
{
goto Cleanup;
}
//
// also let networkservice through.
//
{
LUID NetworkServiceLuid = NETWORKSERVICE_LUID;
if( RtlEqualLuid( &NetworkServiceLuid, &LsaClientInfo.LogonId ) )
{
goto Cleanup;
}
}
//
// We have to pass off to the DC so build the request.
//
SignatureSize = PAC_CHECKSUM_SIZE(PrivSvrBuffer->cbBufferSize);
RequestSize = sizeof(MSV1_0_PASSTHROUGH_REQUEST) +
ROUND_UP_COUNT(ServiceDomain->Length, ALIGN_LPTSTR) +
ROUND_UP_COUNT(KerbPackageName.Length, ALIGN_LPTSTR) +
sizeof(KERB_VERIFY_PAC_REQUEST) - sizeof(UCHAR) * ANYSIZE_ARRAY +
Check->CheckSumSize +
SignatureSize;
SafeAllocaAllocate(PassthroughRequest, RequestSize);
if (PassthroughRequest == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
Where = (PUCHAR) (PassthroughRequest + 1);
PassthroughRequest->MessageType = MsV1_0GenericPassthrough;
PassthroughRequest->DomainName = *ServiceDomain;
PassthroughRequest->DomainName.Buffer = (LPWSTR) Where;
RtlCopyMemory(
Where,
ServiceDomain->Buffer,
ServiceDomain->Length
);
Where += ROUND_UP_COUNT(ServiceDomain->Length, ALIGN_LPTSTR);
PassthroughRequest->PackageName = KerbPackageName;
PassthroughRequest->PackageName.Buffer = (LPWSTR) Where;
RtlCopyMemory(
Where,
KerbPackageName.Buffer,
KerbPackageName.Length
);
Where += ROUND_UP_COUNT(KerbPackageName.Length, ALIGN_LPTSTR);
PassthroughRequest->LogonData = Where;
PassthroughRequest->DataLength = sizeof(KERB_VERIFY_PAC_REQUEST) - sizeof(UCHAR) * ANYSIZE_ARRAY +
Check->CheckSumSize +
SignatureSize;
VerifyRequest = (PKERB_VERIFY_PAC_REQUEST) PassthroughRequest->LogonData;
VerifyRequest->MessageType = KerbVerifyPacMessage;
VerifyRequest->ChecksumLength = Check->CheckSumSize;
VerifyRequest->SignatureType = PrivSvrSignature->SignatureType;
VerifyRequest->SignatureLength = SignatureSize;
RtlCopyMemory(
VerifyRequest->ChecksumAndSignature,
LocalChecksum,
Check->CheckSumSize
);
RtlCopyMemory(
VerifyRequest->ChecksumAndSignature + Check->CheckSumSize,
LocalPrivSvrChecksum,
SignatureSize
);
//
// We've build the buffer, now call NTLM to pass it through.
//
Status = LsaFunctions->CallPackage(
&MsvPackageName,
PassthroughRequest,
RequestSize,
(PVOID *) &PassthroughResponse,
&ResponseSize,
&SubStatus
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "Failed to call MSV package to verify PAC: 0x%x. %ws, line %d\n", Status, THIS_FILE, __LINE__));
if (Status == STATUS_INVALID_INFO_CLASS)
{
Status = STATUS_LOGON_FAILURE;
}
goto Cleanup;
}
if (!NT_SUCCESS(SubStatus))
{
Status = SubStatus;
DebugLog((DEB_ERROR, "KDC failed to verify PAC signature: 0x%x. %ws, line %d\n", Status, THIS_FILE, __LINE__));
if ((Status == STATUS_INVALID_INFO_CLASS) ||
(Status == STATUS_INVALID_SERVER_STATE) ||
(Status == STATUS_NO_SUCH_USER))
{
Status = STATUS_PRIVILEGE_NOT_HELD;
}
goto Cleanup;
}
Cleanup:
KerbFreeString(&ClientName);
if ( ( CheckBuffer != NULL ) &&
( Check != NULL ) )
{
Check->Finish(&CheckBuffer);
}
SafeAllocaFree(PassthroughRequest);
if (PassthroughResponse != NULL)
{
LsaFunctions->FreeReturnBuffer(PassthroughResponse);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbPutClientString
//
// Synopsis: Copies a string into a buffer that will be copied to the
// client's address space
//
// Effects:
//
// Arguments: Where - Location in local buffer to place string.
// Delta - Difference in addresses of local and client buffers.
// OutString - Receives 'put' string
// InString - String to 'put'
//
// Requires:
//
// Returns:
//
// Notes: This code is (effectively) duplicated in
// KerbPutWOWClientString. Make sure any
// changes made here are applied there as well.
//
//--------------------------------------------------------------------------
VOID
KerbPutClientString(
IN OUT PUCHAR * Where,
IN LONG_PTR Delta,
IN PUNICODE_STRING OutString,
IN PUNICODE_STRING InString
)
{
if (InString->Length == 0)
{
OutString->Buffer = NULL;
OutString->Length = OutString->MaximumLength = 0;
}
else
{
RtlCopyMemory(
*Where,
InString->Buffer,
InString->Length
);
OutString->Buffer = (LPWSTR) (*Where + Delta);
OutString->Length = InString->Length;
*Where += InString->Length;
*(LPWSTR) (*Where) = L'\0';
*Where += sizeof(WCHAR);
OutString->MaximumLength = OutString->Length + sizeof(WCHAR);
}
}
//+-------------------------------------------------------------------------
//
// Function: KerbAllocateInteractiveProfile
//
// Synopsis: This allocates and fills in the clients interactive profile.
//
// Effects:
//
// Arguments:
//
// ProfileBuffer - Is used to return the address of the profile
// buffer in the client process. This routine is
// responsible for allocating and returning the profile buffer
// within the client process. However, if the caller subsequently
// encounters an error which prevents a successful logon, then
// then it will take care of deallocating the buffer. This
// buffer is allocated with the AllocateClientBuffer() service.
//
// ProfileBufferSize - Receives the Size (in bytes) of the
// returned profile buffer.
//
// NlpUser - Contains the validation information which is
// to be copied in the ProfileBuffer.
//
// LogonSession - Logon session structure containing certificate
// context for smart card logons.
//
// Requires:
//
// Returns: STATUS_SUCCESS or STATUS_INSUFFICIENT_RESOURCES
//
// Notes: stolen from private\lsa\msv1_0\nlp.c
//
// Some of this code is (effectively) duplicated in
// KerbAllocateInteractiveWOWBuffer. Make sure any
// changes made here are applied there as well.
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAllocateInteractiveProfile (
OUT PKERB_INTERACTIVE_PROFILE *ProfileBuffer,
OUT PULONG ProfileBufferSize,
IN PNETLOGON_VALIDATION_SAM_INFO3 UserInfo,
IN PKERB_LOGON_SESSION LogonSession,
IN OPTIONAL PKERB_ENCRYPTED_TICKET LogonTicket,
IN OPTIONAL PKERB_INTERACTIVE_LOGON KerbLogonInfo
)
{
NTSTATUS Status;
PKERB_INTERACTIVE_PROFILE LocalProfileBuffer = NULL;
PKERB_SMART_CARD_PROFILE SmartCardProfile = NULL;
PKERB_TICKET_PROFILE TicketProfile = NULL;
PUCHAR ClientBufferBase = NULL;
PUCHAR Where = NULL;
LONG_PTR Delta = 0;
BOOLEAN BuildSmartCardProfile = FALSE;
BOOLEAN BuildTicketProfile = FALSE;
//
// Alocate the profile buffer to return to the client
//
KerbReadLockLogonSessions( LogonSession );
#if _WIN64
SECPKG_CALL_INFO CallInfo;
if(!LsaFunctions->GetCallInfo(&CallInfo))
{
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
#endif // _WIN64
*ProfileBuffer = NULL;
if ((LogonSession->PrimaryCredentials.PublicKeyCreds != NULL) &&
(LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext != NULL))
{
BuildSmartCardProfile = TRUE;
}
else if (ARGUMENT_PRESENT(KerbLogonInfo) &&
(KerbLogonInfo->MessageType == KerbTicketLogon) ||
(KerbLogonInfo->MessageType == KerbTicketUnlockLogon))
{
DsysAssert(ARGUMENT_PRESENT(LogonTicket));
BuildTicketProfile = TRUE;
KerbReadLockTicketCache();
}
//
// NOTE: The 64-bit code below is (effectively) duplicated in
// the WOW helper routine. If modifying one, make sure
// to apply the change(s) to the other as well.
//
#if _WIN64
if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
{
Status = KerbAllocateInteractiveWOWBuffer(&LocalProfileBuffer,
ProfileBufferSize,
UserInfo,
LogonSession,
LogonTicket,
KerbLogonInfo,
&ClientBufferBase,
BuildSmartCardProfile,
BuildTicketProfile);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
else
{
#endif // _WIN64
if (BuildSmartCardProfile)
{
*ProfileBufferSize = sizeof(KERB_SMART_CARD_PROFILE) +
LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext->cbCertEncoded;
}
else if (BuildTicketProfile)
{
*ProfileBufferSize = sizeof(KERB_TICKET_PROFILE) +
LogonTicket->key.keyvalue.length;
}
else
{
*ProfileBufferSize = sizeof(KERB_INTERACTIVE_PROFILE);
}
*ProfileBufferSize +=
UserInfo->LogonScript.Length + sizeof(WCHAR) +
UserInfo->HomeDirectory.Length + sizeof(WCHAR) +
UserInfo->HomeDirectoryDrive.Length + sizeof(WCHAR) +
UserInfo->FullName.Length + sizeof(WCHAR) +
UserInfo->ProfilePath.Length + sizeof(WCHAR) +
UserInfo->LogonServer.Length + sizeof(WCHAR);
SafeAllocaAllocate(LocalProfileBuffer, *ProfileBufferSize);
if (LocalProfileBuffer == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
Status = LsaFunctions->AllocateClientBuffer(
NULL,
*ProfileBufferSize,
(PVOID *) &ClientBufferBase
);
if ( !NT_SUCCESS( Status ) ) {
goto Cleanup;
}
Delta = (LONG_PTR) (ClientBufferBase - (PUCHAR) LocalProfileBuffer) ;
//
// Don't walk over smart card data
//
if (BuildSmartCardProfile)
{
Where = (PUCHAR) ((PKERB_SMART_CARD_PROFILE) LocalProfileBuffer + 1);
}
else if (BuildTicketProfile)
{
Where = (PUCHAR) ((PKERB_TICKET_PROFILE) LocalProfileBuffer + 1);
}
else
{
Where = (PUCHAR) (LocalProfileBuffer + 1);
}
//
// Copy the scalar fields into the profile buffer.
//
LocalProfileBuffer->MessageType = KerbInteractiveProfile;
LocalProfileBuffer->LogonCount = UserInfo->LogonCount;
LocalProfileBuffer->BadPasswordCount= UserInfo->BadPasswordCount;
OLD_TO_NEW_LARGE_INTEGER( UserInfo->LogonTime,
LocalProfileBuffer->LogonTime );
OLD_TO_NEW_LARGE_INTEGER( UserInfo->LogoffTime,
LocalProfileBuffer->LogoffTime );
OLD_TO_NEW_LARGE_INTEGER( UserInfo->KickOffTime,
LocalProfileBuffer->KickOffTime );
OLD_TO_NEW_LARGE_INTEGER( UserInfo->PasswordLastSet,
LocalProfileBuffer->PasswordLastSet );
OLD_TO_NEW_LARGE_INTEGER( UserInfo->PasswordCanChange,
LocalProfileBuffer->PasswordCanChange );
OLD_TO_NEW_LARGE_INTEGER( UserInfo->PasswordMustChange,
LocalProfileBuffer->PasswordMustChange );
LocalProfileBuffer->UserFlags = UserInfo->UserFlags;
//
// Copy the Unicode strings into the profile buffer.
//
KerbPutClientString(&Where,
Delta,
&LocalProfileBuffer->LogonScript,
&UserInfo->LogonScript );
KerbPutClientString(&Where,
Delta,
&LocalProfileBuffer->HomeDirectory,
&UserInfo->HomeDirectory );
KerbPutClientString(&Where,
Delta,
&LocalProfileBuffer->HomeDirectoryDrive,
&UserInfo->HomeDirectoryDrive );
KerbPutClientString(&Where,
Delta,
&LocalProfileBuffer->FullName,
&UserInfo->FullName );
KerbPutClientString(&Where,
Delta,
&LocalProfileBuffer->ProfilePath,
&UserInfo->ProfilePath );
KerbPutClientString(&Where,
Delta,
&LocalProfileBuffer->LogonServer,
&UserInfo->LogonServer );
if (BuildSmartCardProfile)
{
LocalProfileBuffer->MessageType = KerbSmartCardProfile;
SmartCardProfile = (PKERB_SMART_CARD_PROFILE) LocalProfileBuffer;
SmartCardProfile->CertificateSize = LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext->cbCertEncoded;
SmartCardProfile->CertificateData = (PUCHAR) Where + Delta;
RtlCopyMemory(
Where,
LogonSession->PrimaryCredentials.PublicKeyCreds->CertContext->pbCertEncoded,
SmartCardProfile->CertificateSize
);
Where += SmartCardProfile->CertificateSize;
}
else if (BuildTicketProfile)
{
LocalProfileBuffer->MessageType = KerbTicketProfile;
TicketProfile = (PKERB_TICKET_PROFILE) LocalProfileBuffer;
//
// If the key is exportable or we are domestic, return the key
//
TicketProfile->SessionKey.KeyType = LogonTicket->key.keytype;
TicketProfile->SessionKey.Length = LogonTicket->key.keyvalue.length;
TicketProfile->SessionKey.Value = (PUCHAR) Where + Delta;
RtlCopyMemory(
Where,
LogonTicket->key.keyvalue.value,
LogonTicket->key.keyvalue.length
);
Where += TicketProfile->SessionKey.Length;
}
#if _WIN64
}
#endif // _WIN64
//
// Flush the buffer to the client's address space.
//
Status = LsaFunctions->CopyToClientBuffer(
NULL,
*ProfileBufferSize,
ClientBufferBase,
LocalProfileBuffer
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
*ProfileBuffer = (PKERB_INTERACTIVE_PROFILE) ClientBufferBase;
Cleanup:
if (BuildTicketProfile)
{
KerbUnlockTicketCache();
}
KerbUnlockLogonSessions( LogonSession );
//
// If the copy wasn't successful,
// cleanup resources we would have returned to the caller.
//
if ( !NT_SUCCESS(Status) )
{
LsaFunctions->FreeClientBuffer( NULL, ClientBufferBase );
}
#if _WIN64
if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
{
KerbFree(LocalProfileBuffer);
}
else
{
#endif // _WIN64
SafeAllocaFree(LocalProfileBuffer);
#if _WIN64
}
#endif // _WIN64
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbMakeTokenInformationV2
//
// Synopsis: This routine makes copies of all the pertinent
// information from the UserInfo and generates a
// LSA_TOKEN_INFORMATION_V2 data structure.
//
// Effects:
//
// Arguments:
//
// UserInfo - Contains the validation information which is
// to be copied into the TokenInformation.
//
// TokenInformation - Returns a pointer to a properly Version 1 token
// information structures. The structure and individual fields are
// allocated properly as described in ntlsa.h.
//
// Requires:
//
// Returns: STATUS_SUCCESS - Indicates the service completed successfully.
//
// STATUS_INSUFFICIENT_RESOURCES - This error indicates that
// the logon could not be completed because the client
// does not have sufficient quota to allocate the return
// buffer.
//
// Notes: stolen from msv1_0\nlp.c:NlpMakeTokenInformationV1
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbMakeTokenInformationV2(
IN PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo,
IN BOOLEAN LocalSystem,
OUT PLSA_TOKEN_INFORMATION_V2 *TokenInformation
)
{
PNETLOGON_VALIDATION_SAM_INFO3 UserInfo = ValidationInfo;
NTSTATUS Status;
PLSA_TOKEN_INFORMATION_V2 V2 = NULL;
ULONG Size, i;
BYTE SidBuffer[sizeof(SID) + sizeof(ULONG)];
SID LocalSystemSid = {SID_REVISION,1,SECURITY_NT_AUTHORITY,SECURITY_LOCAL_SYSTEM_RID};
PSID AdminsSid = SidBuffer;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
DWORD NumGroups = 0;
PBYTE CurrentSid = NULL;
ULONG SidLength = 0;
KERBEROS_MACHINE_ROLE MachineRole;
//
// Filter the SIDs in the "member-to-DC" trust mode
//
KerbGlobalReadLock();
MachineRole = KerbGlobalRole;
KerbGlobalReleaseLock();
//
// If we are on a workstation, filter the SIDs that came from the DC
// on a member-to-DC boundary policy (passing NULL SID to LsaIFilterSids)
//
if ( MachineRole == KerbRoleWorkstation ||
MachineRole == KerbRoleDomainController )
{
//
// Servers in KerbRoleStandalone and KerbRoleRealmlessWksta states
// never receive Kerberos requests (as there are no local system keys
// to decrypt service tickets, due to the fact that the machine aren't
// joined to any domains).
//
Status = LsaIFilterSids(
NULL,
0,
0,
0,
NULL,
NetlogonValidationSamInfo2,
UserInfo,
UserInfo->ResourceGroupDomainSid,
&UserInfo->ResourceGroupCount,
UserInfo->ResourceGroupIds
);
if ( !NT_SUCCESS( Status )) {
return Status;
}
}
//
// For local system, add in administrators & set user id to local system
//
if (LocalSystem)
{
RtlInitializeSid(
AdminsSid,
&NtAuthority,
2
);
*RtlSubAuthoritySid(AdminsSid,0) = SECURITY_BUILTIN_DOMAIN_RID;
*RtlSubAuthoritySid(AdminsSid,1) = DOMAIN_ALIAS_RID_ADMINS;
}
//
// Allocate the structure itself
//
Size = (ULONG)sizeof(LSA_TOKEN_INFORMATION_V2);
//
// Allocate an array to hold the groups
//
Size += sizeof(TOKEN_GROUPS);
// Add room for groups passed as RIDS
NumGroups = UserInfo->GroupCount;
if(UserInfo->GroupCount)
{
Size += UserInfo->GroupCount * (RtlLengthSid(UserInfo->LogonDomainId) + sizeof(ULONG));
}
//
// If there are extra SIDs, add space for them
//
if (UserInfo->UserFlags & LOGON_EXTRA_SIDS) {
ULONG i = 0;
NumGroups += UserInfo->SidCount;
// Add room for the sid's themselves
for(i=0; i < UserInfo->SidCount; i++)
{
Size += RtlLengthSid(UserInfo->ExtraSids[i].Sid);
}
}
//
// If there are resource groups, add space for them
//
if (UserInfo->UserFlags & LOGON_RESOURCE_GROUPS) {
NumGroups += UserInfo->ResourceGroupCount;
if ((UserInfo->ResourceGroupCount != 0) &&
((UserInfo->ResourceGroupIds == NULL) ||
(UserInfo->ResourceGroupDomainSid == NULL)))
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
// Allocate space for the sids
if(UserInfo->ResourceGroupCount)
{
Size += UserInfo->ResourceGroupCount * (RtlLengthSid(UserInfo->ResourceGroupDomainSid) + sizeof(ULONG));
}
}
//
// If this is local system, add space for User & Administrators
//
if (!LocalSystem)
{
if( UserInfo->UserId )
{
Size += 2*(RtlLengthSid(UserInfo->LogonDomainId) + sizeof(ULONG));
}
else
{
if ( UserInfo->SidCount <= 0 ) {
Status = STATUS_INSUFFICIENT_LOGON_INFO;
goto Cleanup;
}
Size += (RtlLengthSid(UserInfo->LogonDomainId) + sizeof(ULONG)) + RtlLengthSid(UserInfo->ExtraSids[0].Sid);
}
}
else
{
NumGroups += 2;
// Allocate sid space for LocalSystem, Administrators
Size += sizeof(LocalSystemSid) + RtlLengthSid(AdminsSid);
// Add space for the user sid
if( UserInfo->UserId )
{
Size += (RtlLengthSid(UserInfo->LogonDomainId) + sizeof(ULONG));
}
}
Size += (NumGroups - ANYSIZE_ARRAY)*sizeof(SID_AND_ATTRIBUTES);
V2 = (PLSA_TOKEN_INFORMATION_V2) KerbAllocate( Size );
if ( V2 == NULL ) {
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlZeroMemory((PVOID)V2, Size);
V2->Groups = (PTOKEN_GROUPS)(V2+1);
V2->Groups->GroupCount = 0;
CurrentSid = (PBYTE)&V2->Groups->Groups[NumGroups];
OLD_TO_NEW_LARGE_INTEGER( UserInfo->KickOffTime, V2->ExpirationTime );
if (!LocalSystem)
{
//
// If the UserId is non-zero, then it contians the users RID.
//
if ( UserInfo->UserId ) {
V2->User.User.Sid = (PSID)CurrentSid;
CurrentSid += KerbCopyDomainRelativeSid((PSID)CurrentSid, UserInfo->LogonDomainId, UserInfo->UserId);
}
//
// Make a copy of the primary group (a required field).
//
V2->PrimaryGroup.PrimaryGroup = (PSID)CurrentSid;
CurrentSid += KerbCopyDomainRelativeSid((PSID)CurrentSid, UserInfo->LogonDomainId, UserInfo->PrimaryGroupId );
}
else
{
//
// For local system, the user sid is LocalSystem and the primary
// group is LocalSystem
//
V2->User.User.Sid = (PSID)CurrentSid;
RtlCopySid(sizeof(LocalSystemSid), (PSID)CurrentSid, &LocalSystemSid);
CurrentSid += sizeof(LocalSystemSid);
//
// The real system token has LocalSystem for the primary
// group. However, the LSA will add the primary group to the
// list of groups if it isn't listed as a group, and since
// LocalSystem is the user sid, we don't want that.
//
V2->PrimaryGroup.PrimaryGroup = (PSID)CurrentSid;
SidLength = RtlLengthSid(AdminsSid);
RtlCopySid(SidLength, (PSID)CurrentSid, AdminsSid);
CurrentSid += SidLength;
//
// If there is a user sid, add it as a group id.
//
if ( UserInfo->UserId ) {
V2->Groups->Groups[V2->Groups->GroupCount].Attributes =
SE_GROUP_MANDATORY |
SE_GROUP_ENABLED|
SE_GROUP_ENABLED_BY_DEFAULT;
V2->Groups->Groups[V2->Groups->GroupCount].Sid = (PSID)CurrentSid;
CurrentSid += KerbCopyDomainRelativeSid((PSID)CurrentSid, UserInfo->LogonDomainId, UserInfo->UserId);
V2->Groups->GroupCount++;
}
//
// Add builtin administrators. This is not mandatory
//
V2->Groups->Groups[V2->Groups->GroupCount].Attributes =
SE_GROUP_ENABLED|
SE_GROUP_OWNER|
SE_GROUP_ENABLED_BY_DEFAULT;
V2->Groups->Groups[V2->Groups->GroupCount].Sid = V2->PrimaryGroup.PrimaryGroup;
V2->Groups->GroupCount++;
}
//
// Copy over all the groups passed as RIDs
//
for ( i=0; i < UserInfo->GroupCount; i++ ) {
V2->Groups->Groups[V2->Groups->GroupCount].Attributes = UserInfo->GroupIds[i].Attributes;
V2->Groups->Groups[V2->Groups->GroupCount].Sid = (PSID)CurrentSid;
CurrentSid += KerbCopyDomainRelativeSid((PSID)CurrentSid, UserInfo->LogonDomainId, UserInfo->GroupIds[i].RelativeId);
V2->Groups->GroupCount++;
}
//
// Add in the extra SIDs
//
if (UserInfo->UserFlags & LOGON_EXTRA_SIDS) {
ULONG index = 0;
//
// If the user SID wasn't passed as a RID, it is the first
// SID.
//
if ( !V2->User.User.Sid ) {
V2->User.User.Sid = (PSID)CurrentSid;
SidLength = RtlLengthSid(UserInfo->ExtraSids[index].Sid);
RtlCopySid(SidLength, (PSID)CurrentSid, UserInfo->ExtraSids[index].Sid);
CurrentSid += SidLength;
index++;
}
//
// Copy over all additional SIDs as groups.
//
for ( ; index < UserInfo->SidCount; index++ ) {
V2->Groups->Groups[V2->Groups->GroupCount].Attributes =
UserInfo->ExtraSids[index].Attributes;
V2->Groups->Groups[V2->Groups->GroupCount].Sid= (PSID)CurrentSid;
SidLength = RtlLengthSid(UserInfo->ExtraSids[index].Sid);
RtlCopySid(SidLength, (PSID)CurrentSid, UserInfo->ExtraSids[index].Sid);
CurrentSid += SidLength;
V2->Groups->GroupCount++;
}
}
//
// Check to see if any resouce groups exist
//
if (UserInfo->UserFlags & LOGON_RESOURCE_GROUPS) {
for ( i=0; i < UserInfo->ResourceGroupCount; i++ ) {
V2->Groups->Groups[V2->Groups->GroupCount].Attributes = UserInfo->ResourceGroupIds[i].Attributes;
V2->Groups->Groups[V2->Groups->GroupCount].Sid= (PSID)CurrentSid;
CurrentSid += KerbCopyDomainRelativeSid((PSID)CurrentSid, UserInfo->ResourceGroupDomainSid, UserInfo->ResourceGroupIds[i].RelativeId);
V2->Groups->GroupCount++;
}
}
ASSERT( ((PBYTE)V2 + Size) == CurrentSid );
if (!V2->User.User.Sid) {
Status = STATUS_INSUFFICIENT_LOGON_INFO;
goto Cleanup;
}
//
// There are no default privileges supplied.
// We don't have an explicit owner SID.
// There is no default DACL.
//
V2->Privileges = NULL;
V2->Owner.Owner = NULL;
V2->DefaultDacl.DefaultDacl = NULL;
//
// Return the Validation Information to the caller.
//
*TokenInformation = V2;
return STATUS_SUCCESS;
//
// Deallocate any memory we've allocated
//
Cleanup:
KerbFree( V2 );
return Status;
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateDelegationLogonSession
//
// Synopsis: Creates a logon session from the delegation information
// in the GSS checksum
//
// Effects:
//
// Arguments: LogonId - The logon id for the AP request, which will be used
// for the new logon session.
// Ticket - The ticket used for the AP request, containing the
// session key to decrypt the KERB_CRED
// GssChecksum - Checksum containing the delegation information
//
// Requires:
//
// Returns: NTSTATUS codes
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateDelegationLogonSession(
IN PLUID LogonId,
IN PKERB_ENCRYPTED_TICKET Ticket,
IN PKERB_GSS_CHECKSUM GssChecksum
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_CRED KerbCred = NULL;
PKERB_ENCRYPTED_CRED EncryptedCred = NULL;
PKERB_LOGON_SESSION LogonSession = NULL;
KERBERR KerbErr;
D_DebugLog((DEB_TRACE, "Building delegation logon session\n"));
if (GssChecksum->Delegation != 1)
{
D_DebugLog((DEB_ERROR, "Asked for GSS_C_DELEG_FLAG but Delegation != 1 = 0x%x. %ws, line %d\n",
GssChecksum->Delegation, THIS_FILE, __LINE__ ));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if (!KERB_SUCCESS(KerbUnpackKerbCred(
GssChecksum->DelegationInfo,
GssChecksum->DelegationLength,
&KerbCred
)))
{
D_DebugLog((DEB_WARN, "Failed to unpack kerb cred\n"));
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Now decrypt the encrypted part of the KerbCred.
//
KerbErr = KerbDecryptDataEx(
&KerbCred->encrypted_part,
&Ticket->key,
KERB_CRED_SALT,
(PULONG) &KerbCred->encrypted_part.cipher_text.length,
KerbCred->encrypted_part.cipher_text.value
);
if (!KERB_SUCCESS(KerbErr))
{
D_DebugLog((DEB_ERROR,"Failed to decrypt KERB_CRED: 0x%x. %ws, line %d\n",KerbErr, THIS_FILE, __LINE__));
if (KerbErr == KRB_ERR_GENERIC)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
else
{
Status = STATUS_LOGON_FAILURE;
//
// MIT clients don't encrypt the encrypted part, so drop through
//
}
}
//
// Now unpack the encrypted part.
//
if (!KERB_SUCCESS(KerbUnpackEncryptedCred(
KerbCred->encrypted_part.cipher_text.value,
KerbCred->encrypted_part.cipher_text.length,
&EncryptedCred
)))
{
//
// Use the old status if it is available.
//
if (NT_SUCCESS(Status))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
D_DebugLog((DEB_WARN, "Failed to unpack encrypted cred\n"));
goto Cleanup;
}
//
// Now build a logon session.
//
Status = KerbCreateLogonSessionFromKerbCred(
LogonId,
Ticket,
KerbCred,
EncryptedCred,
&LogonSession
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to create logon session from kerb cred: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__));
goto Cleanup;
}
KerbWriteLockLogonSessions(LogonSession);
LogonSession->LogonSessionFlags |= KERB_LOGON_DELEGATED ;
KerbUnlockLogonSessions(LogonSession);
KerbDereferenceLogonSession( LogonSession );
Cleanup:
if (EncryptedCred != NULL)
{
KerbFreeEncryptedCred(EncryptedCred);
}
if (KerbCred != NULL)
{
KerbFreeKerbCred(KerbCred);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateTokenFromTicket
//
// Synopsis: Pulls the PAC out of a ticket and
//
// Effects: Creates a logon session and a token
//
// Arguments: InternalTicket - The ticket off of which to base the
// token.
// Authenticator - Authenticator from the AP request,
// which may contain delegation information.
// NewLogonId - Receives the logon ID of the new logon session
// UserSid - Receives user's sid.
// NewTokenHandle - Receives the newly created token handle.
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateTokenFromTicket(
IN OPTIONAL PLUID AcceptingLogonId,
IN OPTIONAL PKERB_AP_REQUEST Request,
IN PKERB_ENCRYPTED_TICKET InternalTicket,
IN OPTIONAL PKERB_AUTHENTICATOR Authenticator,
IN ULONG ContextFlags,
IN PKERB_ENCRYPTION_KEY TicketKey,
IN PUNICODE_STRING ServiceDomain,
IN KERB_ENCRYPTION_KEY* pSessionKey,
OUT PLUID NewLogonId,
OUT PSID * UserSid,
OUT PHANDLE NewTokenHandle,
OUT PUNICODE_STRING ClientName,
OUT PUNICODE_STRING ClientDomain,
OUT PUNICODE_STRING ClientNetbiosDomain,
OUT OPTIONAL PS4U_DELEGATION_INFO* S4uDelegationInfo
)
{
NTSTATUS Status = STATUS_SUCCESS;
KERBERR KerbErr = KDC_ERR_NONE;
PPACTYPE Pac = NULL;
PKERB_AUTHORIZATION_DATA PacAuthData = NULL;
PKERB_AUTHORIZATION_DATA AuthData = NULL;
PKERB_IF_RELEVANT_AUTH_DATA * IfRelevantData = NULL;
PPAC_INFO_BUFFER LogonInfo = NULL;
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
PLSA_TOKEN_INFORMATION_V2 TokenInformation = NULL;
PLSA_TOKEN_INFORMATION_NULL TokenNull = NULL;
LSA_TOKEN_INFORMATION_TYPE TokenType = LsaTokenInformationNull;
PVOID LsaTokenInformation = NULL;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel = SecurityImpersonation;
ULONG NameType;
BOOLEAN BuildNullToken = FALSE;
LUID LogonId;
LUID BaseLuid = (*AcceptingLogonId);
LUID SystemLogonId = SYSTEM_LUID;
UNICODE_STRING Workstation = NULL_UNICODE_STRING;
UNICODE_STRING TempUserName;
UNICODE_STRING TempDomainName;
PKERB_GSS_CHECKSUM GssChecksum;
BOOLEAN IsLocalSystem = FALSE;
NTSTATUS SubStatus;
HANDLE TokenHandle = NULL;
BOOLEAN FreePac = FALSE;
ULONG TicketOptions = 0;
SECPKG_PRIMARY_CRED PrimaryCredentials;
DsysAssert(AcceptingLogonId->LowPart != 0);
RtlZeroMemory(
&PrimaryCredentials,
sizeof(SECPKG_PRIMARY_CRED)
);
LogonId.HighPart = 0;
LogonId.LowPart = 0;
*UserSid = NULL;
*NewLogonId = LogonId;
//
// Check to see if this was NULL session
//
if (ARGUMENT_PRESENT(InternalTicket))
{
TicketOptions = KerbConvertFlagsToUlong(&InternalTicket->flags);
if (!KERB_SUCCESS(KerbConvertPrincipalNameToString(
ClientName,
&NameType,
&InternalTicket->client_name
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString(
ClientDomain,
&InternalTicket->client_realm
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Convert the principal name into just a user name
//
(VOID) KerbSplitFullServiceName(
ClientName,
&TempDomainName,
&TempUserName
);
TokenType = LsaTokenInformationV2;
//
// Make sure there is some authorization data
//
if (((InternalTicket->bit_mask & KERB_ENCRYPTED_TICKET_authorization_data_present) != 0) &&
(InternalTicket->KERB_ENCRYPTED_TICKET_authorization_data != NULL))
{
AuthData = InternalTicket->KERB_ENCRYPTED_TICKET_authorization_data;
//
// Verify the auth data is valid
//
if (!KerbVerifyAuthData(
InternalTicket->KERB_ENCRYPTED_TICKET_authorization_data
))
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Get the PAC out of the authorization data
//
KerbErr = KerbGetPacFromAuthData(
InternalTicket->KERB_ENCRYPTED_TICKET_authorization_data,
&IfRelevantData,
&PacAuthData
);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
if (PacAuthData != NULL)
{
//
// Unmarshall the PAC
//
Pac = (PPACTYPE) PacAuthData->value.auth_data.value;
if (PAC_UnMarshal(Pac, PacAuthData->value.auth_data.length) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac. %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Verify the signature on the pac
//
Status = KerbVerifyPacSignature(
ServiceDomain,
Pac,
PacAuthData->value.auth_data.length,
TicketKey,
InternalTicket,
TRUE,
&ValidationInfo,
S4uDelegationInfo
);
if (!NT_SUCCESS(Status))
{
KerbReportPACError(
ClientName,
ClientDomain,
Status
);
DebugLog((DEB_ERROR, "Pac signature did not verify: domain %wZ, status %x\n", ServiceDomain, Status));
goto Cleanup;
}
}
}
//
// If we didn't find a PAC, try to build one locally
//
if (Pac == NULL)
{
PKERB_INTERNAL_NAME ClientKdcName = NULL;
NTSTATUS TempStatus;
//
// Convert the client's name into a usable format
//
if (!KERB_SUCCESS(KerbConvertPrincipalNameToKdcName(
&ClientKdcName,
&InternalTicket->client_name
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
TempStatus = KerbCreatePacForKerbClient(
&Pac,
ClientKdcName,
ClientDomain,
NULL
);
KerbFreeKdcName(&ClientKdcName);
if (!NT_SUCCESS(TempStatus))
{
//
// Reuse the error from above, it is is available.
//
if ((TempStatus == STATUS_NO_SUCH_USER) ||
(TempStatus == STATUS_PRIVILEGE_NOT_HELD))
{
D_DebugLog((DEB_TRACE,"Failed to create local pac for client : 0x%x\n",TempStatus));
BuildNullToken = TRUE;
Status = STATUS_SUCCESS;
}
else
{
if (NT_SUCCESS(Status))
{
Status = TempStatus;
}
DebugLog((DEB_ERROR,"Failed to create local pac for client : 0x%x\n",Status));
goto Cleanup;
}
}
//
// If we have a PAC, build everything else we need now
//
if (!BuildNullToken)
{
FreePac = TRUE;
KerbFreeString( ClientDomain );
KerbGlobalReadLock();
Status = KerbDuplicateString(
ClientDomain,
&KerbGlobalMachineName
);
KerbGlobalReleaseLock();
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Find the SAM validation info
//
LogonInfo = PAC_Find(
Pac,
PAC_LOGON_INFO,
NULL
);
if (LogonInfo == NULL)
{
DebugLog((DEB_ERROR,"Failed to find logon info! %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Now unmarshall the validation info
//
Status = PAC_UnmarshallValidationInfo(
&ValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to unmarshall validation info: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
}
}
if (!BuildNullToken)
{
//
// Check to see if the caller is local system on this
// machine
//
if (RtlEqualUnicodeString(
ClientName,
&KerbGlobalMachineServiceName,
TRUE) &&
KerbIsThisOurDomain(
ClientDomain
))
{
//
// check for local loopback case: if the session key entry
// exists, it is localsystem loopback
//
Status = KerbDoesSKeyExist(pSessionKey, &IsLocalSystem);
D_DebugLog((DEB_TRACE_LOOPBACK, "KerbCreateTokenFromTicket, KerbDoesSKeyExist Status %#x, IsLocalSystem? %s\n",
Status, (IsLocalSystem ? "true" : "false")));
KerbDeleteSKeyEntry(pSessionKey);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "Failed to detect local loopback: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
}
//
// Now we need to build a LSA_TOKEN_INFORMATION_V2 from the validation
// information
//
Status = KerbMakeTokenInformationV2(
ValidationInfo,
IsLocalSystem,
&TokenInformation
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to make token information v2: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
//
// Copy out the NT4 user name & domain name to give to the LSA. It
// requres the names from SAM because it generates the output of
// GetUserName.
//
// However, we only need to do this for MIT logons, otherwise, we
// should use the name in the ticket...
//
if (RtlEqualUnicodeString(ClientName, &ValidationInfo->EffectiveName, TRUE))
{
KerbFreeString( ClientName );
Status = KerbDuplicateString(
ClientName,
&ValidationInfo->EffectiveName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
D_DebugLog((DEB_TRACE_LOGON, "CreateToken : ClientName - %wZ\n", ClientName));
Status = KerbDuplicateString(
ClientNetbiosDomain,
&ValidationInfo->LogonDomainName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
D_DebugLog((DEB_TRACE_LOGON, "CreateToken : ClientSamDomain - %wZ\n", ClientNetbiosDomain));
}
//
// Now create the token.
//
LsaTokenInformation = TokenInformation;
Status = KerbDuplicateSid(
UserSid,
TokenInformation->User.User.Sid
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
}
else
{
BuildNullToken = TRUE;
}
if (BuildNullToken)
{
SID AnonymousSid = {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_ANONYMOUS_LOGON_RID };
TokenNull = (PLSA_TOKEN_INFORMATION_NULL) KerbAllocate(sizeof(LSA_TOKEN_INFORMATION_NULL));
if (TokenNull == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
LsaTokenInformation = TokenNull;
TokenNull->Groups = NULL;
TokenNull->ExpirationTime = KerbGlobalWillNeverTime;
TokenType = LsaTokenInformationNull;
Status = KerbDuplicateSid(
UserSid,
&AnonymousSid
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
//
// Create a logon session.
//
NtAllocateLocallyUniqueId(&LogonId);
Status = LsaFunctions->CreateLogonSession(&LogonId);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR, "Failed to create logon session: 0x%x. %ws, line %d\n", Status, THIS_FILE, __LINE__));
goto Cleanup;
}
//
// Add additional names to the logon session name map. Ignore failure
// as that just means GetUserNameEx calls for these name formats later
// on will be satisfied by hitting the wire.
//
if (ValidationInfo && ValidationInfo->FullName.Length)
{
I_LsaIAddNameToLogonSession(&LogonId, NameDisplay, &ValidationInfo->FullName);
}
if (ClientDomain->Length && ClientDomain->Buffer)
{
I_LsaIAddNameToLogonSession(&LogonId, NameDnsDomain, ClientDomain);
}
//
// If the caller wanted an identify or delegate level token, duplicate the token
// now.
//
if ((ContextFlags & ISC_RET_IDENTIFY) != 0)
{
ImpersonationLevel = SecurityIdentification;
}
else if ((ContextFlags & ISC_RET_DELEGATE) != 0)
{
ImpersonationLevel = SecurityDelegation;
}
if(ClientName->Length && ClientName->Buffer)
{
PrimaryCredentials.DownlevelName.Buffer = (PWSTR)LsaFunctions->AllocateLsaHeap(ClientName->Length);
if (PrimaryCredentials.DownlevelName.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
PrimaryCredentials.DownlevelName.Length =
PrimaryCredentials.DownlevelName.MaximumLength = ClientName->Length;
RtlCopyMemory(
PrimaryCredentials.DownlevelName.Buffer,
ClientName->Buffer,
ClientName->Length
);
}
if ( ClientNetbiosDomain->Length && ClientNetbiosDomain->Buffer )
{
PrimaryCredentials.DomainName.Buffer = (PWSTR)LsaFunctions->AllocateLsaHeap( ClientNetbiosDomain->Length );
if (PrimaryCredentials.DomainName.Buffer == NULL) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
PrimaryCredentials.DomainName.Length =
PrimaryCredentials.DomainName.MaximumLength = ClientNetbiosDomain->Length;
RtlCopyMemory(
PrimaryCredentials.DomainName.Buffer,
ClientNetbiosDomain->Buffer,
ClientNetbiosDomain->Length
);
}
else
{
//
// Huh? No pac validation info - use the DNS name ...
//
Status = KerbDuplicateString(
ClientNetbiosDomain,
ClientDomain
);
if (!NT_SUCCESS( Status ))
{
goto Cleanup;
}
}
DebugLog((DEB_TRACE, "KerbCreateTokenFromTicket for %wZ\\%wZ, %wZ\n", &PrimaryCredentials.DomainName, &PrimaryCredentials.DownlevelName, &PrimaryCredentials.Upn));
Status = LsaFunctions->CreateTokenEx(
&LogonId,
&KerberosSource,
Network,
ImpersonationLevel,
TokenType,
LsaTokenInformation,
NULL, // no token groups
&Workstation,
(ValidationInfo == NULL ? NULL : &ValidationInfo->ProfilePath),
&PrimaryCredentials,
SecSessionPrimaryCred,
&TokenHandle,
&SubStatus
);
// LsapCreateToken frees the TokenInformation structure for us, so
// we don't need these pointers anymore.
TokenInformation = NULL;
TokenNull = NULL;
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to create token: 0x%x. %ws, line %d\n",Status, THIS_FILE, __LINE__));
goto Cleanup;
}
if (!NT_SUCCESS(SubStatus))
{
DebugLog((DEB_ERROR,"Failed to create token, substatus = 0x%x. %ws, line %d\n",SubStatus, THIS_FILE, __LINE__));
Status = SubStatus;
goto Cleanup;
}
//
// Check the delegation information to see if we need to create
// a logon session for this.
//
// For impersonated contexts, we'll also create a logon session, but
// there won't be much extra information in it.
//
if ((ContextFlags & ISC_RET_DELEGATE) != 0)
{
DsysAssert(ARGUMENT_PRESENT(Authenticator));
GssChecksum = (PKERB_GSS_CHECKSUM) Authenticator->checksum.checksum.value;
DsysAssert(GssChecksum != 0);
Status = KerbCreateDelegationLogonSession(
&LogonId,
InternalTicket,
GssChecksum
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to create delgation logon session: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__ ));
goto Cleanup;
}
}
else if (ARGUMENT_PRESENT(Request))
{
Status = KerbCreateLogonSessionFromTicket(
&LogonId,
&BaseLuid,
ClientName,
ClientDomain,
Request,
InternalTicket,
NULL
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "Failed to create ASC logon session %x\n", Status));
goto Cleanup;
}
}
//
//
//
// Apply any restrictions from the auth data
// Note: Punted until Blackcomb
//
#ifdef RESTRICTED_TOKEN
if (AuthData != NULL)
{
Status = KerbApplyAuthDataRestrictions(
&TokenHandle,
AuthData
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
#endif
*NewLogonId = LogonId;
*NewTokenHandle = TokenHandle;
Cleanup:
if(PrimaryCredentials.DownlevelName.Buffer)
{
LsaFunctions->FreeLsaHeap(PrimaryCredentials.DownlevelName.Buffer);
}
if(PrimaryCredentials.DomainName.Buffer)
{
LsaFunctions->FreeLsaHeap(PrimaryCredentials.DomainName.Buffer);
}
if(PrimaryCredentials.LogonServer.Buffer)
{
LsaFunctions->FreeLsaHeap(PrimaryCredentials.LogonServer.Buffer);
}
if(PrimaryCredentials.UserSid)
{
LsaFunctions->FreeLsaHeap(PrimaryCredentials.UserSid);
}
if(PrimaryCredentials.LogonServer.Buffer)
{
LsaFunctions->FreeLsaHeap(PrimaryCredentials.LogonServer.Buffer);
}
if(PrimaryCredentials.UserSid)
{
LsaFunctions->FreeLsaHeap(PrimaryCredentials.UserSid);
}
if (TokenInformation != NULL)
{
KerbFree( TokenInformation );
}
if (TokenNull != NULL)
{
KerbFree(TokenNull);
}
if (!NT_SUCCESS(Status))
{
//
// Note: if we have created a token, we don't want to delete
// the logon session here because we will end up dereferencing
// the logon session twice.
//
if (TokenHandle != NULL)
{
NtClose(TokenHandle);
}
else if ((LogonId.LowPart != 0) || (LogonId.HighPart != 0))
{
if (!RtlEqualLuid(
&LogonId,
&SystemLogonId
))
{
LsaFunctions->DeleteLogonSession(&LogonId);
}
}
if (*UserSid != NULL)
{
KerbFree(*UserSid);
*UserSid = NULL;
}
}
if (FreePac && (Pac != NULL))
{
MIDL_user_free(Pac);
}
if (ValidationInfo != NULL)
{
MIDL_user_free(ValidationInfo);
}
if (IfRelevantData != NULL)
{
KerbFreeData(
PKERB_IF_RELEVANT_AUTH_DATA_PDU,
IfRelevantData
);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbExtractCachedCreds
//
// Synopsis: Extracts the cached credentials from a logon ticket
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbExtractCachedCreds(
IN PPACTYPE Pac,
IN PKERB_ENCRYPTION_KEY CredentialKey,
OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * CachedCredentials
)
{
NTSTATUS Status = STATUS_SUCCESS;
KERBERR KerbErr;
PPAC_INFO_BUFFER CredBuffer = NULL;
PPAC_CREDENTIAL_INFO CredInfo = NULL;
PBYTE CredData = NULL;
ULONG CredDataSize = 0;
PSECPKG_SUPPLEMENTAL_CRED_ARRAY DecodedCreds = NULL;
KERB_ENCRYPTED_DATA EncryptedData = {0};
*CachedCredentials = NULL;
//
// If we don't have a key to obtain credentials, o.k.
//
if (!ARGUMENT_PRESENT(CredentialKey) ||
(CredentialKey->keyvalue.value == NULL))
{
goto Cleanup;
}
CredBuffer = PAC_Find(
Pac,
PAC_CREDENTIAL_TYPE,
NULL // no previous instance
);
if (CredBuffer == NULL)
{
//
// We have no credentials. O.k.
//
goto Cleanup;
}
//
// Build an encrypted data structure so we can decrypt the response
//
CredInfo = (PPAC_CREDENTIAL_INFO) CredBuffer->Data;
if (CredBuffer->cbBufferSize < sizeof(PAC_CREDENTIAL_INFO))
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
EncryptedData.version = CredInfo->Version;
EncryptedData.encryption_type = CredInfo->EncryptionType;
EncryptedData.cipher_text.value = CredInfo->Data;
CredDataSize = CredBuffer->cbBufferSize -
FIELD_OFFSET(PAC_CREDENTIAL_INFO, Data);
EncryptedData.cipher_text.length = CredDataSize;
//
// Decrypt in place
//
CredData = CredInfo->Data;
KerbErr = KerbDecryptDataEx(
&EncryptedData,
CredentialKey,
KERB_NON_KERB_SALT,
&CredDataSize,
(PBYTE) CredData
);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
D_DebugLog((DEB_ERROR,"Failed to decrypt credentials: 0x%x. %ws, line %d\n",KerbErr, THIS_FILE, __LINE__));
goto Cleanup;
}
//
// Now build the return credentials
//
//
// Now unmarshall the credential data
//
Status = PAC_UnmarshallCredentials(
&DecodedCreds,
CredData,
CredDataSize
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
*CachedCredentials = DecodedCreds;
DecodedCreds = NULL;
Cleanup:
if (DecodedCreds != NULL)
{
MIDL_user_free(DecodedCreds);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCacheLogonInformation
//
// Synopsis: Calls MSV1_0 to cache logon information. This routine
// converts the pac into MSV1_0 compatible data and
// makes a call to MSV1_0 to store it.
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
VOID
KerbCacheLogonInformation(
IN PUNICODE_STRING LogonUserName,
IN PUNICODE_STRING LogonDomainName,
IN OPTIONAL PUNICODE_STRING Password,
IN OPTIONAL PUNICODE_STRING DnsDomainName,
IN OPTIONAL PUNICODE_STRING Upn,
IN OPTIONAL KERB_LOGON_SESSION* LogonSession,
IN ULONG Flags,
IN OPTIONAL PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo,
IN OPTIONAL PVOID SupplementalCreds,
IN OPTIONAL ULONG SupplementalCredSize
)
{
NETLOGON_VALIDATION_SAM_INFO4 ValidationInfoToUse = {0};
NETLOGON_INTERACTIVE_INFO MsvLogonInfo = {0};
MSV1_0_CACHE_LOGON_REQUEST CacheRequest;
UNICODE_STRING MsvPackageName = CONSTANT_UNICODE_STRING(TEXT(MSV1_0_PACKAGE_NAME));
PVOID OutputBuffer = NULL;
ULONG OutputBufferSize;
NTSTATUS Status,TempStatus, SubStatus;
UNICODE_STRING LocalMachineName;
UNICODE_STRING DummyString;
PBYTE Tmp, Tmp2;
ULONG NewGroupCount = 0, Index = 0, Index2 = 0;
PVOID SupplementalMitCreds = NULL;
ULONG SupplementalMitCredSize = 0;
BOOL MitLogon = LogonSession ? ((LogonSession->LogonSessionFlags & KERB_LOGON_MIT_REALM) != 0) : FALSE;
NETLOGON_VALIDATION_SAM_INFO3 TempValidation = {0};
PNETLOGON_SID_AND_ATTRIBUTES NewGroups = NULL;
BOOL fUseTemp = FALSE;
LocalMachineName.Buffer = NULL;
if (KerbGetGlobalRole() == KerbRoleRealmlessWksta)
{
D_DebugLog((DEB_WARN, "KerbCacheLogonInformation does not support cached logon for reamless workstation\n"));
Status = STATUS_NOT_SUPPORTED;
goto Cleanup;
}
//
// Cram resource groups into extra sids, as INFO4 doesn't support
// them.
//
if ( ARGUMENT_PRESENT(ValidationInfo) &&
((ValidationInfo->UserFlags & LOGON_RESOURCE_GROUPS) != 0))
{
D_DebugLog((DEB_TRACE, "Found resource groups in PAC\n"));
if (ValidationInfo->ResourceGroupCount != 0)
{
DebugLog((DEB_ERROR, " Expanding resource groups\n"));
//
// Build a new structure that contains the resource groups
//
RtlCopyMemory(
&TempValidation,
ValidationInfo,
sizeof(NETLOGON_VALIDATION_SAM_INFO3)
);
NewGroupCount = ValidationInfo->SidCount + ValidationInfo->ResourceGroupCount;
NewGroups = (PNETLOGON_SID_AND_ATTRIBUTES) KerbAllocate(sizeof(NETLOGON_SID_AND_ATTRIBUTES) * NewGroupCount);
if (NewGroups == NULL)
{
Status= STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Add in all the extra sids
//
RtlCopyMemory(
NewGroups,
ValidationInfo->ExtraSids,
ValidationInfo->SidCount * sizeof(NETLOGON_SID_AND_ATTRIBUTES)
);
Index2 = ValidationInfo->SidCount;
for (Index = 0; Index < ValidationInfo->ResourceGroupCount ; Index++)
{
NewGroups[Index2].Attributes = ValidationInfo->ResourceGroupIds[Index].Attributes;
NewGroups[Index2].Sid = KerbMakeDomainRelativeSid(
ValidationInfo->ResourceGroupDomainSid,
ValidationInfo->ResourceGroupIds[Index].RelativeId );
if ( NewGroups[Index2].Sid == NULL )
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
Index2++;
}
DsysAssert(NewGroupCount == Index2);
TempValidation.UserFlags |= LOGON_EXTRA_SIDS;
TempValidation.SidCount = NewGroupCount;
TempValidation.ExtraSids = NewGroups;
fUseTemp = TRUE;
}
}
//
// Build up the NETLOGON_VALIDATION_SAM_INFO4 structure to pass to NTLM
//
if (ARGUMENT_PRESENT(ValidationInfo))
{
RtlCopyMemory(&ValidationInfoToUse,
(fUseTemp ? &TempValidation : ValidationInfo),
sizeof(NETLOGON_VALIDATION_SAM_INFO2));
if (ARGUMENT_PRESENT(DnsDomainName))
{
ValidationInfoToUse.DnsLogonDomainName = *DnsDomainName;
}
}
MsvLogonInfo.Identity.LogonDomainName = *LogonDomainName;
MsvLogonInfo.Identity.UserName = *LogonUserName;
CacheRequest.RequestFlags = Flags;
//
// If this was a logon to an MIT realm that we know about,
// then add the MIT username (upn?) & realm to the supplemental data
//
if (MitLogon)
{
D_DebugLog((DEB_TRACE, "KerbCacheLogonInformation caching MIT princ: logon %wZ\\%wZ, account %wZ\\%wZ, cred %wZ\\%wZ\n",
LogonDomainName, LogonUserName,
&ValidationInfoToUse.LogonDomainName, &ValidationInfoToUse.EffectiveName,
&LogonSession->PrimaryCredentials.DomainName, &LogonSession->PrimaryCredentials.UserName));
CacheRequest.RequestFlags |= MSV1_0_CACHE_LOGON_REQUEST_MIT_LOGON;
//
// Marshall the MIT info into the supplemental creds.
//
SupplementalMitCredSize =
(2* sizeof(UNICODE_STRING)) +
ROUND_UP_COUNT(LogonSession->PrimaryCredentials.UserName.Length, ALIGN_LONG) +
ROUND_UP_COUNT(LogonSession->PrimaryCredentials.DomainName.Length, ALIGN_LONG);
SafeAllocaAllocate(SupplementalMitCreds, SupplementalMitCredSize);
if (NULL == SupplementalMitCreds)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
DummyString.Length = DummyString.MaximumLength = LogonSession->PrimaryCredentials.UserName.Length;
Tmp = (PBYTE) (SupplementalMitCreds) + sizeof(UNICODE_STRING);
if (DummyString.Length > 0)
{
RtlCopyMemory(
Tmp,
LogonSession->PrimaryCredentials.UserName.Buffer,
LogonSession->PrimaryCredentials.UserName.Length
);
DummyString.Buffer = (PWSTR) UlongToPtr(RtlPointerToOffset(SupplementalMitCreds, Tmp));
}
else
{
DummyString.Buffer = NULL;
}
RtlCopyMemory(
SupplementalMitCreds,
&DummyString,
sizeof(UNICODE_STRING)
);
Tmp2 = Tmp + ROUND_UP_COUNT(DummyString.Length, ALIGN_LONG);
Tmp += ROUND_UP_COUNT(DummyString.Length, ALIGN_LONG) + sizeof(UNICODE_STRING);
DummyString.Length = DummyString.MaximumLength = LogonSession->PrimaryCredentials.DomainName.Length;
if (DummyString.Length > 0)
{
RtlCopyMemory(
Tmp,
LogonSession->PrimaryCredentials.DomainName.Buffer,
LogonSession->PrimaryCredentials.DomainName.Length
);
DummyString.Buffer = (PWSTR) UlongToPtr(RtlPointerToOffset(SupplementalMitCreds, Tmp));
}
else
{
DummyString.Buffer = NULL;
}
RtlCopyMemory(
Tmp2,
&DummyString,
sizeof(UNICODE_STRING)
);
CacheRequest.SupplementalCacheData = SupplementalMitCreds;
CacheRequest.SupplementalCacheDataLength = SupplementalMitCredSize;
}
else
{
CacheRequest.RequestFlags = Flags;
CacheRequest.SupplementalCacheData = SupplementalCreds;
CacheRequest.SupplementalCacheDataLength = SupplementalCredSize;
}
//
// Store the originating package of the logon
//
MsvLogonInfo.Identity.ParameterControl = RPC_C_AUTHN_GSS_KERBEROS;
KerbGlobalReadLock();
Status = KerbDuplicateString( &LocalMachineName, &KerbGlobalMachineName );
KerbGlobalReleaseLock();
if(!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR, "Failed to duplicate KerbGlobalMachineName\n"));
goto Cleanup;
}
MsvLogonInfo.Identity.Workstation = LocalMachineName;
if (ARGUMENT_PRESENT(Password))
{
Status = RtlCalculateNtOwfPassword(
Password,
&MsvLogonInfo.NtOwfPassword
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to calculate NT OWF for %wZ. %ws, line %d\n",Password, THIS_FILE, __LINE__));
goto Cleanup;
}
}
CacheRequest.MessageType = MsV1_0CacheLogon;
CacheRequest.LogonInformation = &MsvLogonInfo;
CacheRequest.ValidationInformation = &ValidationInfoToUse;
//
// tell NTLM it's a INFO4 structure.
//
CacheRequest.RequestFlags |= MSV1_0_CACHE_LOGON_REQUEST_INFO4;
TempStatus = LsaFunctions->CallPackage(
&MsvPackageName,
&CacheRequest,
sizeof(CacheRequest),
&OutputBuffer,
&OutputBufferSize,
&SubStatus
);
if (!NT_SUCCESS(TempStatus) || !NT_SUCCESS(SubStatus))
{
DebugLog((DEB_ERROR, "KerbCacheLogonInformation failed to cache credentials: 0x%x, 0x%x. %ws, line %d\n", TempStatus, SubStatus, THIS_FILE, __LINE__));
}
Cleanup:
KerbFreeString( &LocalMachineName );
if (NewGroups != NULL)
{
for (Index = ValidationInfo->SidCount; Index < NewGroupCount ; Index++ )
{
if (NewGroups[Index].Sid != 0)
{
KerbFree(NewGroups[Index].Sid);
}
}
KerbFree(NewGroups);
}
SafeAllocaFree(SupplementalMitCreds);
}
//+-------------------------------------------------------------------------
//
// Function: KerbGetCredsFromU2UTicket
//
// Synopsis: Verifies the ticket, and gets the credentials from the PAC
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//-----
NTSTATUS
KerbGetCredsFromU2UTicket(
IN PKERB_TICKET_CACHE_ENTRY U2UTicket,
IN PKERB_TICKET_CACHE_ENTRY Tgt,
IN OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * OutputCreds,
OUT PNETLOGON_VALIDATION_SAM_INFO3* ValidationInfo
)
{
KERBERR KerbErr = KDC_ERR_NONE;
NTSTATUS Status = STATUS_SUCCESS;
PPACTYPE Pac = NULL;
PKERB_AUTHORIZATION_DATA PacAuthData = NULL;
PKERB_AUTHORIZATION_DATA AuthData = NULL;
PKERB_IF_RELEVANT_AUTH_DATA * IfRelevantData = NULL;
PKERB_ENCRYPTED_TICKET EncryptedTicket = NULL;
PSECPKG_SUPPLEMENTAL_CRED_ARRAY LocalCreds = NULL;
*OutputCreds = NULL;
//
// Validate the ticket
//
KerbErr = KerbVerifyTicket(
&U2UTicket->Ticket,
0,
NULL,
NULL,
&Tgt->SessionKey,
NULL,
&EncryptedTicket
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR, "KerbVerifyTicket failed - %x\n", KerbErr));
goto Cleanup;
}
//
// Extract the PAC
//
if (((EncryptedTicket->bit_mask & KERB_ENCRYPTED_TICKET_authorization_data_present) == 0) ||
(EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data == NULL))
{
D_DebugLog((DEB_ERROR, "No authdata!\n"));
DsysAssert(FALSE);
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
AuthData = EncryptedTicket->KERB_ENCRYPTED_TICKET_authorization_data;
//
// Verify the auth data is valid
//
if (!KerbVerifyAuthData( AuthData ))
{
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Get the PAC out of the authorization data
//
KerbErr = KerbGetPacFromAuthData(
AuthData,
&IfRelevantData,
&PacAuthData
);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
if ( PacAuthData == NULL )
{
D_DebugLog((DEB_ERROR, "No PAC!\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Unmarshall the PAC
//
Pac = (PPACTYPE) PacAuthData->value.auth_data.value;
if (PAC_UnMarshal(Pac, PacAuthData->value.auth_data.length) == 0)
{
D_DebugLog((DEB_ERROR, "Failed to unmarshal pac. %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Verify the signature on the pac
//
Status = KerbVerifyPacSignature(
&Tgt->ClientDomainName,
Pac,
PacAuthData->value.auth_data.length,
&Tgt->SessionKey,
EncryptedTicket,
FALSE,
ValidationInfo,
NULL
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_WARN,"Pac signature did not verify %x\n", Status));
DsysAssert(FALSE);
goto Cleanup;
}
Status = KerbExtractCachedCreds(
Pac,
&Tgt->CredentialKey,
&LocalCreds
);
if (!NT_SUCCESS( Status ))
{
DebugLog((DEB_ERROR, "KerbExtractCachedCreds failed %x\n", Status));
goto Cleanup;
}
*OutputCreds = LocalCreds;
LocalCreds = NULL;
Cleanup:
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError( KerbErr );
}
if ( LocalCreds )
{
MIDL_user_free( LocalCreds );
}
if (IfRelevantData != NULL)
{
KerbFreeData(
PKERB_IF_RELEVANT_AUTH_DATA_PDU,
IfRelevantData
);
}
if ( EncryptedTicket )
{
KerbFreeTicket( EncryptedTicket );
}
return (Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbCreateTokenFromLogonTicket
//
// Synopsis: Creates a token from a ticket to the workstation
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
NTSTATUS
KerbCreateTokenFromLogonTicket(
IN OPTIONAL PKERB_TICKET_CACHE_ENTRY LogonTicket,
IN PLUID LogonId,
IN PKERB_INTERACTIVE_LOGON KerbLogonInfo,
IN BOOLEAN RealmlessWkstaLogon,
IN SECURITY_LOGON_TYPE LogonType,
IN OPTIONAL PKERB_ENCRYPTION_KEY CredentialKey,
IN OPTIONAL PKERB_MESSAGE_BUFFER ForwardedTgt,
IN OPTIONAL PUNICODE_STRING MappedName,
IN OPTIONAL PKERB_INTERNAL_NAME S4UClient,
IN OPTIONAL PUNICODE_STRING S4URealm,
IN OPTIONAL PLUID AlternateLuid,
IN PKERB_LOGON_SESSION LogonSession,
OUT PLSA_TOKEN_INFORMATION_TYPE TokenInformationType,
OUT PVOID *NewTokenInformation,
OUT PULONG ProfileBufferLength,
OUT PVOID * ProfileBuffer,
OUT PSECPKG_PRIMARY_CRED PrimaryCredentials,
OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * CachedCredentials,
OUT PNETLOGON_VALIDATION_SAM_INFO4 * ppValidationInfo
)
{
NTSTATUS Status = STATUS_SUCCESS;
PKERB_LOGON_SESSION SystemLogonSession = NULL;
LUID SystemLogonId = SYSTEM_LUID;
BOOLEAN TicketCacheLocked = FALSE;
BOOLEAN LogonSessionsLocked = FALSE;
PKERB_ENCRYPTED_TICKET Ticket = NULL;
PPACTYPE Pac = NULL;
PKERB_AUTHORIZATION_DATA PacAuthData = NULL;
PKERB_IF_RELEVANT_AUTH_DATA * IfRelevantData = NULL;
PPAC_INFO_BUFFER LogonInfo = NULL;
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
PNETLOGON_VALIDATION_SAM_INFO4 ValidationInfo4 = NULL;
PLSA_TOKEN_INFORMATION_V2 TokenInformation = NULL;
PKERB_ENCRYPTION_KEY WkstaKey = NULL;
BOOLEAN FreePac = FALSE;
KERBERR KerbErr;
UNICODE_STRING LocalDnsDomain = {0};
LPWSTR lpDnsDomainName;
UNICODE_STRING CRealm = {0};
UNICODE_STRING CName = {0};
UNICODE_STRING WkstaDomain = {0};
ULONG NameType;
// cache interactive logons only
BOOLEAN CacheLogon = (LogonType == Interactive || LogonType == Service ||
LogonType == Batch || LogonType == RemoteInteractive );
BOOLEAN AllowWorkstationLogon = (LogonType == Network || LogonType == NetworkCleartext);
BOOLEAN CheckIdentify = ((KerbLogonInfo->MessageType == KerbTicketLogon) ||
(KerbLogonInfo->MessageType == KerbTicketUnlockLogon) ||
(KerbLogonInfo->MessageType == KerbS4ULogon));
*ProfileBuffer = NULL;
*NewTokenInformation = NULL;
*ppValidationInfo = NULL;
//
// If you're not on a "joined" wksta, you don't need to use the
// system key. Otherwise, locate the sytem logon session, which contains the key
// to decrypt the ticket
//
if (!RealmlessWkstaLogon)
{
//
// S4U Logons will take place in the caller's context, and tickets
// will be given directly to caller.
//
if (KerbLogonInfo->MessageType == KerbS4ULogon)
{
SystemLogonSession = KerbReferenceLogonSession(
AlternateLuid,
FALSE
);
DsysAssert(SystemLogonSession != NULL);
if (SystemLogonSession == NULL)
{
Status = STATUS_NO_SUCH_LOGON_SESSION;
goto Cleanup;
}
DsysAssert( !LogonSessionsLocked );
KerbReadLockLogonSessions(SystemLogonSession);
LogonSessionsLocked = TRUE;
DsysAssert( !TicketCacheLocked );
KerbReadLockTicketCache();
TicketCacheLocked = TRUE;
Status = KerbDuplicateString(
&LocalDnsDomain,
&SystemLogonSession->PrimaryCredentials.DomainName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (LogonTicket && (LogonTicket->CacheFlags & KERB_TICKET_CACHE_TKT_ENC_IN_SKEY))
{
D_DebugLog((DEB_TRACE, "KerbCreateTokenFromLogonTicket using LogonTicketKey supplied\n"));
WkstaKey = &LogonTicket->SessionKey; // use supplied logon ticket key
}
}
else
{
SystemLogonSession = KerbReferenceLogonSession(
&SystemLogonId,
FALSE
);
DsysAssert(SystemLogonSession != NULL);
if (SystemLogonSession == NULL)
{
Status = STATUS_NO_SUCH_LOGON_SESSION;
goto Cleanup;
}
DsysAssert((SystemLogonSession->LogonSessionFlags & KERB_LOGON_NO_PASSWORD) == 0);
Status = KerbGetOurDomainName(
&LocalDnsDomain
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
DsysAssert( !LogonSessionsLocked );
KerbReadLockLogonSessions(SystemLogonSession);
LogonSessionsLocked = TRUE;
DsysAssert( !TicketCacheLocked );
KerbReadLockTicketCache();
TicketCacheLocked = TRUE;
}
//
// Decrypt the ticket
//
//
// Get the appropriate key
//
if (!WkstaKey)
{
D_DebugLog((DEB_TRACE, "KerbCreateTokenFromLogonTicket getting WkstaKey from SystemLogonSession\n"));
WkstaKey = KerbGetKeyFromList(
SystemLogonSession->PrimaryCredentials.Passwords,
LogonTicket->Ticket.encrypted_part.encryption_type
);
if (WkstaKey == NULL)
{
D_DebugLog((DEB_ERROR, "Couldn't find correct key type: 0x%x. %ws, line %d\n",
LogonTicket->Ticket.encrypted_part.encryption_type, THIS_FILE, __LINE__ ));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
}
KerbErr = KerbVerifyTicket(
&LogonTicket->Ticket,
1,
&KerbGlobalMachineServiceName,
&LocalDnsDomain,
WkstaKey,
NULL, // don't check time
&Ticket
);
//
// Check that expired ticket for ticket logon are handled properly.
// The client may pass a flag to explicitly allow an expired ticket.
// This ticket makes the logon fail if the ticket is not expired.
//
if ((KerbLogonInfo->MessageType == KerbTicketLogon) ||
(KerbLogonInfo->MessageType == KerbTicketUnlockLogon))
{
BOOLEAN AllowExpired = FALSE;
PKERB_TICKET_LOGON TicketLogon = (PKERB_TICKET_LOGON) KerbLogonInfo;
if ((TicketLogon->Flags & KERB_LOGON_FLAG_ALLOW_EXPIRED_TICKET) != 0)
{
AllowExpired = TRUE;
}
if (AllowExpired)
{
if (KerbErr == KDC_ERR_NONE)
{
Status = STATUS_INVALID_PARAMETER;
D_DebugLog((DEB_ERROR,"Can't allow expired ticket on a non-expired ticket\n"));
goto Cleanup;
}
else if (KerbErr == KRB_AP_ERR_TKT_EXPIRED)
{
KerbErr = KDC_ERR_NONE;
}
}
}
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"Failed to decrypt workstation ticket: 0x%x. %ws, line %d\n",KerbErr, THIS_FILE, __LINE__));
if (KerbErr == KRB_AP_ERR_MODIFIED)
{
Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
}
else
{
Status = KerbMapKerbError(KerbErr);
}
}
//
// If that failed, try again using the old password of the server
//
if ((Status == STATUS_TRUSTED_RELATIONSHIP_FAILURE) &&
(SystemLogonSession->PrimaryCredentials.OldPasswords != NULL))
{
DebugLog((DEB_TRACE,"Current system password failed, trying old password\n"));
//
// Get the appropriate key
//
WkstaKey = KerbGetKeyFromList(
SystemLogonSession->PrimaryCredentials.OldPasswords,
LogonTicket->Ticket.encrypted_part.encryption_type
);
if (WkstaKey == NULL)
{
DebugLog((DEB_ERROR,"Couldn't find correct key type: 0x%x. %ws, line %d\n",
LogonTicket->Ticket.encrypted_part.encryption_type, THIS_FILE, __LINE__ ));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
KerbErr = KerbVerifyTicket(
&LogonTicket->Ticket,
1,
&KerbGlobalMachineServiceName,
&LocalDnsDomain,
WkstaKey,
NULL, // don't check time
&Ticket
);
if (!KERB_SUCCESS(KerbErr))
{
DebugLog((DEB_ERROR,"Failed to decrypt workstation ticket. %ws, line %d\n", THIS_FILE, __LINE__));
if (KerbErr == KRB_AP_ERR_MODIFIED)
{
Status = STATUS_TRUSTED_RELATIONSHIP_FAILURE;
}
else
{
Status = KerbMapKerbError(KerbErr);
}
}
else
{
Status = STATUS_SUCCESS;
}
}
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// Check out the client name in the ticket. It must match, exactly,
// what was requested.
//
if (KerbLogonInfo->MessageType == KerbS4ULogon)
{
BOOLEAN Result = FALSE;
DsysAssert(ARGUMENT_PRESENT(S4UClient));
DsysAssert(ARGUMENT_PRESENT(S4URealm));
KerbErr = KerbCompareKdcNameToPrincipalName(
&Ticket->client_name,
S4UClient,
&Result
);
if (!KERB_SUCCESS(KerbErr))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
else if (!Result)
{
//
// TBD: Audit?
//
DebugLog((DEB_ERROR, "S4UClient name != ticket CLIENTNAME\n"));
Status = STATUS_LOGON_FAILURE;
DsysAssert(FALSE);
goto Cleanup;
}
KerbErr = KerbCompareUnicodeRealmToKerbRealm(
&Ticket->client_realm,
S4URealm,
&Result
);
if (!KERB_SUCCESS(KerbErr))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
else if (!Result)
{
//
// TBD: Audit?
//
DebugLog((DEB_ERROR, "S4UClient REALM != ticket REALM\n"));
Status = STATUS_LOGON_FAILURE;
DsysAssert(FALSE);
goto Cleanup;
}
}
//
// Make sure there is some authorization data
//
if (((Ticket->bit_mask & KERB_ENCRYPTED_TICKET_authorization_data_present) != 0) &&
(Ticket->KERB_ENCRYPTED_TICKET_authorization_data != NULL))
{
KERB_ENCRYPTION_KEY LocalKey = {0};
//
// Verify the auth data is valid
//
if (!KerbVerifyAuthData(
Ticket->KERB_ENCRYPTED_TICKET_authorization_data
))
{
D_DebugLog((DEB_ERROR,"Failed to verify auth data\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
//
// Get the PAC out of the authorization data
//
KerbErr = KerbGetPacFromAuthData(
Ticket->KERB_ENCRYPTED_TICKET_authorization_data,
&IfRelevantData,
&PacAuthData
);
if (!KERB_SUCCESS(KerbErr))
{
Status = KerbMapKerbError(KerbErr);
goto Cleanup;
}
if (PacAuthData != NULL)
{
//
// Unmarshall the PAC
//
Pac = (PPACTYPE) PacAuthData->value.auth_data.value;
if (PAC_UnMarshal(Pac, PacAuthData->value.auth_data.length) == 0)
{
D_DebugLog((DEB_ERROR,"Failed to unmarshal pac. %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Copy state from the system logon session so we don't
// leave it locked while verifying the PAC.
//
Status = KerbDuplicateString(
&WkstaDomain,
&SystemLogonSession->PrimaryCredentials.DomainName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (!KERB_SUCCESS(KerbDuplicateKey(
&LocalKey,
WkstaKey)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
if (TicketCacheLocked)
{
KerbUnlockTicketCache();
TicketCacheLocked = FALSE;
}
if (LogonSessionsLocked)
{
KerbUnlockLogonSessions(SystemLogonSession);
LogonSessionsLocked = FALSE;
}
Status = KerbVerifyPacSignature(
&WkstaDomain,
Pac,
PacAuthData->value.auth_data.length,
&LocalKey,
Ticket,
FALSE, // don't bother verifying at KDC, because we obtained the ticket
&ValidationInfo,
NULL // not interested in S4U delegation info
);
KerbFreeKey( &LocalKey );
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_WARN,"Pac signature did not verify %x.\n", Status));
if (ARGUMENT_PRESENT(LogonTicket))
{
if (!KERB_SUCCESS(KerbConvertPrincipalNameToString( &CName, &NameType, &Ticket->client_name )))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString( &CRealm, &Ticket->client_realm )))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
KerbReportPACError(
&CName,
&CRealm,
Status
);
}
//
// DOA Leave now...
//
goto Cleanup;
}
DsysAssert( !LogonSessionsLocked );
KerbReadLockLogonSessions(SystemLogonSession); // nothing can change
LogonSessionsLocked = TRUE;
}
}
}
//
// If we didn't find a PAC, try to build one locally
//
if (RealmlessWkstaLogon || Pac == NULL)
{
PKERB_INTERNAL_NAME ClientName = NULL;
NTSTATUS TempStatus;
DebugLog((DEB_WARN,"No authorization data in ticket - trying local\n"));
// if we don't have a name, make one, but only if we have a service ticket
if (ARGUMENT_PRESENT(LogonTicket))
{
if (!KERB_SUCCESS(KerbConvertRealmToUnicodeString(
&CRealm,
&Ticket->client_realm
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
//
// Convert the client's name into a usable format
//
if (!KERB_SUCCESS(KerbConvertPrincipalNameToKdcName(
&ClientName,
&Ticket->client_name
)))
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
}
//
// We don't have any information to do the mapping. Return
else if (MappedName->Buffer == NULL)
{
D_DebugLog((DEB_ERROR, "We don't have any information for creating a token!\n"));
Status = STATUS_LOGON_FAILURE;
goto Cleanup;
}
TempStatus = KerbCreatePacForKerbClient(
&Pac,
ClientName,
&CRealm,
MappedName
);
KerbFreeKdcName(&ClientName);
if (!NT_SUCCESS(TempStatus))
{
DebugLog((DEB_ERROR,"Failed to create local pac for client: 0x%x\n",TempStatus));
//
// Return the original error if we failed to build a pac
//
if (NT_SUCCESS(Status))
{
Status = TempStatus;
}
goto Cleanup;
}
FreePac = TRUE;
//
// Find the SAM validation info
//
LogonInfo = PAC_Find(
Pac,
PAC_LOGON_INFO,
NULL
);
if (LogonInfo == NULL)
{
D_DebugLog((DEB_ERROR,"Failed to find logon info! %ws, line %d\n", THIS_FILE, __LINE__));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// Now unmarshall the validation info
//
Status = PAC_UnmarshallValidationInfo(
&ValidationInfo,
LogonInfo->Data,
LogonInfo->cbBufferSize
);
if (!NT_SUCCESS(Status))
{
D_DebugLog((DEB_ERROR,"Failed to unmarshall validation info: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
}
//
// Check to see if this is a non-user account. If so, don't allow the logon
// unless its network, or network-cleartext.
//
if (((ValidationInfo->ExpansionRoom[SAMINFO_USER_ACCOUNT_CONTROL] & USER_MACHINE_ACCOUNT_MASK) != 0) &&
( !AllowWorkstationLogon ))
{
DebugLog((DEB_ERROR,"Logons to non-user accounts not allowed. UserAccountControl = 0x%x. %ws, line %d\n",
ValidationInfo->ExpansionRoom[SAMINFO_USER_ACCOUNT_CONTROL], THIS_FILE, __LINE__ ));
Status = STATUS_LOGON_TYPE_NOT_GRANTED;
goto Cleanup;
}
//
// Now we need to build a LSA_TOKEN_INFORMATION_V2 from the validation
// information
//
Status = KerbMakeTokenInformationV2(
ValidationInfo,
FALSE, // not local system
&TokenInformation
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR,"Failed to make token informatin v1: 0x%x. %ws, line %d\n",
Status, THIS_FILE, __LINE__));
goto Cleanup;
}
//
// unlock ticket cache
//
// KerbAllocateInteractiveProfile will lock logon session and ticket cache
//
if (TicketCacheLocked)
{
KerbUnlockTicketCache();
TicketCacheLocked = FALSE;
}
//
// Allocate the client profile
//
Status = KerbAllocateInteractiveProfile(
(PKERB_INTERACTIVE_PROFILE *) ProfileBuffer,
ProfileBufferLength,
ValidationInfo,
LogonSession,
Ticket,
KerbLogonInfo
);
if (!KERB_SUCCESS(Status))
{
goto Cleanup;
}
//
// Build the primary credential. We let someone else fill in the
// password.
//
PrimaryCredentials->LogonId = *LogonId;
Status = KerbDuplicateString(
&PrimaryCredentials->DownlevelName,
&ValidationInfo->EffectiveName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbDuplicateString(
&PrimaryCredentials->DomainName,
&ValidationInfo->LogonDomainName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbDuplicateString(
&PrimaryCredentials->LogonServer,
&ValidationInfo->LogonServer
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = KerbDuplicateSid(
&PrimaryCredentials->UserSid,
TokenInformation->User.User.Sid
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
PrimaryCredentials->Flags = 0;
if ( CheckIdentify )
{
//
// Do TCB check to see if we need an identify or impersonation
// level token.
//
SECPKG_CLIENT_INFO ClientInfo;
Status = LsaFunctions->GetClientInfo(&ClientInfo);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
if (!ClientInfo.HasTcbPrivilege)
{
D_DebugLog((DEB_TRACE, "Building identify token\n"));
PrimaryCredentials->Flags |= PRIMARY_CRED_LOGON_NO_TCB;
}
}
//
// Get supplemental credentials out of the pac
//
Status = KerbExtractCachedCreds(
Pac,
CredentialKey,
CachedCredentials
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
//
// We're using the validation_info4 struct to pass appropriate information
// back to caller for use in Lsa GetUserName(). We don't really use all
// of this information, however, so only copy over interesting fields
//
ValidationInfo4 = (PNETLOGON_VALIDATION_SAM_INFO4) KerbAllocate(sizeof(NETLOGON_VALIDATION_SAM_INFO4));
if (NULL == ValidationInfo4)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
if (ValidationInfo->FullName.Length)
{
Status = KerbDuplicateString(
&ValidationInfo4->FullName,
&ValidationInfo->FullName
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
KerbReadLockLogonSessions(LogonSession);
if (LogonSession->PrimaryCredentials.DomainName.Length)
{
D_DebugLog((DEB_TRACE_LOGON, "DomainName %wZ\n", &LogonSession->PrimaryCredentials.DomainName));
Status = KerbDuplicateString(
&ValidationInfo4->DnsLogonDomainName,
&LogonSession->PrimaryCredentials.DomainName
);
if (!NT_SUCCESS(Status))
{
KerbUnlockLogonSessions(LogonSession);
goto Cleanup;
}
ValidationInfo4->Upn.Length = LogonSession->PrimaryCredentials.UserName.Length
+ LogonSession->PrimaryCredentials.DomainName.Length
+sizeof(WCHAR);
ValidationInfo4->Upn.MaximumLength = ValidationInfo4->Upn.Length + sizeof(WCHAR);
ValidationInfo4->Upn.Buffer = (LPWSTR) KerbAllocate(ValidationInfo4->Upn.MaximumLength);
if ( ValidationInfo4->Upn.Buffer == NULL)
{
Status = STATUS_INSUFFICIENT_RESOURCES;
KerbUnlockLogonSessions(LogonSession);
goto Cleanup;
}
RtlCopyMemory(
ValidationInfo4->Upn.Buffer,
LogonSession->PrimaryCredentials.UserName.Buffer,
LogonSession->PrimaryCredentials.UserName.Length
);
ValidationInfo4->Upn.Buffer[LogonSession->PrimaryCredentials.UserName.Length/ sizeof(WCHAR)] = L'@';
lpDnsDomainName = ValidationInfo4->Upn.Buffer +
LogonSession->PrimaryCredentials.UserName.Length / sizeof(WCHAR) + 1;
RtlCopyMemory(
lpDnsDomainName,
LogonSession->PrimaryCredentials.DomainName.Buffer,
LogonSession->PrimaryCredentials.DomainName.Length
);
D_DebugLog((DEB_TRACE_LOGON, "KerbCreateTokenFromLogonTicket built UPN %wZ\n", &ValidationInfo4->Upn));
}
KerbUnlockLogonSessions(LogonSession);
//
// Cache the logon info in MSV
//
if (CacheLogon)
{
if ( (KerbLogonInfo->MessageType == KerbInteractiveLogon)
|| (KerbLogonInfo->MessageType == KerbWorkstationUnlockLogon) )
{
//
// Hold no locks when leaving this dll
//
if (TicketCacheLocked)
{
KerbUnlockTicketCache();
TicketCacheLocked = FALSE;
}
if (LogonSessionsLocked)
{
KerbUnlockLogonSessions(SystemLogonSession);
LogonSessionsLocked = FALSE;
}
KerbCacheLogonInformation(
&KerbLogonInfo->UserName,
&KerbLogonInfo->LogonDomainName,
&KerbLogonInfo->Password,
((ValidationInfo4->DnsLogonDomainName.Length) ? &ValidationInfo4->DnsLogonDomainName : NULL),
NULL, //((ValidationInfo4->Upn.Length) ? &ValidationInfo4->Upn : NULL),
LogonSession,
0, // no special flags
ValidationInfo,
NULL, // no supplemental creds
0
);
}
else if ((KerbLogonInfo->MessageType == KerbSmartCardLogon)
|| (KerbLogonInfo->MessageType == KerbSmartCardUnlockLogon))
{
KerbCacheSmartCardLogon(
ValidationInfo,
((ValidationInfo4->DnsLogonDomainName.Length) ? &ValidationInfo4->DnsLogonDomainName : NULL),
NULL, //((ValidationInfo4->Upn.Length) ? &ValidationInfo4->Upn : NULL),
LogonSession,
*CachedCredentials
);
}
else
{
D_DebugLog((DEB_WARN,"CacheLogon requested but logon type not cacheable\n"));
}
}
//
// If we were supplied a TGT for this logon, stick it in the logon session
//
if (ARGUMENT_PRESENT(ForwardedTgt) && (ForwardedTgt->BufferSize != 0))
{
Status = KerbExtractForwardedTgt(
LogonSession,
ForwardedTgt,
Ticket
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
*NewTokenInformation = TokenInformation;
*TokenInformationType = LsaTokenInformationV2;
*ppValidationInfo = ValidationInfo4;
ValidationInfo4 = NULL;
Cleanup:
KerbFreeString(&LocalDnsDomain);
KerbFreeString( &CName );
KerbFreeString( &CRealm );
KerbFreeString( &WkstaDomain );
if (TicketCacheLocked)
{
KerbUnlockTicketCache();
}
if (LogonSessionsLocked)
{
KerbUnlockLogonSessions(SystemLogonSession);
}
if (!NT_SUCCESS(Status))
{
if (TokenInformation != NULL)
{
KerbFree( TokenInformation );
}
if (*ProfileBuffer != NULL)
{
LsaFunctions->FreeClientBuffer(NULL, *ProfileBuffer);
*ProfileBuffer = NULL;
}
KerbFreeString(
&PrimaryCredentials->DownlevelName
);
KerbFreeString(
&PrimaryCredentials->DomainName
);
KerbFreeString(
&PrimaryCredentials->LogonServer
);
if (PrimaryCredentials->UserSid != NULL)
{
KerbFree(PrimaryCredentials->UserSid);
PrimaryCredentials->UserSid = NULL;
}
}
if (Ticket != NULL)
{
KerbFreeTicket(Ticket);
}
if (FreePac && (Pac != NULL))
{
MIDL_user_free(Pac);
}
if (IfRelevantData != NULL)
{
KerbFreeData(
PKERB_IF_RELEVANT_AUTH_DATA_PDU,
IfRelevantData
);
}
if (ValidationInfo != NULL)
{
MIDL_user_free(ValidationInfo);
}
if (ValidationInfo4)
{
KerbFreeString(&ValidationInfo4->DnsLogonDomainName);
KerbFreeString(&ValidationInfo4->Upn);
KerbFreeString(&ValidationInfo4->FullName);
KerbFree(ValidationInfo4);
}
return(Status);
}
//+-------------------------------------------------------------------------
//
// Function: KerbBuildLocalAccountToken
//
// Synopsis: Creates a token from a mapped kerberos principal
//
// Effects:
//
// Arguments:
//
// Requires:
//
// Returns:
//
// Notes:
//
//
//--------------------------------------------------------------------------
/*
NTSTATUS
KerbBuildLocalAccountToken(
IN PKERB_LOGON_SESSION LogonSession,
IN PLUID LogonId,
IN PUNICODE_STRING MappedClientName,
IN PKERB_INTERACTIVE_LOGON KerbLogonInfo,
OUT PLSA_TOKEN_INFORMATION_TYPE LogonSession,
OUT PVOID * NewTokenInformation,
OUT PULONG ProfileBufferLength,
OUT PVOID * ProfileBuffer,
OUT PSECPKG_PRIMARY_CRED PrimaryCredentials,
OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * CachedCreds
)
{
NTSTATUS Status = STATUS_SUCCESS;
SECPKG_CLIENT_INFO ClientInfo;
PLSAPR_POLICY_INFORMATION PolicyInfo = NULL;
SAMPR_HANDLE SamHandle = NULL;
SAMPR_HANDLE DomainHandle = NULL;
SAMPR_HANDLE UserHandle = NULL;
PSAMPR_GET_GROUPS_BUFFER Groups = NULL;
SID_AND_ATTRIBUTES_LIST TransitiveGroups = {0};
PSAMPR_USER_INFO_BUFFER UserInfo = NULL;
PPACTYPE LocalPac = NULL;
SAMPR_ULONG_ARRAY RidArray;
SAMPR_ULONG_ARRAY UseArray;
*ProfileBuffer = NULL;
*NewTokenInformation = NULL;
//
// Verify that the caller has TCB privilege. Otherwise anyone can forge
// a ticket to themselves to logon with any name in the list.
//
Status = LsaFunctions->GetClientInfo(&ClientInfo);
if (!NT_SUCCESS(Status))
{
return(Status);
}
if (!ClientInfo.HasTcbPrivilege)
{
return(STATUS_PRIVILEGE_NOT_HELD);
}
//
// Call the LSA to get our domain sid
//
Status = LsaIQueryInformationPolicyTrusted(
PolicyAccountDomainInformation,
&PolicyInfo
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "LsaIQueryInformationPolicyTrusted failed - %x\n", Status));
goto Cleanup;
}
//
// Open SAM to get the account information
//
Status = SamIConnect(
NULL, // no server name
&SamHandle,
0, // no desired access
TRUE // trusted caller
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "SamIConnectFailed - %x\n", Status));
goto Cleanup;
}
Status = SamrOpenDomain(
SamHandle,
0, // no desired access
(PRPC_SID) PolicyInfo->PolicyAccountDomainInfo.DomainSid,
&DomainHandle
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "SamrOpenDomain failed - %x\n", Status));
goto Cleanup;
}
Status = SamrLookupNamesInDomain(
DomainHandle,
1,
(PRPC_UNICODE_STRING) MappedClientName,
&RidArray,
&UseArray
);
if (!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "SamrOpenDomain failed - %x\n", Status));
goto Cleanup;
}
if ((UseArray.Element[0] != SidTypeUser) &&
(UseArray.Element[0] != SidTypeComputer))
{
Status = STATUS_NONE_MAPPED;
goto Cleanup;
}
Status = SamrOpenUser(
DomainHandle,
0, // no desired access,
RidArray.Element[0],
&UserHandle
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = SamrQueryInformationUser(
UserHandle,
UserAllInformation,
&UserInfo
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = SamrGetGroupsForUser(
UserHandle,
&Groups
);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
KerbGlobalReadLock();
Status = KerbDuplicateString(
&LocalMachineName,
&KerbGlobalMachineName
);
KerbGlobalReleaseLock();
if(!NT_SUCCESS(Status))
{
DebugLog((DEB_ERROR, "Failed to duplicate KerbGlobalMachineName\n"));
goto Cleanup;
}
//
// Set the password must changes time to inifinite because we don't
// want spurious password must change popups
//
UserInfo->All.PasswordMustChange = *(POLD_LARGE_INTEGER) &KerbGlobalWillNeverTime;
//
// *Don't build a PAC, that's extra effort in marshalling unmarshalling
// data we can just convert over from Samuserall to Netlogon_Validation_Info
//
} */