You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2465 lines
75 KiB
2465 lines
75 KiB
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
//
|
|
// Copyright (c) Microsoft Corporation 2000
|
|
//
|
|
// File: lsaap.cxx
|
|
//
|
|
// Contents: Authentication package dispatch routines
|
|
// LsaApInitializePackage (Not needed done in SpInitialize)
|
|
// LsaApLogonUser2
|
|
// LsaApCallPackage
|
|
// LsaApCallPackagePassthrough
|
|
// LsaApLogonTerminated
|
|
//
|
|
// Helper functions:
|
|
// This file also has all routines that access the SAM through SamR* and SamI* routines
|
|
//
|
|
// History: KDamour 10Mar00 Based on msv_sspi\msv1_0.c
|
|
//
|
|
//---------------------------------------------------------------------
|
|
|
|
|
|
|
|
#include "global.h"
|
|
|
|
extern "C"
|
|
{
|
|
#include <align.h> // ROUND_UP_COUNT
|
|
#include <lsarpc.h>
|
|
#include <samrpc.h>
|
|
#include <logonmsv.h>
|
|
#include <lsaisrv.h>
|
|
}
|
|
|
|
#include <pac.hxx> // MUST be outside of the Extern C since libs are exported as C++
|
|
|
|
|
|
// Local prototypes
|
|
NTSTATUS
|
|
DigestFilterSids(
|
|
IN PDIGEST_PARAMETER pDigest,
|
|
IN PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo
|
|
);
|
|
|
|
#define SAM_CLEARTEXT_CREDENTIAL_NAME L"CLEARTEXT"
|
|
#define SAM_WDIGEST_CREDENTIAL_NAME WDIGEST_SP_NAME // Name of the Supplemental (primary) cred blob for MD5 hashes
|
|
|
|
const ULONG USER_ALL_DIGEST_INFO =
|
|
USER_ALL_USERNAME |
|
|
USER_ALL_BADPASSWORDCOUNT |
|
|
USER_ALL_LOGONCOUNT |
|
|
USER_ALL_PASSWORDMUSTCHANGE |
|
|
USER_ALL_PASSWORDCANCHANGE |
|
|
USER_ALL_WORKSTATIONS |
|
|
USER_ALL_LOGONHOURS |
|
|
USER_ALL_ACCOUNTEXPIRES |
|
|
USER_ALL_PRIMARYGROUPID |
|
|
USER_ALL_USERID |
|
|
USER_ALL_USERACCOUNTCONTROL;
|
|
|
|
// Local variables for access to SAM - used only on domain controller
|
|
SAMPR_HANDLE l_AccountSamHandle = NULL;
|
|
SAMPR_HANDLE l_AccountDomainHandle = NULL;
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the dispatch routine for
|
|
LsaCallAuthenticationPackage().
|
|
|
|
--*/
|
|
NTSTATUS
|
|
LsaApCallPackage (
|
|
IN PLSA_CLIENT_REQUEST ClientRequest,
|
|
IN PVOID ProtocolSubmitBuffer,
|
|
IN PVOID ClientBufferBase,
|
|
IN ULONG SubmitBufferLength,
|
|
OUT PVOID *ProtocolReturnBuffer,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PNTSTATUS ProtocolStatus
|
|
)
|
|
|
|
{
|
|
UNREFERENCED_PARAMETER(ClientRequest);
|
|
UNREFERENCED_PARAMETER(ProtocolSubmitBuffer);
|
|
UNREFERENCED_PARAMETER(ClientBufferBase);
|
|
UNREFERENCED_PARAMETER(SubmitBufferLength);
|
|
UNREFERENCED_PARAMETER(ProtocolReturnBuffer);
|
|
UNREFERENCED_PARAMETER(ReturnBufferLength);
|
|
UNREFERENCED_PARAMETER(ProtocolStatus);
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApCallPackage: Entering/Leaving \n"));
|
|
return(SEC_E_UNSUPPORTED_FUNCTION);
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the dispatch routine for
|
|
LsaCallAuthenticationPackage() for untrusted clients.
|
|
|
|
|
|
--*/
|
|
NTSTATUS
|
|
LsaApCallPackageUntrusted (
|
|
IN PLSA_CLIENT_REQUEST ClientRequest,
|
|
IN PVOID ProtocolSubmitBuffer,
|
|
IN PVOID ClientBufferBase,
|
|
IN ULONG SubmitBufferLength,
|
|
OUT PVOID *ProtocolReturnBuffer,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PNTSTATUS ProtocolStatus
|
|
)
|
|
|
|
{
|
|
UNREFERENCED_PARAMETER(ClientRequest);
|
|
UNREFERENCED_PARAMETER(ProtocolSubmitBuffer);
|
|
UNREFERENCED_PARAMETER(ClientBufferBase);
|
|
UNREFERENCED_PARAMETER(SubmitBufferLength);
|
|
UNREFERENCED_PARAMETER(ProtocolReturnBuffer);
|
|
UNREFERENCED_PARAMETER(ReturnBufferLength);
|
|
UNREFERENCED_PARAMETER(ProtocolStatus);
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApCallPackageUntrusted: Entering/Leaving \n"));
|
|
return(SEC_E_UNSUPPORTED_FUNCTION);
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is the dispatch routine for
|
|
LsaCallAuthenticationPackage() for passthrough logon requests.
|
|
When the passthrough is called (from AcceptSecurityCOntext)
|
|
a databuffer is sent to the DC and this function is called.
|
|
|
|
Arguments:
|
|
|
|
ClientRequest - Is a pointer to an opaque data structure
|
|
representing the client's request.
|
|
|
|
ProtocolSubmitBuffer - Supplies a protocol message specific to
|
|
the authentication package.
|
|
|
|
ClientBufferBase - Provides the address within the client
|
|
process at which the protocol message was resident.
|
|
This may be necessary to fix-up any pointers within the
|
|
protocol message buffer.
|
|
|
|
SubmitBufferLength - Indicates the length of the submitted
|
|
protocol message buffer.
|
|
|
|
ProtocolReturnBuffer - Is used to return the address of the
|
|
protocol buffer in the client process. The authentication
|
|
package is responsible for allocating and returning the
|
|
protocol buffer within the client process. This buffer is
|
|
expected to have been allocated with the
|
|
AllocateClientBuffer() service.
|
|
|
|
The format and semantics of this buffer are specific to the
|
|
authentication package.
|
|
|
|
ReturnBufferLength - Receives the length (in bytes) of the
|
|
returned protocol buffer.
|
|
|
|
ProtocolStatus - Assuming the services completion is
|
|
STATUS_SUCCESS, this parameter will receive completion status
|
|
returned by the specified authentication package. The list
|
|
of status values that may be returned are authentication
|
|
package specific.
|
|
|
|
Return Status:
|
|
|
|
STATUS_SUCCESS - The call was made to the authentication package.
|
|
The ProtocolStatus parameter must be checked to see what the
|
|
completion status from the authentication package is.
|
|
|
|
STATUS_QUOTA_EXCEEDED - This error indicates that the return
|
|
buffer could not could not be allocated because the client
|
|
does not have sufficient quota.
|
|
|
|
|
|
|
|
|
|
--*/
|
|
NTSTATUS
|
|
LsaApCallPackagePassthrough (
|
|
IN PLSA_CLIENT_REQUEST ClientRequest,
|
|
IN PVOID ProtocolSubmitBuffer,
|
|
IN PVOID ClientBufferBase,
|
|
IN ULONG SubmitBufferLength,
|
|
OUT PVOID *ProtocolReturnBuffer,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PNTSTATUS ProtocolStatus
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NTSTATUS StatusProtocol = STATUS_SUCCESS;
|
|
ULONG MessageType = 0;
|
|
ULONG ulReturnBuffer = 0;
|
|
BYTE *pReturnBuffer = NULL;
|
|
|
|
UNREFERENCED_PARAMETER(ClientRequest);
|
|
UNREFERENCED_PARAMETER(ClientBufferBase);
|
|
|
|
ASSERT(ProtocolSubmitBuffer);
|
|
ASSERT(ProtocolReturnBuffer);
|
|
ASSERT(ReturnBufferLength);
|
|
ASSERT(ProtocolStatus);
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApCallPackagePassthrough: Entering \n"));
|
|
|
|
|
|
//
|
|
// Get the messsage type from the protocol submit buffer.
|
|
//
|
|
|
|
if ( SubmitBufferLength < sizeof(DIGEST_BLOB_REQUEST) ) {
|
|
DebugLog((DEB_ERROR, "LsaApCallPackagePassthrough: FAILED message size to contain Digest Request\n"));
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
memcpy((char *)&MessageType, (char *)ProtocolSubmitBuffer, sizeof(MessageType));
|
|
|
|
if ( MessageType != VERIFY_DIGEST_MESSAGE)
|
|
{
|
|
DebugLog((DEB_ERROR, "FAILED to have correct message type\n"));
|
|
return STATUS_ACCESS_DENIED;
|
|
}
|
|
|
|
//
|
|
// Allow the DigestCalc routine to only set the return buffer information
|
|
// on success conditions.
|
|
//
|
|
|
|
DebugLog((DEB_TRACE, "LsaApCallPackagePassthrough: setting return buffers to NULL\n"));
|
|
*ProtocolReturnBuffer = NULL;
|
|
*ReturnBufferLength = 0;
|
|
|
|
// We will need to free any memory allocated in the Returnbuffer
|
|
StatusProtocol = DigestPackagePassthrough((USHORT)SubmitBufferLength, (BYTE *)ProtocolSubmitBuffer,
|
|
&ulReturnBuffer, &pReturnBuffer);
|
|
if (!NT_SUCCESS(StatusProtocol))
|
|
{
|
|
DebugLog((DEB_ERROR,"LsaApCallPackagePassthrough: DigestPackagePassthrough failed 0x%x\n", StatusProtocol));
|
|
ulReturnBuffer = 0;
|
|
goto CleanUp;
|
|
}
|
|
|
|
// DebugLog((DEB_TRACE, "LsaApCallPackagePassthrough: setting return auth status to STATUS_SUCCEED\n"));
|
|
// DebugLog((DEB_TRACE, "LsaApCallPackagePassthrough: Total Return Buffer size %ld bytes\n", ulReturnBuffer));
|
|
|
|
// Now place the data back to the client (the server calling this)
|
|
ASSERT(ulReturnBuffer); // we should always have data on successful logon to send back
|
|
if (ulReturnBuffer)
|
|
{
|
|
Status = g_LsaFunctions->AllocateClientBuffer(
|
|
NULL,
|
|
ulReturnBuffer,
|
|
ProtocolReturnBuffer
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto CleanUp;
|
|
}
|
|
Status = g_LsaFunctions->CopyToClientBuffer(
|
|
NULL,
|
|
ulReturnBuffer,
|
|
*ProtocolReturnBuffer,
|
|
pReturnBuffer
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{ // Failed to copy over the data to the client
|
|
g_LsaFunctions->FreeClientBuffer(
|
|
NULL,
|
|
*ProtocolReturnBuffer
|
|
);
|
|
*ProtocolReturnBuffer = NULL;
|
|
}
|
|
else
|
|
{
|
|
*ReturnBufferLength = ulReturnBuffer;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR, "LsaApCallPackagePassthrough: Zero length return buffer\n"));
|
|
Status = STATUS_INTERNAL_ERROR;
|
|
}
|
|
|
|
CleanUp:
|
|
|
|
*ProtocolStatus = StatusProtocol;
|
|
|
|
if (pReturnBuffer)
|
|
{
|
|
DigestFreeMemory(pReturnBuffer);
|
|
pReturnBuffer = NULL;
|
|
ulReturnBuffer = 0;
|
|
}
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApCallPackagePassthrough: Leaving Status 0x%x\n", Status));
|
|
return(Status);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to authenticate a user logon attempt. This is
|
|
the user's initial logon. A new LSA logon session will be established
|
|
for the user and validation information for the user will be returned.
|
|
|
|
Unused function in Digest
|
|
|
|
--*/
|
|
NTSTATUS
|
|
LsaApLogonUserEx2 (
|
|
IN PLSA_CLIENT_REQUEST ClientRequest,
|
|
IN SECURITY_LOGON_TYPE LogonType,
|
|
IN PVOID ProtocolSubmitBuffer,
|
|
IN PVOID ClientBufferBase,
|
|
IN ULONG SubmitBufferSize,
|
|
OUT PVOID *ProfileBuffer,
|
|
OUT PULONG ProfileBufferSize,
|
|
OUT PLUID LogonId,
|
|
OUT PNTSTATUS SubStatus,
|
|
OUT PLSA_TOKEN_INFORMATION_TYPE TokenInformationType,
|
|
OUT PVOID *TokenInformation,
|
|
OUT PUNICODE_STRING *AccountName,
|
|
OUT PUNICODE_STRING *AuthenticatingAuthority,
|
|
OUT PUNICODE_STRING *MachineName,
|
|
OUT PSECPKG_PRIMARY_CRED PrimaryCredentials,
|
|
OUT PSECPKG_SUPPLEMENTAL_CRED_ARRAY * SupplementalCredentials
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(ClientRequest);
|
|
UNREFERENCED_PARAMETER(LogonType);
|
|
UNREFERENCED_PARAMETER(ProtocolSubmitBuffer);
|
|
UNREFERENCED_PARAMETER(ClientBufferBase);
|
|
UNREFERENCED_PARAMETER(SubmitBufferSize);
|
|
UNREFERENCED_PARAMETER(ProfileBuffer);
|
|
UNREFERENCED_PARAMETER(ProfileBufferSize);
|
|
UNREFERENCED_PARAMETER(LogonId);
|
|
UNREFERENCED_PARAMETER(SubStatus);
|
|
UNREFERENCED_PARAMETER(TokenInformationType);
|
|
UNREFERENCED_PARAMETER(TokenInformation);
|
|
UNREFERENCED_PARAMETER(AccountName);
|
|
UNREFERENCED_PARAMETER(AuthenticatingAuthority);
|
|
UNREFERENCED_PARAMETER(MachineName);
|
|
UNREFERENCED_PARAMETER(PrimaryCredentials);
|
|
UNREFERENCED_PARAMETER(SupplementalCredentials);
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApLogonUserEx2: Entering/Leaving \n"));
|
|
return (SEC_E_UNSUPPORTED_FUNCTION);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is used to notify each authentication package when a logon
|
|
session terminates. A logon session terminates when the last token
|
|
referencing the logon session is deleted.
|
|
|
|
Arguments:
|
|
|
|
LogonId - Is the logon ID that just logged off.
|
|
|
|
Return Status:
|
|
|
|
None.
|
|
--*/
|
|
VOID
|
|
LsaApLogonTerminated (
|
|
IN PLUID pLogonId
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PDIGEST_LOGONSESSION pLogonSession = NULL;
|
|
LONG lReferences = 0;
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApLogonTerminated: Entering LogonID (%x:%lx) \n",
|
|
pLogonId->HighPart, pLogonId->LowPart));
|
|
|
|
//
|
|
// Find the entry, dereference, and de-link it from the active logon table.
|
|
//
|
|
|
|
Status = LogSessHandlerLogonIdToPtr(pLogonId, TRUE, &pLogonSession);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto CleanUp; // No LongonID found in Active list - simply exit quietly
|
|
}
|
|
|
|
DebugLog((DEB_TRACE, "LsaApLogonTerminated: Found LogonID (%x:%lx) \n",
|
|
pLogonId->HighPart, pLogonId->LowPart));
|
|
|
|
|
|
// This relies on the LSA terminating all of the credentials before killing off
|
|
// the LogonSession.
|
|
|
|
lReferences = InterlockedDecrement(&pLogonSession->lReferences);
|
|
|
|
DebugLog((DEB_TRACE, "LsaApLogonTerminated: Refcount %ld \n", lReferences));
|
|
|
|
ASSERT( lReferences >= 0 );
|
|
|
|
if (lReferences)
|
|
{
|
|
DebugLog((DEB_WARN, "LsaApLogonTerminated: WARNING Terminate LogonID (%x:%lx) non-zero RefCount!\n",
|
|
pLogonId->HighPart, pLogonId->LowPart));
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_TRACE, "LsaApLogonTerminated: Removed LogonID (%x:%lx) from Active List! \n",
|
|
pLogonId->HighPart, pLogonId->LowPart));
|
|
|
|
LogonSessionFree(pLogonSession);
|
|
}
|
|
|
|
CleanUp:
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "LsaApLogonTerminated: Exiting LogonID (%x:%lx) \n",
|
|
pLogonId->HighPart, pLogonId->LowPart));
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
// Routine to acquire the plaintext password for a given user
|
|
// If supplemental credentials exist that contain the Digest Hash values
|
|
// then return them also.
|
|
// This routine runs on the domain controller
|
|
// Must provide STRING strPasswd
|
|
|
|
NTSTATUS
|
|
DigestGetPasswd(
|
|
SAMPR_HANDLE UserHandle,
|
|
IN PDIGEST_PARAMETER pDigest,
|
|
IN OUT PUSER_CREDENTIALS pUserCreds
|
|
)
|
|
{
|
|
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
UNICODE_STRING ustrcPackageName = {0};
|
|
|
|
UNICODE_STRING ustrcTemp = {0};
|
|
STRING strcTemp = {0};
|
|
PVOID pvPlainPwd = NULL;
|
|
PVOID pvHashCred = NULL;
|
|
ULONG ulLenPassword = 0;
|
|
ULONG ulLenHash = 0;
|
|
|
|
DebugLog((DEB_TRACE_FUNC,"DigestGetPasswd: Entering\n"));
|
|
|
|
pUserCreds->fIsValidDigestHash = FALSE;
|
|
pUserCreds->fIsValidPasswd = FALSE;
|
|
|
|
ASSERT(pDigest);
|
|
ASSERT(pUserCreds);
|
|
|
|
if (!g_fDomainController)
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestGetPasswd: Not on a domaincontroller - can not get credentials\n"));
|
|
Status = STATUS_INVALID_SERVER_STATE;
|
|
goto CleanUp;
|
|
}
|
|
|
|
//
|
|
// Retrieve the MD5 hashed pre-calculated values if they exist for this user
|
|
//
|
|
// NOTE : On NT 5, this API only works on Domain Controllers !!
|
|
//
|
|
RtlInitUnicodeString(&ustrcPackageName, SAM_WDIGEST_CREDENTIAL_NAME);
|
|
|
|
Status = SamIRetrievePrimaryCredentials( UserHandle,
|
|
&ustrcPackageName,
|
|
&pvHashCred,
|
|
&ulLenHash);
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
pvHashCred = NULL;
|
|
DebugLog((DEB_TRACE,"DigestGetPasswd: NO Pre-calc Hashes were found for user\n"));
|
|
}
|
|
else
|
|
{
|
|
if ((ulLenHash >= MD5_HASH_BYTESIZE) && (ulLenHash < MAXUSHORT)) // must have a valid header size header
|
|
{
|
|
strcTemp.Buffer = (PCHAR) pvHashCred;
|
|
strcTemp.Length = strcTemp.MaximumLength = (USHORT) ulLenHash;
|
|
|
|
Status = StringDuplicate(&(pUserCreds->strDigestHash), &strcTemp);
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestGetPasswd: Failed to copy precalc hashes, error 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
|
|
/* RC1 supp creds does not have SUPPCREDS_CNTLOC so use the supp cred length for now
|
|
// Extract the number of pre-calculated digest hashes - just an extra check on supp cred size
|
|
pUserCreds->usDigestHashCnt = (USHORT) *(strcTemp.Buffer + SUPPCREDS_CNTLOC);
|
|
DebugLog((DEB_TRACE,"DigestGetPasswd: Read in %d Pre-calc Hashes size = %lu\n",
|
|
pUserCreds->usDigestHashCnt, ulLenHash));
|
|
|
|
if (ulLenHash != ((ULONG)(pUserCreds->usDigestHashCnt + 1) * MD5_HASH_BYTESIZE))
|
|
{
|
|
Status = SEC_E_NO_CREDENTIALS;
|
|
DebugLog((DEB_ERROR, "DigestGetPasswd: Mismatch count with hash size count %d, error 0x%x\n",
|
|
pUserCreds->usDigestHashCnt, Status));
|
|
goto CleanUp;
|
|
}
|
|
*/
|
|
|
|
pUserCreds->usDigestHashCnt = (USHORT) (ulLenHash / MD5_HASH_BYTESIZE) - 1;
|
|
DebugLog((DEB_TRACE,"DigestGetPasswd: Read in %d Pre-calc Hashes size = %lu\n",
|
|
pUserCreds->usDigestHashCnt, ulLenHash));
|
|
|
|
// setup the hashes to utilize - get format from the notify.cxx hash calcs
|
|
switch (pDigest->typeName)
|
|
{
|
|
case NAMEFORMAT_ACCOUNTNAME:
|
|
pUserCreds->sHashTags[NAME_ACCT] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_UPCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_DUCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_UDCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_NUCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_NDCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS_UPCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS_DUCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS_UDCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS_NUCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCTDNS_NDCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_FREALM] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_FREALM_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_ACCT_FREALM_UPCASE] = 1;
|
|
break;
|
|
case NAMEFORMAT_UPN:
|
|
pUserCreds->sHashTags[NAME_UPN] = 1;
|
|
pUserCreds->sHashTags[NAME_UPN_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_UPN_UPCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_UPN_FREALM] = 1;
|
|
pUserCreds->sHashTags[NAME_UPN_FREALM_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_UPN_FREALM_UPCASE] = 1;
|
|
break;
|
|
case NAMEFORMAT_NETBIOS:
|
|
pUserCreds->sHashTags[NAME_NT4] = 1;
|
|
pUserCreds->sHashTags[NAME_NT4_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_NT4_UPCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_NT4_FREALM] = 1;
|
|
pUserCreds->sHashTags[NAME_NT4_FREALM_DOWNCASE] = 1;
|
|
pUserCreds->sHashTags[NAME_NT4_FREALM_UPCASE] = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
pUserCreds->fIsValidDigestHash = TRUE;
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestGetPasswd: Invalid header on pre-calc hashes\n"));
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Retrieve the plaintext password
|
|
//
|
|
// NOTE : On NT 5, this API only works on Domain Controllers !!
|
|
//
|
|
RtlInitUnicodeString(&ustrcPackageName, SAM_CLEARTEXT_CREDENTIAL_NAME);
|
|
|
|
// Note: Would be nice to have this as a LSAFunction
|
|
Status = SamIRetrievePrimaryCredentials( UserHandle,
|
|
&ustrcPackageName,
|
|
&pvPlainPwd,
|
|
&ulLenPassword);
|
|
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
DebugLog((DEB_WARN, "DigestGetPasswd: Could not retrieve plaintext password, status 0x%x\n", Status));
|
|
|
|
if (pUserCreds->fIsValidDigestHash == FALSE)
|
|
{
|
|
// We have no pre-computed MD5 hashes and also no cleartext password
|
|
// we can not perform any Digest Auth operations
|
|
//
|
|
// Explicitly set the status to be "wrong password" instead of whatever
|
|
// is returned by SamIRetrievePrimaryCredentials
|
|
//
|
|
Status = STATUS_WRONG_PASSWORD;
|
|
DebugLog((DEB_ERROR,"DigestGetPasswd: Can not obtain cleartext or Hashed Creds\n"));
|
|
goto CleanUp;
|
|
}
|
|
Status = STATUS_SUCCESS; // we have valid pre-calc hash so continue with no error
|
|
|
|
}
|
|
else
|
|
{
|
|
ustrcTemp.Buffer = (PUSHORT) pvPlainPwd;
|
|
ustrcTemp.Length = ustrcTemp.MaximumLength = (USHORT) ulLenPassword;
|
|
|
|
Status = UnicodeStringDuplicate(&(pUserCreds->ustrPasswd), &ustrcTemp);
|
|
if (!NT_SUCCESS( Status ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestGetPasswd: Failed to copy plaintext password, error 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
// DebugLog((DEB_TRACE,"DigestGetPasswd: Have the PASSWORD %wZ\n", &(pUserCreds->ustrPasswd)));
|
|
|
|
pUserCreds->fIsValidPasswd = TRUE;
|
|
|
|
DebugLog((DEB_TRACE,"DigestGetPasswd: Password retrieved\n"));
|
|
}
|
|
|
|
CleanUp:
|
|
|
|
// Release any memory from SamI* calls Would be nice to have this as a LSAFunction
|
|
|
|
if (pvPlainPwd)
|
|
{
|
|
if (ulLenPassword > 0)
|
|
{
|
|
SecureZeroMemory(pvPlainPwd, ulLenPassword);
|
|
}
|
|
LocalFree(pvPlainPwd);
|
|
pvPlainPwd = NULL;
|
|
}
|
|
|
|
if (pvHashCred)
|
|
{
|
|
if (ulLenHash > 0)
|
|
{
|
|
SecureZeroMemory(pvHashCred, ulLenHash);
|
|
}
|
|
LocalFree(pvHashCred);
|
|
pvHashCred = NULL;
|
|
}
|
|
|
|
DebugLog((DEB_TRACE_FUNC,"DigestGetPasswd: Leaving 0x%x\n", Status));
|
|
|
|
return(Status);
|
|
}
|
|
|
|
|
|
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Function: DigestOpenSamUser
|
|
//
|
|
// Synopsis: Opens the Sam User database for an account
|
|
//
|
|
// Arguments: pusrtUserName - Unicode string for the AccountName (UPN or SAMAccountName)
|
|
// pUserHandle - output handle to the opened User account
|
|
//
|
|
// Returns: NTSTATUS
|
|
//
|
|
// Notes: this call will attempt to locally open an account specified by UPN or SAMAccountName. If it is
|
|
// a UPN, SAM will attemp to crackName locally. If this fails, an error will be returned.
|
|
//
|
|
//---------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestOpenSamUser(
|
|
IN PDIGEST_PARAMETER pDigest,
|
|
OUT SAMPR_HANDLE *ppUserHandle,
|
|
OUT PUCHAR * ppucUserAuthData,
|
|
OUT PULONG pulAuthDataSize
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
NTSTATUS StatusSub = STATUS_SUCCESS;
|
|
PSAMPR_USER_INFO_BUFFER UserAllInfo = NULL ;
|
|
PSAMPR_USER_ALL_INFORMATION UserAll = NULL ;
|
|
SID_AND_ATTRIBUTES_LIST GroupMembership = {0};
|
|
|
|
LARGE_INTEGER CurrentTime = {0};
|
|
LARGE_INTEGER LogoffTime = {0};
|
|
LARGE_INTEGER KickoffTime = {0};
|
|
PLARGE_INTEGER pTempTime = NULL;
|
|
|
|
PPACTYPE Pac = NULL;
|
|
|
|
ASSERT(ppUserHandle);
|
|
ASSERT(ppucUserAuthData);
|
|
ASSERT(pulAuthDataSize);
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestOpenSamUser: Entering\n"));
|
|
|
|
*ppucUserAuthData = NULL;
|
|
*pulAuthDataSize = 0L;
|
|
*ppUserHandle = NULL;
|
|
|
|
GetSystemTimeAsFileTime((PFILETIME) &CurrentTime );
|
|
|
|
Status = DigestOpenSam();
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: DigestOpenSam failed 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Figure out which name format is used in account
|
|
// If it was NetBIOS, then this was cracked locally on the server
|
|
|
|
if (pDigest->ustrCrackedAccountName.Length)
|
|
{
|
|
ASSERT(pDigest->typeName != NAMEFORMAT_UNKNOWN); // Should ALWAYS know format if cracked
|
|
|
|
Status = SamIGetUserLogonInformationEx(l_AccountDomainHandle,
|
|
0, // Defaults to SamAccount Name
|
|
&(pDigest->ustrCrackedAccountName),
|
|
USER_ALL_DIGEST_INFO,
|
|
&UserAllInfo,
|
|
&GroupMembership,
|
|
ppUserHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: GetUserLogonInformation Cracked AccountName failed 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Username format could be SamAccount name or UPN
|
|
// UPN could be a local UPN or need to CrackName and then make genericpassthrough to another DC
|
|
|
|
// Try opening up just the SamAccount name first
|
|
Status = SamIGetUserLogonInformationEx(l_AccountDomainHandle,
|
|
0,
|
|
&(pDigest->ustrUsername),
|
|
USER_ALL_DIGEST_INFO,
|
|
&UserAllInfo,
|
|
&GroupMembership,
|
|
ppUserHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_TRACE, "DigestOpenSamUser: GetUserLogonInformation for SamAccount failed 0x%x\n", Status));
|
|
Status = SamIGetUserLogonInformationEx(l_AccountDomainHandle,
|
|
SAM_OPEN_BY_UPN_OR_ACCOUNTNAME,
|
|
&(pDigest->ustrUsername),
|
|
USER_ALL_DIGEST_INFO,
|
|
&UserAllInfo,
|
|
&GroupMembership,
|
|
ppUserHandle);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: GetUserLogonInformation failed 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Succeeded in finding account by UPN (SamAccount would have matched before)
|
|
pDigest->typeName = NAMEFORMAT_UPN;
|
|
}
|
|
else
|
|
{
|
|
// We suceeded opening up account by SamAccount name
|
|
pDigest->typeName = NAMEFORMAT_ACCOUNTNAME;
|
|
}
|
|
}
|
|
|
|
|
|
UserAll = &UserAllInfo->All ;
|
|
|
|
// Fill in the account name & domain
|
|
UnicodeStringFree(&(pDigest->ustrCrackedAccountName));
|
|
UnicodeStringFree(&(pDigest->ustrCrackedDomain));
|
|
Status = UnicodeStringWCharDuplicate(&(pDigest->ustrCrackedAccountName),
|
|
UserAll->UserName.Buffer,
|
|
UserAll->UserName.Length / sizeof(WCHAR));
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: Account copy failed 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
Status = UnicodeStringDuplicate(&(pDigest->ustrCrackedDomain), &g_NtDigestSecPkg.DomainName);
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: Domain copy failed 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
pDigest->usFlags = pDigest->usFlags & (~FLAG_CRACKNAME_ON_DC); // reset - name is now cracked
|
|
|
|
DebugLog((DEB_TRACE,"DigestOpenSamUser: BadPasswordCount %u Logon Count %u\n",
|
|
UserAll->BadPasswordCount, UserAll->LogonCount));
|
|
|
|
if ( UserAll->UserAccountControl & USER_ACCOUNT_DISABLED )
|
|
{
|
|
Status = STATUS_ACCOUNT_DISABLED;
|
|
goto CleanUp;
|
|
}
|
|
|
|
if ( UserAll->UserAccountControl & USER_ACCOUNT_AUTO_LOCKED )
|
|
{
|
|
Status = STATUS_ACCOUNT_LOCKED_OUT;
|
|
goto CleanUp ;
|
|
}
|
|
|
|
if ( UserAll->UserAccountControl & USER_SMARTCARD_REQUIRED )
|
|
{
|
|
Status = STATUS_SMARTCARD_LOGON_REQUIRED;
|
|
goto CleanUp ;
|
|
}
|
|
|
|
//
|
|
// Check the restrictions SAM doesn't:
|
|
//
|
|
|
|
pTempTime = (PLARGE_INTEGER) &UserAll->AccountExpires;
|
|
if ((pTempTime->QuadPart != 0) &&
|
|
(pTempTime->QuadPart < CurrentTime.QuadPart))
|
|
{
|
|
Status = STATUS_ACCOUNT_EXPIRED;
|
|
goto CleanUp;
|
|
}
|
|
|
|
//
|
|
// For user accounts, check if the password has expired.
|
|
//
|
|
|
|
if (UserAll->UserAccountControl & USER_NORMAL_ACCOUNT)
|
|
{
|
|
pTempTime = (PLARGE_INTEGER) &UserAll->PasswordMustChange;
|
|
|
|
if (pTempTime->QuadPart < CurrentTime.QuadPart)
|
|
{
|
|
if (pTempTime->QuadPart == 0)
|
|
{
|
|
Status = STATUS_PASSWORD_MUST_CHANGE;
|
|
}
|
|
else
|
|
{
|
|
Status = STATUS_PASSWORD_EXPIRED;
|
|
}
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: Failed PasswordMustChange 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
}
|
|
|
|
// One final check on status of password - this should be duplicate test from previous one
|
|
if ( UserAll->UserAccountControl & USER_PASSWORD_EXPIRED )
|
|
{
|
|
Status = STATUS_PASSWORD_EXPIRED;
|
|
goto CleanUp ;
|
|
}
|
|
|
|
Status = SamIAccountRestrictions(
|
|
*ppUserHandle,
|
|
&pDigest->ustrWorkstation,
|
|
(PUNICODE_STRING) &UserAll->WorkStations,
|
|
(PLOGON_HOURS) &UserAll->LogonHours,
|
|
&LogoffTime,
|
|
&KickoffTime
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: Failed TOD AccountRestrictions 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
// Now create the PAC for this user
|
|
Status = PAC_Init( UserAll,
|
|
NULL,
|
|
&GroupMembership,
|
|
g_NtDigestSecPkg.DomainSid,
|
|
&(g_NtDigestSecPkg.DnsDomainName),
|
|
&g_ustrWorkstationName,
|
|
0, // no signature
|
|
0, // no additional data
|
|
NULL, // no additional data
|
|
&Pac );
|
|
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestOpenSamUser: Failed to Init PAC 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
*pulAuthDataSize = PAC_GetSize( Pac );
|
|
|
|
*ppucUserAuthData = (PUCHAR)MIDL_user_allocate( *pulAuthDataSize );
|
|
|
|
if ( *ppucUserAuthData )
|
|
{
|
|
PAC_Marshal( Pac, *pulAuthDataSize, *ppucUserAuthData );
|
|
}
|
|
else
|
|
{
|
|
Status = SEC_E_INSUFFICIENT_MEMORY ;
|
|
}
|
|
|
|
MIDL_user_free( Pac );
|
|
|
|
|
|
CleanUp:
|
|
|
|
if ( UserAllInfo )
|
|
{
|
|
SamIFree_SAMPR_USER_INFO_BUFFER( UserAllInfo, UserAllInformation );
|
|
}
|
|
|
|
if (GroupMembership.SidAndAttributes != NULL)
|
|
{
|
|
SamIFreeSidAndAttributesList(&GroupMembership);
|
|
}
|
|
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{ // Cleanup functions since there was a failure
|
|
if (*ppucUserAuthData)
|
|
{
|
|
MIDL_user_free(*ppucUserAuthData);
|
|
*ppucUserAuthData = NULL;
|
|
*pulAuthDataSize = 0L;
|
|
}
|
|
|
|
if (*ppUserHandle)
|
|
{
|
|
StatusSub = DigestCloseSamUser(*ppUserHandle);
|
|
if (!NT_SUCCESS(StatusSub))
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestOpenSamUser: failed DigestCloseSamUser on error 0x%x\n", StatusSub));
|
|
}
|
|
*ppUserHandle = NULL;
|
|
}
|
|
}
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestOpenSamUser: Leaving 0x%x\n", Status));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Function: DigestCloseSamUser
|
|
//
|
|
// Synopsis: Closes the Sam User database for an account
|
|
//
|
|
// Arguments: pUserHandle - output handle to the opened User account
|
|
//
|
|
// Returns: NTSTATUS
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestCloseSamUser(
|
|
IN SAMPR_HANDLE UserHandle)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestCloseSamUser: Entering\n"));
|
|
|
|
// used to use LsaCloseSamUser()
|
|
Status = SamrCloseHandle( &UserHandle );
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestCloseSamUser: Leaving 0x%x\n", Status));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Function: DigestUpdateLogonStatistics
|
|
//
|
|
// Synopsis: Update the logon stats for a user (bad passwd attempts ....)
|
|
//
|
|
// Arguments: pUserHandle - output handle to the opened User account
|
|
//
|
|
// Returns: NTSTATUS
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestUpdateLogonStatistics(
|
|
IN SAM_HANDLE UserHandle,
|
|
IN PSAM_LOGON_STATISTICS pLogonStats)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestUpdateLogonStatistics: Entering\n"));
|
|
|
|
Status = SamIUpdateLogonStatistics(
|
|
UserHandle,
|
|
pLogonStats );
|
|
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestUpdateLogonStatistics: Leaving 0x%x\n", Status));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Function: DigestOpenSam
|
|
//
|
|
// Synopsis: Opens the Sam - done only on a domain controller
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns: NTSTATUS
|
|
//
|
|
// Notes: Can be called in multithreaded mode - no need to acquire at startup
|
|
//
|
|
//---------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestOpenSam()
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
SAMPR_HANDLE SamHandle = NULL;
|
|
SAMPR_HANDLE DomainHandle = NULL;
|
|
|
|
// if already have valid handle to SAM
|
|
if (l_AccountDomainHandle)
|
|
return STATUS_SUCCESS;
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestOpenSam: Entering\n"));
|
|
|
|
//
|
|
// 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, "DigestOpenSam: SamIConnect failed 0x%x\n", Status));
|
|
goto CleanUp;
|
|
}
|
|
|
|
if(InterlockedCompareExchangePointer(
|
|
&l_AccountSamHandle,
|
|
SamHandle,
|
|
NULL
|
|
) != NULL)
|
|
{
|
|
SamrCloseHandle( &SamHandle );
|
|
}
|
|
|
|
|
|
Status = SamrOpenDomain(
|
|
l_AccountSamHandle,
|
|
0, // no desired access
|
|
(PRPC_SID) g_NtDigestSecPkg.DomainSid,
|
|
&DomainHandle
|
|
);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto CleanUp;
|
|
}
|
|
|
|
if(InterlockedCompareExchangePointer(
|
|
&l_AccountDomainHandle,
|
|
DomainHandle,
|
|
NULL
|
|
) != NULL)
|
|
{
|
|
SamrCloseHandle( &DomainHandle );
|
|
}
|
|
|
|
CleanUp:
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestOpenSam: Leaving 0x%x\n", Status));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
//+--------------------------------------------------------------------
|
|
//
|
|
// Function: DigestCloseSam
|
|
//
|
|
// Synopsis: Closes the Sam
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Returns: NTSTATUS
|
|
//
|
|
// Notes:
|
|
//
|
|
//---------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestCloseSam()
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
if (l_AccountDomainHandle)
|
|
{
|
|
SamrCloseHandle( &l_AccountDomainHandle );
|
|
l_AccountDomainHandle = NULL;
|
|
}
|
|
|
|
|
|
if (l_AccountSamHandle)
|
|
{
|
|
SamrCloseHandle( &l_AccountSamHandle );
|
|
l_AccountSamHandle = NULL;
|
|
}
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestCloseSamUser: Leaving\n"));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: MIDL_user_allocate
|
|
//
|
|
// Synopsis: Allocation routine for use by RPC client stubs
|
|
//
|
|
// Effects: allocates memory with LsaFunctions.AllocateLsaHeap
|
|
//
|
|
// Arguments: BufferSize - size of buffer, in bytes, to allocate
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: Buffer pointer or NULL on allocation failure
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
PVOID
|
|
MIDL_user_allocate(
|
|
IN size_t BufferSize
|
|
)
|
|
{
|
|
return (DigestAllocateMemory( ROUND_UP_COUNT((ULONG) BufferSize, 8) ) );
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: MIDL_user_free
|
|
//
|
|
// Synopsis: Memory free routine for RPC client stubs
|
|
//
|
|
// Effects: frees the buffer with LsaFunctions.FreeLsaHeap
|
|
//
|
|
// Arguments: Buffer - Buffer to free
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: none
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
|
|
|
|
VOID
|
|
MIDL_user_free(
|
|
IN PVOID Buffer
|
|
)
|
|
{
|
|
DigestFreeMemory( Buffer );
|
|
}
|
|
|
|
|
|
|
|
// The following two routines were copied from the LSA server policy utilities dbluutil.c
|
|
// LSANullTerminateUnicodeString and LsapCompareDomainNames
|
|
NTSTATUS
|
|
DigestNullTerminateUnicodeString(
|
|
IN PUNICODE_STRING String,
|
|
OUT LPWSTR *pBuffer,
|
|
OUT BOOLEAN *fFreeBuffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine accepts a UNICODE_STRING and returns its internal buffer,
|
|
ensuring that it is NULL terminated.
|
|
|
|
If the buffer is NULL terminated it will be returned in pBuffer.
|
|
|
|
If the buffer isn't NULL terminated it will be reallocated, NULL terminated,
|
|
and returned in pBuffer. fFreeBuffer will be set to TRUE indicating the
|
|
caller is responsible for deallocating pBuffer.
|
|
|
|
If an error occurs then pBuffer will be NULL, fFreeBuffer will be FALSE, and
|
|
no memory will be allocated.
|
|
|
|
Arguments:
|
|
|
|
String - Pointer to a UNICODE_STRING
|
|
|
|
pBuffer - Pointer to a pointer to return the buffer
|
|
|
|
fFreeBuffer - Pointer to a BOOLEAN to indicate whether the caller needs to
|
|
deallocate pBuffer or not.
|
|
|
|
Return Values:
|
|
|
|
STATUS_SUCCESS - *pBuffer points to a NULL terminated version of String's
|
|
internal buffer. Check *fFreeBuffer to determine if
|
|
pBuffer must be freed by the caller.
|
|
|
|
STATUS_NO_MEMORY - The routine failed to NULL terminate String's internal
|
|
buffer. *pBuffer is NULL and *fFreeBuffer is FALSE.
|
|
|
|
--*/
|
|
|
|
{
|
|
BOOLEAN fNullTerminated;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
ASSERT(pBuffer);
|
|
ASSERT(fFreeBuffer);
|
|
|
|
//
|
|
// Initialize input parameters
|
|
//
|
|
*pBuffer = NULL;
|
|
*fFreeBuffer = FALSE;
|
|
|
|
//
|
|
// Short circuit for strings that are already NULL terminated.
|
|
//
|
|
fNullTerminated = (String->MaximumLength > String->Length &&
|
|
String->Buffer[String->Length / sizeof(WCHAR)] == UNICODE_NULL);
|
|
|
|
if (!fNullTerminated) {
|
|
|
|
//
|
|
// Allocate enough memory to include a terminating NULL character
|
|
//
|
|
*pBuffer = (WCHAR*)midl_user_allocate(String->Length + sizeof(WCHAR));
|
|
|
|
if ( NULL == *pBuffer ) {
|
|
Status = STATUS_NO_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Copy the buffer into pBuffer and NULL terminate it.
|
|
//
|
|
*fFreeBuffer = TRUE;
|
|
|
|
RtlCopyMemory(*pBuffer, String->Buffer, String->Length);
|
|
|
|
(*pBuffer)[String->Length / sizeof(WCHAR)] = UNICODE_NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// String's internal buffer is already NULL terminated, return it.
|
|
//
|
|
*pBuffer = String->Buffer;
|
|
}
|
|
|
|
return Status;
|
|
|
|
}
|
|
|
|
|
|
BOOL
|
|
DigestCompareDomainNames(
|
|
IN PUNICODE_STRING String,
|
|
IN PUNICODE_STRING AmbiguousName,
|
|
IN PUNICODE_STRING FlatName OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine performs a case insensitive comparison between a string
|
|
and a domain name. If both the NetBIOS and Dns name forms are known then
|
|
the caller must pass the NetBIOS domain name as FlatName and the Dns domain
|
|
name as AmbiguousName. A non-NULL value for FlatName indicates the caller
|
|
knows both name forms and will result in a more optimal comparison. If the
|
|
caller has only one name form and it is ambiguous, that is it may be NetBIOS
|
|
or Dns, then the caller must pass NULL for FlatName. The routine will try
|
|
both a NetBIOS comparison using RtlEqualDomainName and a Dns comparison
|
|
using DnsNameCompare_W. If either comparison results in equality the TRUE
|
|
is returned, otherwise FALSE.
|
|
|
|
This routine provides centralized logic for robust domain name comparison
|
|
consistent with domain name semantics in Lsa data structures. Lsa trust
|
|
information structures are interpreted in the following way.
|
|
|
|
LSAPR_TRUST_INFORMATION.Name - Either NetBIOS or Dns
|
|
|
|
The following structures have both a FlatName and DomainName (or Name)
|
|
field. In this case they are interpreted as follows:
|
|
|
|
LSAPR_TRUST_INFORMATION_EX
|
|
LSAPR_TRUSTED_DOMAIN_INFORMATION_EX
|
|
LSAPR_TRUSTED_DOMAIN_INFORMATION_EX2
|
|
|
|
If the FlatName field is NULL then the other name field is ambiguous.
|
|
If the FlatName field is non NULL, then the other name field is Dns.
|
|
|
|
NetBIOS comparison is performed using RtlEqualDomainName which enforces the
|
|
proper OEM character equivelencies. DNS name comparison is performed using
|
|
DnsNameCompare_W to ensure proper handling of trailing dots and character
|
|
equivelencies.
|
|
|
|
Arguments:
|
|
|
|
String -- Potentially ambiguous domain name to compare against
|
|
AmbiguousName, and FlatName if non-NULL.
|
|
|
|
AmbiguousName -- Is treated as an ambiguous name form unless FlatName
|
|
is also specified. If FlatName is non-NULL then
|
|
AmbiguousName is treated as a Dns domain name.
|
|
|
|
FlatName -- This parameter is optional. If present it must be the
|
|
flat name of the domain. Furthermore, passing this
|
|
parameter indicates that AmbiguousName is in fact a
|
|
Dns domain name.
|
|
|
|
Return Values:
|
|
|
|
TRUE - String is equivelent to one of FlatDomainName or DnsDomainName
|
|
|
|
FALSE - String is not equivelent to either domain name
|
|
|
|
If any parameter isn't a valid UNICODE_STRING then FALSE is returned.
|
|
|
|
Notes:
|
|
|
|
The number of comparisons required to determine equivelency will depend
|
|
on the ammount of information passed in by the caller. If both the
|
|
NetBIOS and Dns domain names are known, pass them both to ensure the minimal
|
|
number of comparisons.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status;
|
|
BOOLEAN fEquivalent = FALSE;
|
|
LPWSTR StringBuffer = NULL;
|
|
LPWSTR AmbiguousNameBuffer = NULL;
|
|
BOOLEAN fFreeStringBuffer = FALSE;
|
|
BOOLEAN fFreeAmbiguousBuffer = FALSE;
|
|
|
|
//
|
|
// Validate input strings
|
|
//
|
|
ASSERT(String);
|
|
ASSERT(AmbiguousName);
|
|
ASSERT(NT_SUCCESS(RtlValidateUnicodeString( 0, String )));
|
|
ASSERT(NT_SUCCESS(RtlValidateUnicodeString( 0, AmbiguousName )));
|
|
|
|
//
|
|
// Ensure the UNICODE_STRING data buffers are NULL terminated before
|
|
// passing them to DnsNameCompare_W
|
|
//
|
|
Status = DigestNullTerminateUnicodeString( String,
|
|
&StringBuffer,
|
|
&fFreeStringBuffer
|
|
);
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = DigestNullTerminateUnicodeString( AmbiguousName,
|
|
&AmbiguousNameBuffer,
|
|
&fFreeAmbiguousBuffer
|
|
);
|
|
}
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
|
|
if ( NULL == FlatName ) {
|
|
//
|
|
// AmbiguousName is truly ambiguous, we must perform both
|
|
// types of comparison between String
|
|
//
|
|
fEquivalent = ( RtlEqualDomainName( String, AmbiguousName ) ||
|
|
DnsNameCompare_W( StringBuffer,
|
|
AmbiguousNameBuffer )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(NT_SUCCESS(RtlValidateUnicodeString( 0, FlatName )));
|
|
|
|
//
|
|
// We are sure of the name forms so lets just use the
|
|
// appropriate comparison routines on each.
|
|
//
|
|
fEquivalent = ( RtlEqualDomainName( String, FlatName ) ||
|
|
DnsNameCompare_W( StringBuffer,
|
|
AmbiguousNameBuffer )
|
|
);
|
|
}
|
|
}
|
|
|
|
if ( fFreeStringBuffer ) {
|
|
MIDL_user_free( StringBuffer );
|
|
}
|
|
|
|
if ( fFreeAmbiguousBuffer ) {
|
|
MIDL_user_free( AmbiguousNameBuffer );
|
|
}
|
|
|
|
if (fEquivalent)
|
|
return(TRUE);
|
|
else
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: DigestCheckPacForSidFiltering
|
|
//
|
|
// Synopsis: If the ticket info has a TDOSid then the function
|
|
// makes a check to make sure the SID from the TDO matches
|
|
// the client's home domain SID. A call to LsaIFilterSids
|
|
// is made to do the check. If this function fails with
|
|
// STATUS_TRUST_FAILURE then an audit log is generated.
|
|
// Otherwise the function succeeds but SIDs are filtered
|
|
// from the PAC.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments:
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns:
|
|
//
|
|
// Notes: taken from updated kerberos code
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestCheckPacForSidFiltering(
|
|
IN PDIGEST_PARAMETER pDigest,
|
|
IN OUT PUCHAR *PacData,
|
|
IN OUT PULONG PacSize
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PPAC_INFO_BUFFER LogonInfo;
|
|
PPACTYPE OldPac;
|
|
ULONG OldPacSize;
|
|
PPACTYPE NewPac = NULL;
|
|
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL;
|
|
SAMPR_PSID_ARRAY ZeroResourceGroups;
|
|
ULONG OldExtraSidCount;
|
|
PPACTYPE RemarshalPac = NULL;
|
|
ULONG RemarshalPacSize = 0;
|
|
|
|
OldPac = (PPACTYPE) *PacData;
|
|
OldPacSize = *PacSize;
|
|
|
|
DebugLog((DEB_TRACE_FUNC,"DigestCheckPacForSidFiltering: Entering\n"));
|
|
|
|
if (PAC_UnMarshal(OldPac, OldPacSize) == 0)
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestCheckPacForSidFiltering: Failed to unmarshal pac\n"));
|
|
Status = SEC_E_CANNOT_PACK;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Must remember to remarshal the PAC prior to returning
|
|
//
|
|
|
|
RemarshalPac = OldPac;
|
|
RemarshalPacSize = OldPacSize;
|
|
|
|
RtlZeroMemory(
|
|
&ZeroResourceGroups,
|
|
sizeof(ZeroResourceGroups)); // allows us to use PAC_InitAndUpdateGroups to remarshal the PAC
|
|
|
|
//
|
|
// First, find the logon information
|
|
//
|
|
|
|
LogonInfo = PAC_Find(
|
|
OldPac,
|
|
PAC_LOGON_INFO,
|
|
NULL
|
|
);
|
|
|
|
if (LogonInfo == NULL)
|
|
{
|
|
DebugLog((DEB_WARN,"DigestCheckPacForSidFiltering: No logon info for PAC - not making SID filtering check\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now unmarshall the validation information and build a list of sids
|
|
//
|
|
|
|
Status = PAC_UnmarshallValidationInfo(
|
|
&ValidationInfo,
|
|
LogonInfo->Data,
|
|
LogonInfo->cbBufferSize);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestCheckPacForSidFiltering: Failed to unmarshall validation info! 0x%x\n", Status));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Save the old extra SID count (so that if KdcFilterSids compresses
|
|
// the SID array, we can avoid allocating memory for the other-org SID later)
|
|
//
|
|
|
|
OldExtraSidCount = ValidationInfo->SidCount;
|
|
|
|
//
|
|
// Call lsaifiltersids().
|
|
//
|
|
|
|
Status = DigestFilterSids(
|
|
pDigest,
|
|
ValidationInfo
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestCheckPacForSidFiltering: Failed filtering SIDs\n"));
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Other org prcoessing was here - not supported currently for digest
|
|
|
|
//
|
|
// Now build a new pac
|
|
//
|
|
|
|
Status = PAC_InitAndUpdateGroups(
|
|
ValidationInfo,
|
|
&ZeroResourceGroups,
|
|
OldPac,
|
|
&NewPac
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestCheckPacForSidFiltering: Failed pac init and updating groups 0x%x\n", Status));
|
|
goto Cleanup;
|
|
}
|
|
|
|
RemarshalPacSize = PAC_GetSize(NewPac);
|
|
RemarshalPac = NewPac;
|
|
|
|
Cleanup:
|
|
|
|
if ( RemarshalPac != NULL )
|
|
{
|
|
if (!PAC_ReMarshal(RemarshalPac, RemarshalPacSize))
|
|
{
|
|
// PAC_Remarshal Failed
|
|
ASSERT(0);
|
|
Status = SEC_E_CANNOT_PACK;
|
|
}
|
|
else if ( NewPac != NULL &&
|
|
*PacData != (PBYTE)NewPac )
|
|
{
|
|
MIDL_user_free(*PacData);
|
|
*PacData = (PBYTE) NewPac;
|
|
NewPac = NULL;
|
|
*PacSize = RemarshalPacSize;
|
|
}
|
|
}
|
|
|
|
if (NewPac != NULL)
|
|
{
|
|
MIDL_user_free(NewPac);
|
|
}
|
|
|
|
if (ValidationInfo != NULL)
|
|
{
|
|
MIDL_user_free(ValidationInfo);
|
|
}
|
|
|
|
DebugLog((DEB_TRACE_FUNC,"DigestCheckPacForSidFiltering: Leaving 0x%x\n", Status));
|
|
|
|
return(Status);
|
|
}
|
|
|
|
//+-------------------------------------------------------------------------
|
|
//
|
|
// Function: DigestFilterSids
|
|
//
|
|
// Synopsis: Function that just call LsaIFilterSids.
|
|
//
|
|
// Effects:
|
|
//
|
|
// Arguments: ServerInfo structure containing attributes of the trust
|
|
// ValidationInfo authorization information to filter
|
|
//
|
|
// Requires:
|
|
//
|
|
// Returns: See LsaIFilterSids
|
|
//
|
|
// Notes:
|
|
//
|
|
//
|
|
//--------------------------------------------------------------------------
|
|
NTSTATUS
|
|
DigestFilterSids(
|
|
IN PDIGEST_PARAMETER pDigest,
|
|
IN PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUNICODE_STRING pustrTrustedForest = NULL;
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestFilterSids: Entering\n"));
|
|
|
|
// Currently we do not allow auth for domains outside forests so this should never fire
|
|
/*
|
|
if ((pDigest->ulTrustAttributes & TRUST_ATTRIBUTE_FOREST_TRANSITIVE) != 0)
|
|
{
|
|
ASSERT(0); // this should not happen until cross forest is supported
|
|
pustrTrustedForest = &(pDigest->ustrTrustedForest);
|
|
DebugLog((DEB_TRACE, "DigestFilterSids: Filtering Sids for forest %wZ\n", pustrTrustedForest));
|
|
}
|
|
*/
|
|
|
|
// This call is used for both intra-forest domain filtering (where the pTrustSid is non-NULL
|
|
// and for member-to-DC boundary filtering - where the pTrustSid is NULL
|
|
|
|
Status = LsaIFilterSids(
|
|
pustrTrustedForest, // Pass domain name here
|
|
pDigest->ulTrustDirection,
|
|
pDigest->ulTrustType,
|
|
pDigest->ulTrustAttributes,
|
|
pDigest->pTrustSid,
|
|
NetlogonValidationSamInfo2,
|
|
ValidationInfo,
|
|
NULL,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// Create an audit log if it looks like the SID has been tampered with - ToDo
|
|
//
|
|
|
|
/*
|
|
if ((STATUS_DOMAIN_TRUST_INCONSISTENT == Status) &&
|
|
SecData.AuditKdcEvent(KDC_AUDIT_TGS_FAILURE))
|
|
{
|
|
DWORD Dummy = 0;
|
|
|
|
KdcLsaIAuditKdcEvent(
|
|
SE_AUDITID_TGS_TICKET_REQUEST,
|
|
&ValidationInfo->EffectiveName,
|
|
&ValidationInfo->LogonDomainName,
|
|
NULL,
|
|
&ServerInfo->AccountName,
|
|
NULL,
|
|
&Dummy,
|
|
(PULONG) &Status,
|
|
NULL,
|
|
NULL, // no preauth type
|
|
GET_CLIENT_ADDRESS(NULL),
|
|
NULL // no logon guid
|
|
);
|
|
}
|
|
*/
|
|
|
|
DebugLog((DEB_ERROR,"DigestFilterSids: Failed to filter SIDS (LsaIFilterSids): 0x%x\n",Status));
|
|
}
|
|
else
|
|
{
|
|
DebugLog((DEB_TRACE,"DigestFilterSids: successfully filtered sids\n",Status));
|
|
}
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestFilterSids: Leaving Status 0x%x\n", Status));
|
|
|
|
return Status;
|
|
}
|
|
|
|
/*
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: LsaConvertAuthDataToToken
|
|
//
|
|
// Synopsis: Convert an opaque PAC structure into a token.
|
|
//
|
|
// Arguments: [UserAuthData] --
|
|
// [UserAuthDataSize] --
|
|
// [TokenInformation] --
|
|
// [TokenInformationType] --
|
|
//
|
|
// History: 3-14-97 RichardW Created
|
|
//
|
|
// Notes:
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
LsaConvertAuthDataToToken(
|
|
IN PVOID UserAuthData,
|
|
IN ULONG UserAuthDataSize,
|
|
IN SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
|
|
IN PTOKEN_SOURCE TokenSource,
|
|
IN SECURITY_LOGON_TYPE LogonType,
|
|
IN PUNICODE_STRING AuthorityName,
|
|
OUT PHANDLE TokenHandle,
|
|
OUT PLUID LogonId,
|
|
OUT PUNICODE_STRING AccountName,
|
|
OUT PNTSTATUS SubStatus
|
|
)
|
|
{
|
|
NTSTATUS Status ;
|
|
PPACTYPE Pac = NULL ;
|
|
PPAC_INFO_BUFFER LogonInfo = NULL ;
|
|
PNETLOGON_VALIDATION_SAM_INFO3 ValidationInfo = NULL ;
|
|
PLSA_TOKEN_INFORMATION_V1 TokenInfo = NULL ;
|
|
|
|
LogonId->HighPart = LogonId->LowPart = 0;
|
|
*TokenHandle = NULL;
|
|
RtlInitUnicodeString(
|
|
AccountName,
|
|
NULL
|
|
);
|
|
|
|
*SubStatus = STATUS_SUCCESS;
|
|
|
|
Pac = (PPACTYPE) UserAuthData ;
|
|
|
|
if ( PAC_UnMarshal( Pac, UserAuthDataSize ) == 0 )
|
|
{
|
|
DebugLog(( DEB_ERROR, "Failed to unmarshall pac\n" ));
|
|
|
|
Status = STATUS_INVALID_PARAMETER ;
|
|
|
|
goto CreateToken_Cleanup ;
|
|
}
|
|
|
|
LogonInfo = PAC_Find( Pac, PAC_LOGON_INFO, NULL );
|
|
|
|
if ( !LogonInfo )
|
|
{
|
|
DebugLog(( DEB_ERROR, "Failed to find logon info in pac\n" ));
|
|
|
|
Status = STATUS_INVALID_PARAMETER ;
|
|
|
|
goto CreateToken_Cleanup ;
|
|
}
|
|
|
|
Status = PAC_UnmarshallValidationInfo(
|
|
&ValidationInfo,
|
|
LogonInfo->Data,
|
|
LogonInfo->cbBufferSize
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to unmarshall validation info: 0x%x\n",
|
|
Status));
|
|
|
|
goto CreateToken_Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now we need to build a LSA_TOKEN_INFORMATION_V1 from the validation
|
|
// information
|
|
//
|
|
|
|
Status = LsapMakeTokenInformationV1(
|
|
ValidationInfo,
|
|
&TokenInfo
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
DebugLog((DEB_ERROR,"Failed to make token informatin v1: 0x%x\n",
|
|
Status));
|
|
goto CreateToken_Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now, copy the user name.
|
|
//
|
|
|
|
Status = LsapDuplicateString( AccountName, &ValidationInfo->EffectiveName );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
goto CreateToken_Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Now create a logon session
|
|
//
|
|
|
|
Status = LsapCreateLogonSession( LogonId );
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto CreateToken_Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now create the token
|
|
//
|
|
|
|
Status = LsapCreateToken(
|
|
LogonId,
|
|
TokenSource,
|
|
LogonType,
|
|
ImpersonationLevel,
|
|
LsaTokenInformationV1,
|
|
TokenInfo,
|
|
NULL, // no token groups
|
|
AccountName,
|
|
AuthorityName,
|
|
NULL,
|
|
&ValidationInfo->ProfilePath,
|
|
TokenHandle,
|
|
SubStatus
|
|
);
|
|
|
|
//
|
|
// NULL out the TokenInfo pointer. LsapCreateToken will
|
|
// free the memory under all conditions
|
|
//
|
|
|
|
TokenInfo = NULL ;
|
|
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
goto CreateToken_Cleanup;
|
|
}
|
|
|
|
//
|
|
// We don't need to free the token information because CreateToken does
|
|
// that for us.
|
|
//
|
|
|
|
MIDL_user_free(ValidationInfo);
|
|
return Status ;
|
|
|
|
CreateToken_Cleanup:
|
|
|
|
if ( TokenInfo )
|
|
{
|
|
LsaFreeTokenInfo( LsaTokenInformationV1, TokenInfo );
|
|
}
|
|
|
|
if ((LogonId->LowPart != 0) || (LogonId->HighPart != 0))
|
|
{
|
|
LsapDeleteLogonSession(LogonId);
|
|
}
|
|
|
|
LsapFreeString(
|
|
AccountName
|
|
);
|
|
|
|
if (ValidationInfo != NULL)
|
|
{
|
|
MIDL_user_free(ValidationInfo);
|
|
}
|
|
|
|
return Status ;
|
|
}
|
|
|
|
*/
|
|
|
|
|
|
#ifdef ROGUE_DC
|
|
|
|
#pragma message( "COMPILING A ROGUE DC!!!" )
|
|
#pragma message( "MUST NOT SHIP THIS BUILD!!!" )
|
|
|
|
#pragma warning(disable:4127) // Disable warning/error for conditional expression is constant
|
|
|
|
#define MAX_SID_LEN (sizeof(SID) + sizeof(ULONG) * SID_MAX_SUB_AUTHORITIES)
|
|
|
|
|
|
extern "C"
|
|
{
|
|
#include "sddl.h"
|
|
#include "stdlib.h"
|
|
}
|
|
|
|
extern HKEY g_hDigestRogueKey;
|
|
|
|
NTSTATUS
|
|
DigestInstrumentRoguePac(
|
|
IN OUT PUCHAR *PacData,
|
|
IN OUT PULONG PacSize
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
PNETLOGON_VALIDATION_SAM_INFO3 OldValidationInfo = NULL;
|
|
NETLOGON_VALIDATION_SAM_INFO3 NewValidationInfo = {0};
|
|
SAMPR_PSID_ARRAY ZeroResourceGroups = {0};
|
|
PPACTYPE NewPac = NULL;
|
|
ULONG NewPacSize;
|
|
PPAC_INFO_BUFFER LogonInfo;
|
|
|
|
PPACTYPE OldPac = NULL;
|
|
ULONG OldPacSize;
|
|
|
|
PSID LogonDomainId = NULL;
|
|
PSID ResourceGroupDomainSid = NULL;
|
|
PGROUP_MEMBERSHIP GroupIds = NULL;
|
|
PGROUP_MEMBERSHIP ResourceGroupIds = NULL;
|
|
PNETLOGON_SID_AND_ATTRIBUTES ExtraSids = NULL;
|
|
BYTE FullUserSidBuffer[MAX_SID_LEN];
|
|
SID * FullUserSid = ( SID * )FullUserSidBuffer;
|
|
CHAR * FullUserSidText = NULL;
|
|
|
|
DWORD dwType;
|
|
DWORD cbData = 0;
|
|
PCHAR Buffer;
|
|
PCHAR Value = NULL;
|
|
|
|
BOOLEAN PacChanged = FALSE;
|
|
|
|
DebugLog((DEB_TRACE_FUNC,"DigestInstrumentRoguePac: Entering\n"));
|
|
|
|
//
|
|
// Optimization: no "rogue" key in registry - nothing for us to do
|
|
//
|
|
|
|
if ( g_hDigestRogueKey == NULL )
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
OldPac = (PPACTYPE) *PacData;
|
|
OldPacSize = *PacSize;
|
|
|
|
//
|
|
// Unmarshall the old PAC
|
|
//
|
|
|
|
if ( PAC_UnMarshal(OldPac, OldPacSize) == 0 )
|
|
{
|
|
DebugLog((DEB_ERROR,"DigestInstrumentRoguePac: Failed to unmarshal pac\n"));
|
|
Status = SEC_E_CANNOT_PACK;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// First, find the logon information
|
|
//
|
|
|
|
LogonInfo = PAC_Find(
|
|
OldPac,
|
|
PAC_LOGON_INFO,
|
|
NULL
|
|
);
|
|
|
|
if ( LogonInfo == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: No logon info on PAC - not performing substitution\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Now unmarshall the validation information and build a list of sids
|
|
//
|
|
|
|
if ( !NT_SUCCESS(PAC_UnmarshallValidationInfo(
|
|
&OldValidationInfo,
|
|
LogonInfo->Data,
|
|
LogonInfo->cbBufferSize )))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Unable to unmarshal validation info\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Construct the text form of the full user's SID (logon domain ID + user ID)
|
|
//
|
|
|
|
DsysAssert( sizeof( FullUserSidBuffer ) >= MAX_SID_LEN );
|
|
|
|
RtlCopySid(
|
|
sizeof( FullUserSidBuffer ),
|
|
FullUserSid,
|
|
OldValidationInfo->LogonDomainId
|
|
);
|
|
|
|
FullUserSid->SubAuthority[FullUserSid->SubAuthorityCount] = OldValidationInfo->UserId;
|
|
FullUserSid->SubAuthorityCount += 1;
|
|
|
|
if ( FALSE == ConvertSidToStringSidA(
|
|
FullUserSid,
|
|
&FullUserSidText ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Unable to convert user's SID\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Now look in the registry for the SID matching the validation info
|
|
//
|
|
|
|
if ( ERROR_SUCCESS != RegQueryValueExA(
|
|
g_hDigestRogueKey,
|
|
FullUserSidText,
|
|
NULL,
|
|
&dwType,
|
|
NULL,
|
|
&cbData ) ||
|
|
dwType != REG_MULTI_SZ ||
|
|
cbData <= 1 )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: No substitution info available for %s\n", FullUserSidText));
|
|
Status = STATUS_SUCCESS;
|
|
goto Error;
|
|
}
|
|
|
|
// SafeAllocaAllocate( Value, cbData );
|
|
Value = (PCHAR)DigestAllocateMemory(cbData);
|
|
if ( Value == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating substitution buffer\n", FullUserSidText));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
if ( ERROR_SUCCESS != RegQueryValueExA(
|
|
g_hDigestRogueKey,
|
|
FullUserSidText,
|
|
NULL,
|
|
&dwType,
|
|
(PBYTE)Value,
|
|
&cbData ) ||
|
|
dwType != REG_MULTI_SZ ||
|
|
cbData <= 1 )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Error reading from registry\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Substituting the PAC for %s\n", FullUserSidText));
|
|
|
|
Buffer = Value;
|
|
|
|
//
|
|
// New validation info will be overloaded with stuff from the file
|
|
//
|
|
|
|
NewValidationInfo = *OldValidationInfo;
|
|
|
|
//
|
|
// Read the input file one line at a time
|
|
//
|
|
|
|
while ( *Buffer != '\0' )
|
|
{
|
|
switch( Buffer[0] )
|
|
{
|
|
case 'l':
|
|
case 'L': // logon domain ID
|
|
|
|
if ( LogonDomainId != NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Logon domain ID specified more than once - only first one kept\n"));
|
|
break;
|
|
}
|
|
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Substituting logon domain ID by %s\n", &Buffer[1]));
|
|
|
|
if ( FALSE == ConvertStringSidToSidA(
|
|
&Buffer[1],
|
|
&LogonDomainId ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Unable to convert SID\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
if ( LogonDomainId == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating LogonDomainId\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
NewValidationInfo.LogonDomainId = LogonDomainId;
|
|
LogonDomainId = NULL;
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
case 'D': // resource group domain SID
|
|
|
|
if ( ResourceGroupDomainSid != NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Resource group domain SID specified more than once - only first one kept\n"));
|
|
break;
|
|
}
|
|
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Substituting resource group domain SID by %s\n", &Buffer[1]));
|
|
|
|
if ( FALSE == ConvertStringSidToSidA(
|
|
&Buffer[1],
|
|
&ResourceGroupDomainSid ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Unable to convert SID\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
if ( ResourceGroupDomainSid == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating ResourceGroupDomainSid\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
NewValidationInfo.ResourceGroupDomainSid = ResourceGroupDomainSid;
|
|
ResourceGroupDomainSid = NULL;
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
case 'p':
|
|
case 'P': // primary group ID
|
|
|
|
DebugLog((DEB_WARN, "DigestInstrumentRoguePac: Substituting primary group ID by %s\n", &Buffer[1]));
|
|
|
|
NewValidationInfo.PrimaryGroupId = atoi(&Buffer[1]);
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
case 'u':
|
|
case 'U': // User ID
|
|
|
|
DebugLog((DEB_WARN, "DigestInstrumentRoguePac: Substituting user ID by %s\n", &Buffer[1]));
|
|
|
|
NewValidationInfo.UserId = atoi(&Buffer[1]);
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
case 'e':
|
|
case 'E': // Extra SID
|
|
|
|
DebugLog((DEB_WARN, "DigestInstrumentRoguePac: Adding an ExtraSid: %s\n", &Buffer[1]));
|
|
|
|
if ( ExtraSids == NULL )
|
|
{
|
|
NewValidationInfo.ExtraSids = NULL;
|
|
NewValidationInfo.SidCount = 0;
|
|
|
|
ExtraSids = ( PNETLOGON_SID_AND_ATTRIBUTES )HeapAlloc(
|
|
GetProcessHeap(),
|
|
0,
|
|
sizeof( NETLOGON_SID_AND_ATTRIBUTES )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ExtraSids = ( PNETLOGON_SID_AND_ATTRIBUTES )HeapReAlloc(
|
|
GetProcessHeap(),
|
|
0,
|
|
NewValidationInfo.ExtraSids,
|
|
( NewValidationInfo.SidCount + 1 ) * sizeof( NETLOGON_SID_AND_ATTRIBUTES )
|
|
);
|
|
}
|
|
|
|
if ( ExtraSids == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating ExtraSids\n"));
|
|
ExtraSids = NewValidationInfo.ExtraSids;
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Read the actual SID
|
|
//
|
|
|
|
NewValidationInfo.ExtraSids = ExtraSids;
|
|
|
|
if ( FALSE == ConvertStringSidToSidA(
|
|
&Buffer[1],
|
|
&NewValidationInfo.ExtraSids[NewValidationInfo.SidCount].Sid ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Unable to convert SID\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
if ( NewValidationInfo.ExtraSids[NewValidationInfo.SidCount].Sid == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating an extra SID\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
NewValidationInfo.ExtraSids[NewValidationInfo.SidCount].Attributes =
|
|
SE_GROUP_MANDATORY |
|
|
SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED;
|
|
|
|
NewValidationInfo.SidCount += 1;
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
case 'G': // Group ID
|
|
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Adding a GroupId: %s\n", &Buffer[1]));
|
|
|
|
if ( GroupIds == NULL )
|
|
{
|
|
NewValidationInfo.GroupIds = NULL;
|
|
NewValidationInfo.GroupCount = 0;
|
|
|
|
GroupIds = ( PGROUP_MEMBERSHIP )HeapAlloc(
|
|
GetProcessHeap(),
|
|
0,
|
|
sizeof( GROUP_MEMBERSHIP )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
GroupIds = ( PGROUP_MEMBERSHIP )HeapReAlloc(
|
|
GetProcessHeap(),
|
|
0,
|
|
NewValidationInfo.GroupIds,
|
|
( NewValidationInfo.GroupCount + 1 ) * sizeof( GROUP_MEMBERSHIP )
|
|
);
|
|
}
|
|
|
|
if ( GroupIds == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating Group IDs\n"));
|
|
GroupIds = NewValidationInfo.GroupIds;
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Read the actual ID
|
|
//
|
|
|
|
NewValidationInfo.GroupIds = GroupIds;
|
|
NewValidationInfo.GroupIds[NewValidationInfo.GroupCount].RelativeId = atoi(&Buffer[1]);
|
|
NewValidationInfo.GroupIds[NewValidationInfo.GroupCount].Attributes =
|
|
SE_GROUP_MANDATORY |
|
|
SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED;
|
|
NewValidationInfo.GroupCount += 1;
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
case 'R': // Resource groups
|
|
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Adding a ResourceGroupId: %s\n", &Buffer[1]));
|
|
|
|
if ( ResourceGroupIds == NULL )
|
|
{
|
|
NewValidationInfo.ResourceGroupIds = NULL;
|
|
NewValidationInfo.ResourceGroupCount = 0;
|
|
|
|
ResourceGroupIds = ( PGROUP_MEMBERSHIP )HeapAlloc(
|
|
GetProcessHeap(),
|
|
0,
|
|
sizeof( GROUP_MEMBERSHIP )
|
|
);
|
|
}
|
|
else
|
|
{
|
|
ResourceGroupIds = ( PGROUP_MEMBERSHIP )HeapReAlloc(
|
|
GetProcessHeap(),
|
|
0,
|
|
NewValidationInfo.ResourceGroupIds,
|
|
( NewValidationInfo.ResourceGroupCount + 1 ) * sizeof( GROUP_MEMBERSHIP )
|
|
);
|
|
}
|
|
|
|
if ( ResourceGroupIds == NULL )
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Out of memory allocating Resource Group IDs\n"));
|
|
ResourceGroupIds = NewValidationInfo.ResourceGroupIds;
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// Read the actual ID
|
|
//
|
|
|
|
NewValidationInfo.ResourceGroupIds = ResourceGroupIds;
|
|
NewValidationInfo.ResourceGroupIds[NewValidationInfo.ResourceGroupCount].RelativeId = atoi(&Buffer[1]);
|
|
NewValidationInfo.ResourceGroupIds[NewValidationInfo.ResourceGroupCount].Attributes =
|
|
SE_GROUP_MANDATORY |
|
|
SE_GROUP_ENABLED_BY_DEFAULT |
|
|
SE_GROUP_ENABLED;
|
|
NewValidationInfo.ResourceGroupCount += 1;
|
|
PacChanged = TRUE;
|
|
|
|
break;
|
|
|
|
default: // unrecognized
|
|
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Entry \'%c\' unrecognized\n", Buffer[0]));
|
|
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Move to the next line
|
|
//
|
|
|
|
while (*Buffer++ != '\0');
|
|
}
|
|
|
|
if ( !PacChanged )
|
|
{
|
|
DebugLog((DEB_TRACE, "DigestInstrumentRoguePac: Nothing to substitute for %s\n", FullUserSidText));
|
|
Status = STATUS_SUCCESS;
|
|
goto Error;
|
|
}
|
|
|
|
//
|
|
// If resource group IDs were added, indicate that by setting the corresponding flag
|
|
//
|
|
|
|
if ( ResourceGroupIds )
|
|
{
|
|
NewValidationInfo.UserFlags |= LOGON_RESOURCE_GROUPS;
|
|
}
|
|
|
|
//
|
|
// If extra SIDs were added, indicate that by setting the corresponding flag
|
|
//
|
|
|
|
if ( ExtraSids )
|
|
{
|
|
NewValidationInfo.UserFlags |= LOGON_EXTRA_SIDS;
|
|
}
|
|
|
|
//
|
|
// Now build a new pac
|
|
//
|
|
|
|
Status = PAC_InitAndUpdateGroups(
|
|
&NewValidationInfo,
|
|
&ZeroResourceGroups,
|
|
OldPac,
|
|
&NewPac
|
|
);
|
|
|
|
if ( !NT_SUCCESS( Status ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Error 0x%x from PAC_InitAndUpdateGroups\n"));
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
NewPacSize = PAC_GetSize( NewPac );
|
|
|
|
if (!PAC_ReMarshal(NewPac, NewPacSize))
|
|
{
|
|
DsysAssert(!"DigestInstrumentRoguePac: PAC_Remarshal Failed");
|
|
Status = STATUS_UNSUCCESSFUL;
|
|
goto Error;
|
|
}
|
|
|
|
MIDL_user_free( *PacData ); // Free up the OldPac structure
|
|
*PacData = (PBYTE) NewPac;
|
|
NewPac = NULL;
|
|
*PacSize = NewPacSize;
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
Cleanup:
|
|
|
|
MIDL_user_free( OldValidationInfo );
|
|
LocalFree( FullUserSidText );
|
|
LocalFree( ResourceGroupDomainSid );
|
|
LocalFree( LogonDomainId );
|
|
HeapFree( GetProcessHeap(), 0, ResourceGroupIds );
|
|
HeapFree( GetProcessHeap(), 0, GroupIds );
|
|
|
|
if ( ExtraSids )
|
|
{
|
|
for ( ULONG i = 0; i < NewValidationInfo.SidCount; i++ )
|
|
{
|
|
HeapFree( GetProcessHeap(), 0, ExtraSids[i].Sid );
|
|
}
|
|
|
|
HeapFree( GetProcessHeap(), 0, ExtraSids );
|
|
}
|
|
|
|
MIDL_user_free( NewPac );
|
|
|
|
// SafeAllocaFree( Value );
|
|
DigestFreeMemory( Value );
|
|
|
|
DebugLog((DEB_TRACE_FUNC, "DigestInstrumentRoguePac: Leaving Status 0x%x\n", Status));
|
|
|
|
return Status;
|
|
|
|
Error:
|
|
|
|
if ( !NT_SUCCESS( Status ))
|
|
{
|
|
DebugLog((DEB_ERROR, "DigestInstrumentRoguePac: Substitution encountered an error, not performed\n"));
|
|
}
|
|
|
|
if ( !PAC_ReMarshal(OldPac, OldPacSize))
|
|
{
|
|
// PAC_Remarshal Failed
|
|
DsysAssert(!"DigestInstrumentRoguePac: PAC_Remarshal Failed");
|
|
Status = SEC_E_CANNOT_PACK;
|
|
}
|
|
|
|
goto Cleanup;
|
|
}
|
|
|
|
#endif
|
|
|
|
|