3833 lines
104 KiB
C++
3833 lines
104 KiB
C++
//+-----------------------------------------------------------------------
|
|
//
|
|
// 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>
|
|
|
|
#ifdef DEBUG_SUPPORT
|
|
static TCHAR THIS_FILE[]=TEXT(__FILE__);
|
|
#endif
|
|
|
|
DWORD
|
|
KerbCopyDomainRelativeSid(
|
|
OUT PSID TargetSid,
|
|
IN PSID DomainId,
|
|
IN ULONG RelativeId
|
|
);
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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
|
|
)
|
|
{
|
|
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 ClientBuffer = 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// 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 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;
|
|
}
|
|
|
|
//
|
|
// 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;
|
|
|
|
PassthroughRequest = (PMSV1_0_PASSTHROUGH_REQUEST) KerbAllocate(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);
|
|
}
|
|
if (PassthroughRequest != NULL)
|
|
{
|
|
KerbFree(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;
|
|
|
|
#if _WIN64
|
|
|
|
SECPKG_CALL_INFO CallInfo;
|
|
|
|
if(!LsaFunctions->GetCallInfo(&CallInfo))
|
|
{
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
#endif // _WIN64
|
|
|
|
//
|
|
// Alocate the profile buffer to return to the client
|
|
//
|
|
|
|
KerbReadLockLogonSessions( LogonSession );
|
|
|
|
|
|
*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);
|
|
|
|
LocalProfileBuffer = (PKERB_INTERACTIVE_PROFILE) KerbAllocate(*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
|
|
//
|
|
|
|
if (KerbGlobalStrongEncryptionPermitted ||
|
|
KerbIsKeyExportable(
|
|
&LogonTicket->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 (LocalProfileBuffer != NULL)
|
|
{
|
|
KerbFree(LocalProfileBuffer);
|
|
}
|
|
|
|
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;
|
|
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|
|
KerbReadLockLogonSessions(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 PKERB_ENCRYPTED_TICKET InternalTicket,
|
|
IN 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
|
|
)
|
|
{
|
|
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 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;
|
|
|
|
SECPKG_PRIMARY_CRED PrimaryCredentials;
|
|
|
|
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))
|
|
{
|
|
|
|
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
|
|
);
|
|
if (!NT_SUCCESS(Status)) {
|
|
KerbReportPACError(
|
|
ClientName,
|
|
ClientDomain,
|
|
Status
|
|
);
|
|
|
|
DebugLog((DEB_WARN,"Pac signature did not verify. Trying to build local pac now. (KerbCreateTokenFromTicket)\n"));
|
|
Pac = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// 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
|
|
))
|
|
{
|
|
BOOLEAN bExist = FALSE;
|
|
|
|
//
|
|
// check for special case where client is network serice of
|
|
// local computer
|
|
//
|
|
|
|
Status = KerbDoesSKeyExist(pSessionKey, &bExist);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// bExist is false indicates that it is not network
|
|
// service of local computer, hence IsLocalSystem is true
|
|
//
|
|
|
|
IsLocalSystem = !bExist;
|
|
|
|
DebugLog((DEB_TRACE_LOOPBACK, "KerbCreateTokenFromTicket, IsLocalSystem? %s\n", (IsLocalSystem ? "true" : "false")));
|
|
}
|
|
else // !NT_SUCCESS(Status)
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to detect local network service: 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 informatin 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.
|
|
//
|
|
|
|
KerbFreeString( ClientName);
|
|
Status = KerbDuplicateString(
|
|
ClientName,
|
|
&ValidationInfo->EffectiveName
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
KerbFreeString( ClientDomain );
|
|
|
|
Status = KerbDuplicateString(
|
|
ClientDomain,
|
|
&ValidationInfo->LogonDomainName
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// 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 (ClientName->Length && ClientName->Buffer)
|
|
{
|
|
I_LsaIAddNameToLogonSession(&LogonId, NameUserPrincipal, ClientName);
|
|
} */
|
|
|
|
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(ClientDomain->Length && ClientDomain->Buffer)
|
|
{
|
|
PrimaryCredentials.DomainName.Buffer = (PWSTR)LsaFunctions->AllocateLsaHeap(ClientDomain->Length);
|
|
if (PrimaryCredentials.DomainName.Buffer == NULL) {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
PrimaryCredentials.DomainName.Length =
|
|
PrimaryCredentials.DomainName.MaximumLength = ClientDomain->Length;
|
|
|
|
RtlCopyMemory(
|
|
PrimaryCredentials.DomainName.Buffer,
|
|
ClientDomain->Buffer,
|
|
ClientDomain->Length
|
|
);
|
|
}
|
|
|
|
Status = LsaFunctions->CreateTokenEx(
|
|
&LogonId,
|
|
&KerberosSource,
|
|
Network,
|
|
ImpersonationLevel,
|
|
TokenType,
|
|
LsaTokenInformation,
|
|
NULL, // no token groups
|
|
&Workstation,
|
|
(ValidationInfo == NULL ? NULL : &ValidationInfo->ProfilePath),
|
|
&PrimaryCredentials,
|
|
SecSessionPrimaryCred,
|
|
&TokenHandle,
|
|
&SubStatus
|
|
);
|
|
|
|
|
|
// LsapCreateToken free's 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.
|
|
//
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
//
|
|
// 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
|
|
);
|
|
}
|
|
|
|
|
|
//
|
|
// If the caller didn't have the requisite privilege, just continue
|
|
// without a token.
|
|
//
|
|
|
|
if ((Status == STATUS_PRIVILEGE_NOT_HELD) ||
|
|
(Status == STATUS_NO_SUCH_USER))
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
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 UserName,
|
|
IN PUNICODE_STRING DomainName,
|
|
IN OPTIONAL PUNICODE_STRING Password,
|
|
IN OPTIONAL PUNICODE_STRING DnsDomainName,
|
|
IN OPTIONAL PUNICODE_STRING Upn,
|
|
IN BOOLEAN MitLogon,
|
|
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;
|
|
ULONG NewGroupCount = 0;
|
|
ULONG Index, Index2;
|
|
UNICODE_STRING LocalMachineName;
|
|
UNICODE_STRING DummyString;
|
|
PBYTE Tmp, Tmp2;
|
|
PVOID SupplementalMitCreds = NULL;
|
|
ULONG SupplementalMitCredSize = 0;
|
|
|
|
LocalMachineName.Buffer = NULL;
|
|
|
|
//
|
|
// We *used* to ADD a bunch of resource group code here....
|
|
// Removed 5/1/01, as it appears we never add these to the
|
|
// VALIDATION_SAM_INFO3 structure, and the KDC adds in
|
|
// resource groups into ExtraSids.... see rtl/pac.cxx
|
|
//
|
|
|
|
#if DBG
|
|
if (ARGUMENT_PRESENT(ValidationInfo))
|
|
{
|
|
DsysAssert(ValidationInfo->ResourceGroupCount == 0);
|
|
}
|
|
#endif
|
|
|
|
MsvLogonInfo.Identity.LogonDomainName = *DomainName;
|
|
MsvLogonInfo.Identity.UserName = *UserName;
|
|
|
|
//
|
|
// 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, "Using MIT caching\n"));
|
|
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(UserName->Length, ALIGN_LONG) +
|
|
ROUND_UP_COUNT(DomainName->Length, ALIGN_LONG);
|
|
|
|
|
|
SupplementalMitCreds = (PVOID) KerbAllocate(SupplementalMitCredSize);
|
|
if (NULL == SupplementalMitCreds)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto Cleanup;
|
|
}
|
|
|
|
DummyString.Length = DummyString.MaximumLength = UserName->Length;
|
|
Tmp = (PBYTE) (SupplementalMitCreds) + sizeof(UNICODE_STRING);
|
|
|
|
if (DummyString.Length > 0)
|
|
{
|
|
|
|
RtlCopyMemory(
|
|
Tmp,
|
|
UserName->Buffer,
|
|
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 = DomainName->Length;
|
|
|
|
if (DummyString.Length > 0)
|
|
{
|
|
RtlCopyMemory(
|
|
Tmp,
|
|
DomainName->Buffer,
|
|
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;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Build up the NETLOGON_VALIDATION_SAM_INFO4 structure to pass to NTLM
|
|
//
|
|
if (ARGUMENT_PRESENT(ValidationInfo))
|
|
{
|
|
|
|
RtlCopyMemory(&ValidationInfoToUse,
|
|
ValidationInfo,
|
|
sizeof(NETLOGON_VALIDATION_SAM_INFO2));
|
|
|
|
if (ARGUMENT_PRESENT(DnsDomainName))
|
|
{
|
|
ValidationInfoToUse.DnsLogonDomainName = *DnsDomainName;
|
|
}
|
|
|
|
/*if (ARGUMENT_PRESENT(Upn))
|
|
{
|
|
ValidationInfoToUse.Upn = *Upn;
|
|
}*/
|
|
}
|
|
|
|
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))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to cache credentials: 0x%x, 0x%x. %ws, line %d\n",TempStatus, SubStatus, THIS_FILE, __LINE__));
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
KerbFreeString( &LocalMachineName );
|
|
|
|
if (SupplementalMitCreds != NULL)
|
|
{
|
|
KerbFree(SupplementalMitCreds);
|
|
}
|
|
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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 CacheLogon,
|
|
IN BOOLEAN RealmlessWkstaLogon,
|
|
IN OPTIONAL PKERB_ENCRYPTION_KEY CredentialKey,
|
|
IN OPTIONAL PKERB_MESSAGE_BUFFER ForwardedTgt,
|
|
IN OPTIONAL PUNICODE_STRING MappedName,
|
|
IN OPTIONAL PKERB_INTERNAL_NAME S4UClientName,
|
|
IN OPTIONAL PUNICODE_STRING S4UClientRealm,
|
|
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;
|
|
BOOLEAN FreePac = FALSE;
|
|
KERBERR KerbErr;
|
|
UNICODE_STRING LocalDnsDomain = {0};
|
|
LPWSTR lpDnsDomainName;
|
|
|
|
*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)
|
|
{
|
|
|
|
SystemLogonSession = KerbReferenceLogonSession(
|
|
&SystemLogonId,
|
|
FALSE // don't unlink
|
|
);
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
//
|
|
// Decrypt the ticket
|
|
//
|
|
|
|
KerbReadLockLogonSessions(SystemLogonSession);
|
|
LogonSessionsLocked = TRUE;
|
|
KerbReadLockTicketCache();
|
|
TicketCacheLocked = TRUE;
|
|
|
|
|
|
//
|
|
// Get the appropriate key
|
|
//
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
//
|
|
// 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};
|
|
UNICODE_STRING DomainName = {0};
|
|
|
|
if (!KerbVerifyAuthData(
|
|
Ticket->KERB_ENCRYPTED_TICKET_authorization_data
|
|
))
|
|
{
|
|
D_DebugLog((DEB_ERROR,"Failed to verify auth data\n"));
|
|
Status = STATUS_LOGON_FAILURE;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify the auth data is valid
|
|
//
|
|
|
|
if (!KerbVerifyAuthData(
|
|
Ticket->KERB_ENCRYPTED_TICKET_authorization_data
|
|
))
|
|
{
|
|
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(
|
|
&DomainName,
|
|
&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(
|
|
&DomainName,
|
|
Pac,
|
|
PacAuthData->value.auth_data.length,
|
|
&LocalKey,
|
|
Ticket,
|
|
FALSE, // don't bother verifying at KDC, because we obtained the ticket
|
|
&ValidationInfo
|
|
);
|
|
|
|
KerbFreeString( &DomainName );
|
|
KerbFreeKey( &LocalKey );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
NTSTATUS Tmp;
|
|
UNICODE_STRING UserName = {0};
|
|
|
|
DebugLog((DEB_WARN,"Pac signature did not verify. Trying to build local pac now. (KerbCreateTokenFromLogonTicket)\n"));
|
|
|
|
Tmp = KerbDuplicateString(
|
|
&UserName,
|
|
&SystemLogonSession->PrimaryCredentials.UserName
|
|
);
|
|
|
|
if (NT_SUCCESS(Tmp))
|
|
{
|
|
KerbReportPACError(
|
|
&UserName,
|
|
&DomainName,
|
|
Status
|
|
);
|
|
|
|
KerbFreeString( &UserName );
|
|
}
|
|
|
|
|
|
//
|
|
// Null the pac so we can try to build one locally.
|
|
//
|
|
|
|
Pac = NULL;
|
|
}
|
|
|
|
KerbReadLockLogonSessions(SystemLogonSession); // nothing can change
|
|
LogonSessionsLocked = TRUE;
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// If we didn't find a PAC, try to build one locally
|
|
//
|
|
|
|
if (RealmlessWkstaLogon || Pac == NULL)
|
|
{
|
|
UNICODE_STRING DomainName = NULL_UNICODE_STRING;
|
|
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(
|
|
&DomainName,
|
|
&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
|
|
)))
|
|
{
|
|
KerbFreeString(&DomainName);
|
|
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,
|
|
&DomainName,
|
|
MappedName
|
|
);
|
|
|
|
KerbFreeKdcName(&ClientName);
|
|
KerbFreeString(&DomainName);
|
|
|
|
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
|
|
//
|
|
|
|
if ((ValidationInfo->ExpansionRoom[SAMINFO_USER_ACCOUNT_CONTROL] & USER_MACHINE_ACCOUNT_MASK) != 0)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
//
|
|
// 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;
|
|
|
|
//
|
|
// 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)
|
|
{
|
|
|
|
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_ERROR, "Built UPN %wZ\n", &ValidationInfo4->Upn)); // fester: dumb down
|
|
|
|
}
|
|
|
|
KerbUnlockLogonSessions(LogonSession);
|
|
|
|
//
|
|
// Cache the logon info in MSV
|
|
//
|
|
if (CacheLogon)
|
|
{
|
|
if (KerbLogonInfo->MessageType == KerbInteractiveLogon)
|
|
{
|
|
|
|
BOOLEAN MitLogon;
|
|
|
|
|
|
//
|
|
// Hold no locks when leaving this dll
|
|
//
|
|
|
|
if (TicketCacheLocked)
|
|
{
|
|
KerbUnlockTicketCache();
|
|
TicketCacheLocked = FALSE;
|
|
}
|
|
|
|
if (LogonSessionsLocked)
|
|
{
|
|
KerbUnlockLogonSessions(SystemLogonSession);
|
|
LogonSessionsLocked = FALSE;
|
|
}
|
|
|
|
|
|
MitLogon = ((LogonSession->LogonSessionFlags & KERB_LOGON_MIT_REALM) != 0);
|
|
|
|
|
|
KerbCacheLogonInformation(
|
|
&KerbLogonInfo->UserName,
|
|
&KerbLogonInfo->LogonDomainName,
|
|
&KerbLogonInfo->Password,
|
|
((ValidationInfo4->DnsLogonDomainName.Length) ? &ValidationInfo4->DnsLogonDomainName : NULL),
|
|
NULL, //((ValidationInfo4->Upn.Length) ? &ValidationInfo4->Upn : NULL),
|
|
MitLogon,
|
|
0, // no special flags
|
|
ValidationInfo,
|
|
NULL, // no supplemental creds
|
|
0
|
|
);
|
|
|
|
}
|
|
else if (KerbLogonInfo->MessageType == KerbSmartCardLogon)
|
|
{
|
|
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);
|
|
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: KerbCopyDomainRelativeSid
|
|
//
|
|
// Synopsis: Given a domain Id and a relative ID create the corresponding
|
|
// SID at the location indicated by TargetSid
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: TargetSid - target memory location
|
|
// DomainId - The template SID to use.
|
|
//
|
|
// RelativeId - The relative Id to append to the DomainId.
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: Size - Size of the sid copied
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
DWORD
|
|
KerbCopyDomainRelativeSid(
|
|
OUT PSID TargetSid,
|
|
IN PSID DomainId,
|
|
IN ULONG RelativeId
|
|
)
|
|
{
|
|
UCHAR DomainIdSubAuthorityCount;
|
|
ULONG Size;
|
|
|
|
//
|
|
// Allocate a Sid which has one more sub-authority than the domain ID.
|
|
//
|
|
|
|
DomainIdSubAuthorityCount = *(RtlSubAuthorityCountSid( DomainId ));
|
|
Size = RtlLengthRequiredSid(DomainIdSubAuthorityCount+1);
|
|
|
|
//
|
|
// Initialize the new SID to have the same inital value as the
|
|
// domain ID.
|
|
//
|
|
|
|
if ( !NT_SUCCESS( RtlCopySid( Size, TargetSid, DomainId ) ) ) {
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Adjust the sub-authority count and
|
|
// add the relative Id unique to the newly allocated SID
|
|
//
|
|
|
|
(*(RtlSubAuthorityCountSid( TargetSid ))) ++;
|
|
*RtlSubAuthoritySid( TargetSid, DomainIdSubAuthorityCount ) = RelativeId;
|
|
|
|
|
|
return Size;
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// 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
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} */
|
|
|
|
|