1168 lines
29 KiB
C
1168 lines
29 KiB
C
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// ezlogon.c
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// Defines the IAS wrapper around LsaLogonUser
|
|
//
|
|
// MODIFICATION HISTORY
|
|
//
|
|
// 08/15/1998 Original version.
|
|
// 09/09/1998 Fix AV when logon domain doesn't match user domain.
|
|
// 10/02/1998 NULL out handle when LsaLogonUser fails.
|
|
// 10/11/1998 Use SubStatus for STATUS_ACCOUNT_RESTRICTION.
|
|
// 10/22/1998 PIAS_LOGON_HOURS is now a mandatory parameter.
|
|
// 01/28/1999 Remove LogonDomainName check.
|
|
// 04/19/1999 Add IASPurgeTicketCache.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <windows.h>
|
|
#include <lm.h>
|
|
#include <sha.h>
|
|
#include <ntsam.h>
|
|
#include <ntsamp.h>
|
|
#include <dsgetdc.h>
|
|
#include <ntlsa.h>
|
|
#include <kerberos.h>
|
|
#include <windows.h>
|
|
#include <ezlogon.h>
|
|
#include <malloc.h>
|
|
|
|
//#include <iaslsa.h>
|
|
|
|
|
|
//#define NT_RESPONSE_LENGTH 24
|
|
//#define LM_RESPONSE_LENGTH 24
|
|
|
|
|
|
//////////
|
|
// Handle to the IAS Logon Process.
|
|
//////////
|
|
LSA_HANDLE theLogonProcess;
|
|
|
|
//////////
|
|
// The MSV1_0 authentication package.
|
|
//////////
|
|
ULONG theMSV1_0_Package;
|
|
|
|
CONST CHAR LOGON_PROCESS_NAME[] = "CHAP";
|
|
CONST CHAR TOKEN_SOURCE_NAME[TOKEN_SOURCE_LENGTH] = "CHAP";
|
|
|
|
// Number of milliseconds in a week.
|
|
#define MSEC_PER_WEEK (1000 * 60 * 60 * 24 * 7)
|
|
|
|
//////////
|
|
// Misc. global variables used for logons.
|
|
//////////
|
|
LSA_HANDLE theLogonProcess; // The handle for the logon process.
|
|
ULONG theMSV1_0_Package; // The MSV1_0 authentication package.
|
|
ULONG theKerberosPackage; // The Kerberos authentication package.
|
|
STRING theOriginName; // The origin of the logon requests.
|
|
TOKEN_SOURCE theSourceContext; // The source context of the logon requests.
|
|
|
|
//////////
|
|
// Domain names.
|
|
//////////
|
|
WCHAR theAccountDomain [DNLEN + 1]; // Local account domain.
|
|
WCHAR theRegistryDomain[DNLEN + 1]; // Registry override for default domain.
|
|
|
|
//////////
|
|
// SID's
|
|
//////////
|
|
PSID theAccountDomainSid;
|
|
PSID theBuiltinDomainSid;
|
|
|
|
//////////
|
|
// UNC name of the local computer.
|
|
//////////
|
|
WCHAR theLocalServer[CNLEN + 3];
|
|
|
|
SECURITY_QUALITY_OF_SERVICE QOS =
|
|
{
|
|
sizeof(SECURITY_QUALITY_OF_SERVICE), // Length
|
|
SecurityImpersonation, // ImpersonationLevel
|
|
SECURITY_DYNAMIC_TRACKING, // ContextTrackingMode
|
|
FALSE // EffectiveOnly
|
|
};
|
|
|
|
OBJECT_ATTRIBUTES theObjectAttributes =
|
|
{
|
|
sizeof(OBJECT_ATTRIBUTES), // Length
|
|
NULL, // RootDirectory
|
|
NULL, // ObjectName
|
|
0, // Attributes
|
|
NULL, // SecurityDescriptor
|
|
&QOS // SecurityQualityOfService
|
|
};
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// //
|
|
// FUNCTION
|
|
//
|
|
// IASLogonInitialize
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Registers the logon process.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASLogonInitialize( VOID )
|
|
{
|
|
DWORD status;
|
|
BOOLEAN wasEnabled;
|
|
LSA_STRING processName, packageName;
|
|
PPOLICY_ACCOUNT_DOMAIN_INFO padi;
|
|
LSA_OPERATIONAL_MODE opMode;
|
|
DWORD cbData = 0;
|
|
LSA_HANDLE hLsa;
|
|
//////////
|
|
// Enable SE_TCB_PRIVILEGE.
|
|
//////////
|
|
|
|
status = RtlAdjustPrivilege(
|
|
SE_TCB_PRIVILEGE,
|
|
TRUE,
|
|
FALSE,
|
|
&wasEnabled
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto exit; }
|
|
|
|
//////////
|
|
// Register as a logon process.
|
|
//////////
|
|
|
|
RtlInitString(
|
|
&processName,
|
|
LOGON_PROCESS_NAME
|
|
);
|
|
|
|
status = LsaRegisterLogonProcess(
|
|
&processName,
|
|
&theLogonProcess,
|
|
&opMode
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto exit; }
|
|
|
|
//////////
|
|
// Lookup the MSV1_0 authentication package.
|
|
//////////
|
|
|
|
RtlInitString(
|
|
&packageName,
|
|
MSV1_0_PACKAGE_NAME
|
|
);
|
|
|
|
status = LsaLookupAuthenticationPackage(
|
|
theLogonProcess,
|
|
&packageName,
|
|
&theMSV1_0_Package
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto deregister; }
|
|
|
|
//////////
|
|
// Lookup the Kerberos authentication package.
|
|
//////////
|
|
|
|
RtlInitString(
|
|
&packageName,
|
|
MICROSOFT_KERBEROS_NAME_A
|
|
);
|
|
|
|
status = LsaLookupAuthenticationPackage(
|
|
theLogonProcess,
|
|
&packageName,
|
|
&theKerberosPackage
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto deregister; }
|
|
|
|
//////////
|
|
// Initialize the source context.
|
|
//////////
|
|
|
|
memcpy(theSourceContext.SourceName,
|
|
TOKEN_SOURCE_NAME,
|
|
TOKEN_SOURCE_LENGTH);
|
|
status = NtAllocateLocallyUniqueId(
|
|
&theSourceContext.SourceIdentifier
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto deregister; }
|
|
|
|
|
|
/////////
|
|
/// Initialize the account domain and local domain
|
|
////////
|
|
wcscpy(theLocalServer, L"\\\\");
|
|
cbData = CNLEN + 1;
|
|
if (!GetComputerNameW(theLocalServer + 2, &cbData))
|
|
{ return GetLastError(); }
|
|
|
|
|
|
//////////
|
|
// Open a handle to the LSA.
|
|
//////////
|
|
|
|
status = LsaOpenPolicy(
|
|
NULL,
|
|
&theObjectAttributes,
|
|
POLICY_VIEW_LOCAL_INFORMATION,
|
|
&hLsa
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto deregister; }
|
|
|
|
//////////
|
|
// Get the account domain information.
|
|
//////////
|
|
|
|
status = LsaQueryInformationPolicy(
|
|
hLsa,
|
|
PolicyAccountDomainInformation,
|
|
(PVOID*)&padi
|
|
);
|
|
LsaClose(hLsa);
|
|
if (!NT_SUCCESS(status)) { goto deregister; }
|
|
|
|
// Save the domain name.
|
|
wcsncpy(theAccountDomain, padi->DomainName.Buffer, DNLEN);
|
|
_wcsupr(theAccountDomain);
|
|
|
|
return NO_ERROR;
|
|
|
|
deregister:
|
|
LsaDeregisterLogonProcess(theLogonProcess);
|
|
theLogonProcess = NULL;
|
|
//setup the logon domain for the machine
|
|
exit:
|
|
return RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// //
|
|
// FUNCTION
|
|
//
|
|
// IASLogonShutdown
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Deregisters the logon process.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
VOID
|
|
WINAPI
|
|
IASLogonShutdown( VOID )
|
|
{
|
|
LsaDeregisterLogonProcess(theLogonProcess);
|
|
theLogonProcess = NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// //
|
|
// FUNCTION
|
|
//
|
|
// IASInitAuthInfo
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Initializes the fields common to all MSV1_0_LM20* structs.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
VOID
|
|
WINAPI
|
|
IASInitAuthInfo(
|
|
IN PVOID AuthInfo,
|
|
IN DWORD FixedLength,
|
|
IN PCWSTR UserName,
|
|
IN PCWSTR Domain,
|
|
OUT PBYTE* Data
|
|
)
|
|
{
|
|
PMSV1_0_LM20_LOGON logon;
|
|
|
|
// Zero out the fixed data.
|
|
memset(AuthInfo, 0, FixedLength);
|
|
|
|
// Set Data to point just past the fixed struct.
|
|
*Data = FixedLength + (PBYTE)AuthInfo;
|
|
|
|
// This cast is safe since all LM20 structs have the same initial fields.
|
|
logon = (PMSV1_0_LM20_LOGON)AuthInfo;
|
|
|
|
// We always do Network logons.
|
|
logon->MessageType = MsV1_0NetworkLogon;
|
|
|
|
// Copy in the strings common to all logons.
|
|
IASInitUnicodeString(logon->LogonDomainName, *Data, Domain);
|
|
IASInitUnicodeString(logon->UserName, *Data, UserName);
|
|
IASInitUnicodeString(logon->Workstation, *Data, L"");
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// //
|
|
// FUNCTION
|
|
//
|
|
// IASLogonUser
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Wrapper around LsaLogonUser.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASLogonUser(
|
|
IN PVOID AuthInfo,
|
|
IN ULONG AuthInfoLength,
|
|
OUT PMSV1_0_LM20_LOGON_PROFILE *Profile,
|
|
OUT PHANDLE Token
|
|
)
|
|
{
|
|
NTSTATUS status, SubStatus;
|
|
PMSV1_0_LM20_LOGON_PROFILE ProfileBuffer;
|
|
ULONG ProfileBufferLength;
|
|
LUID LogonId;
|
|
QUOTA_LIMITS Quotas;
|
|
|
|
// Make sure the OUT arguments are NULL.
|
|
*Token = NULL;
|
|
ProfileBuffer = NULL;
|
|
|
|
status = LsaLogonUser(
|
|
theLogonProcess,
|
|
&theOriginName,
|
|
Network,
|
|
theMSV1_0_Package,
|
|
AuthInfo,
|
|
AuthInfoLength,
|
|
NULL,
|
|
&theSourceContext,
|
|
&ProfileBuffer,
|
|
&ProfileBufferLength,
|
|
&LogonId,
|
|
Token,
|
|
&Quotas,
|
|
&SubStatus
|
|
);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
// For account restrictions, we can get a more descriptive error
|
|
// from the SubStatus.
|
|
if (status == STATUS_ACCOUNT_RESTRICTION && !NT_SUCCESS(SubStatus))
|
|
{
|
|
status = SubStatus;
|
|
}
|
|
|
|
// Sometimes LsaLogonUser returns an invalid handle value on failure.
|
|
*Token = NULL;
|
|
}
|
|
|
|
if (Profile)
|
|
{
|
|
// Return the profile if requested ...
|
|
*Profile = ProfileBuffer;
|
|
}
|
|
else if (ProfileBuffer)
|
|
{
|
|
// ... otherwise free it.
|
|
LsaFreeReturnBuffer(ProfileBuffer);
|
|
}
|
|
|
|
return RtlNtStatusToDosError(status);
|
|
}
|
|
|
|
//
|
|
// All MSCHAP Related stuff goes here
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Various constants used for MS-CHAP v2
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
UCHAR AuthMagic1[39] =
|
|
{
|
|
0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
|
|
0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
|
|
0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
|
|
0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74
|
|
};
|
|
|
|
UCHAR AuthMagic2[41] =
|
|
{
|
|
0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
|
|
0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
|
|
0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
|
|
0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
|
|
0x6E
|
|
};
|
|
|
|
UCHAR SHSpad1[40] =
|
|
{
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
UCHAR SHSpad2[40] =
|
|
{
|
|
0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
|
|
0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
|
|
0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2,
|
|
0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2
|
|
};
|
|
|
|
UCHAR KeyMagic1[27] =
|
|
{
|
|
0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74,
|
|
0x68, 0x65, 0x20, 0x4D, 0x50, 0x50, 0x45, 0x20, 0x4D,
|
|
0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x4B, 0x65, 0x79
|
|
};
|
|
|
|
UCHAR KeyMagic2[84] =
|
|
{
|
|
0x4F, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6C, 0x69,
|
|
0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2C, 0x20,
|
|
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
|
|
0x65, 0x20, 0x73, 0x65, 0x6E, 0x64, 0x20, 0x6B, 0x65, 0x79,
|
|
0x3B, 0x20, 0x6F, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73,
|
|
0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73, 0x69, 0x64, 0x65,
|
|
0x2C, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
|
|
0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
|
|
0x6B, 0x65, 0x79, 0x2E
|
|
};
|
|
|
|
UCHAR KeyMagic3[84] =
|
|
{
|
|
0x4F, 0x6E, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6C, 0x69,
|
|
0x65, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2C, 0x20,
|
|
0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68,
|
|
0x65, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20,
|
|
0x6B, 0x65, 0x79, 0x3B, 0x20, 0x6F, 0x6E, 0x20, 0x74, 0x68,
|
|
0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x73,
|
|
0x69, 0x64, 0x65, 0x2C, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73,
|
|
0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6E, 0x64, 0x20,
|
|
0x6B, 0x65, 0x79, 0x2E
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FUNCTION
|
|
//
|
|
// IASLogonMSCHAP
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Performs MS-CHAP authentication against the NT SAM database.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASLogonMSCHAP(
|
|
PCWSTR UserName,
|
|
PCWSTR Domain,
|
|
PBYTE Challenge,
|
|
PBYTE NtResponse,
|
|
PBYTE LmResponse,
|
|
PIAS_MSCHAP_PROFILE Profile,
|
|
PHANDLE Token
|
|
)
|
|
{
|
|
DWORD status;
|
|
ULONG authLength;
|
|
PMSV1_0_LM20_LOGON authInfo;
|
|
PBYTE data;
|
|
PMSV1_0_LM20_LOGON_PROFILE logonProfile;
|
|
DWORD len;
|
|
|
|
// Calculate the length of the authentication info.
|
|
authLength = sizeof(MSV1_0_LM20_LOGON) +
|
|
(wcslen(Domain) + wcslen(UserName)) * sizeof(WCHAR) +
|
|
(LmResponse ? LM_RESPONSE_LENGTH : 0) +
|
|
(NtResponse ? NT_RESPONSE_LENGTH : 0);
|
|
|
|
|
|
|
|
__try
|
|
{
|
|
// Allocate a buffer on the stack.
|
|
authInfo = (PMSV1_0_LM20_LOGON)_alloca(authLength);
|
|
|
|
} __except(GetExceptionCode() == STATUS_STACK_OVERFLOW)
|
|
{
|
|
_resetstkoflw();
|
|
}
|
|
|
|
// Initialize the struct.
|
|
IASInitAuthInfo(
|
|
authInfo,
|
|
sizeof(MSV1_0_LM20_LOGON),
|
|
UserName,
|
|
Domain,
|
|
&data
|
|
);
|
|
|
|
/////////
|
|
// Fill in the challenges and responses.
|
|
/////////
|
|
|
|
IASInitFixedArray(
|
|
authInfo->ChallengeToClient,
|
|
Challenge
|
|
);
|
|
|
|
if (NtResponse)
|
|
{
|
|
IASInitOctetString(
|
|
authInfo->CaseSensitiveChallengeResponse,
|
|
data,
|
|
NtResponse,
|
|
NT_RESPONSE_LENGTH
|
|
);
|
|
}
|
|
else
|
|
{
|
|
memset(
|
|
&authInfo->CaseSensitiveChallengeResponse,
|
|
0,
|
|
sizeof(authInfo->CaseSensitiveChallengeResponse)
|
|
);
|
|
}
|
|
|
|
if (LmResponse)
|
|
{
|
|
IASInitOctetString(
|
|
authInfo->CaseInsensitiveChallengeResponse,
|
|
data,
|
|
LmResponse,
|
|
LM_RESPONSE_LENGTH
|
|
);
|
|
}
|
|
else
|
|
{
|
|
memset(
|
|
&authInfo->CaseInsensitiveChallengeResponse,
|
|
0,
|
|
sizeof(authInfo->CaseInsensitiveChallengeResponse)
|
|
);
|
|
}
|
|
|
|
// Set the parameters.
|
|
authInfo->ParameterControl = DEFAULT_PARAMETER_CONTROL;
|
|
|
|
status = IASLogonUser(
|
|
authInfo,
|
|
authLength,
|
|
&logonProfile,
|
|
Token
|
|
);
|
|
|
|
if (status == NO_ERROR)
|
|
{
|
|
Profile->KickOffTime.QuadPart = logonProfile->KickOffTime.QuadPart;
|
|
|
|
// NOTE Workaround for LSA IA64 WINBUG # 126930 6/13/2000 IA64:
|
|
// LsaLogonUser succeeds but returns NULL LogonDomainName.
|
|
|
|
if (logonProfile->LogonDomainName.Buffer)
|
|
{
|
|
wcsncpy(Profile->LogonDomainName,
|
|
logonProfile->LogonDomainName.Buffer,
|
|
DNLEN);
|
|
}
|
|
else
|
|
{
|
|
memset(Profile->LogonDomainName, 0, sizeof(Profile->LogonDomainName));
|
|
}
|
|
|
|
IASInitFixedArray(
|
|
Profile->LanmanSessionKey,
|
|
logonProfile->LanmanSessionKey
|
|
);
|
|
|
|
IASInitFixedArray(
|
|
Profile->UserSessionKey,
|
|
logonProfile->UserSessionKey
|
|
);
|
|
|
|
LsaFreeReturnBuffer(logonProfile);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FUNCTION
|
|
//
|
|
// IASLogonMSCHAPv2
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Performs MS-CHAP v2 authentication.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASLogonMSCHAPv2(
|
|
IN PCWSTR UserName,
|
|
IN PCWSTR Domain,
|
|
IN PCSTR HashUserName,
|
|
IN PBYTE Challenge,
|
|
IN DWORD ChallengeLength,
|
|
IN PBYTE Response,
|
|
IN PBYTE PeerChallenge,
|
|
OUT PIAS_MSCHAP_V2_PROFILE Profile,
|
|
OUT PHANDLE Token
|
|
)
|
|
{
|
|
A_SHA_CTX context;
|
|
BYTE digest[A_SHA_DIGEST_LEN], masterKey[A_SHA_DIGEST_LEN];
|
|
BYTE computedChallenge[MSV1_0_CHALLENGE_LENGTH];
|
|
IAS_MSCHAP_PROFILE v1profile;
|
|
DWORD status;
|
|
|
|
/////////
|
|
// Compute the v2 challenge.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, PeerChallenge, 16);
|
|
A_SHAUpdate(&context, Challenge, ChallengeLength);
|
|
A_SHAUpdate(&context, (PBYTE)HashUserName, strlen(HashUserName));
|
|
A_SHAFinal(&context, digest);
|
|
memcpy(computedChallenge, digest, sizeof(computedChallenge));
|
|
|
|
/////////
|
|
// Authenticate the user.
|
|
/////////
|
|
|
|
status = IASLogonMSCHAP(
|
|
UserName,
|
|
Domain,
|
|
computedChallenge,
|
|
Response,
|
|
NULL,
|
|
&v1profile,
|
|
Token
|
|
);
|
|
if (status != NO_ERROR) { return status; }
|
|
|
|
/////////
|
|
// Generate authenticator response.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, v1profile.UserSessionKey, 16);
|
|
A_SHAUpdate(&context, Response, NT_RESPONSE_LENGTH);
|
|
A_SHAUpdate(&context, AuthMagic1, sizeof(AuthMagic1));
|
|
A_SHAFinal(&context, digest);
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, digest, sizeof(digest));
|
|
A_SHAUpdate(&context, computedChallenge, sizeof(computedChallenge));
|
|
A_SHAUpdate(&context, AuthMagic2, sizeof(AuthMagic2));
|
|
A_SHAFinal(&context, digest);
|
|
|
|
memcpy(Profile->AuthResponse, digest, _AUTHENTICATOR_RESPONSE_LENGTH);
|
|
|
|
/////////
|
|
// Generate master key.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, v1profile.UserSessionKey, 16);
|
|
A_SHAUpdate(&context, Response, NT_RESPONSE_LENGTH);
|
|
A_SHAUpdate(&context, KeyMagic1, sizeof(KeyMagic1));
|
|
A_SHAFinal(&context, masterKey);
|
|
|
|
/////////
|
|
// Generate receive key.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, masterKey, 16);
|
|
A_SHAUpdate(&context, SHSpad1, sizeof(SHSpad1));
|
|
A_SHAUpdate(&context, KeyMagic2, sizeof(KeyMagic2));
|
|
A_SHAUpdate(&context, SHSpad2, sizeof(SHSpad2));
|
|
A_SHAFinal(&context, digest);
|
|
|
|
memcpy(Profile->RecvSessionKey, digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
/////////
|
|
// Generate send key.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, masterKey, 16);
|
|
A_SHAUpdate(&context, SHSpad1, sizeof(SHSpad1));
|
|
A_SHAUpdate(&context, KeyMagic3, sizeof(KeyMagic3));
|
|
A_SHAUpdate(&context, SHSpad2, sizeof(SHSpad2));
|
|
A_SHAFinal(&context, digest);
|
|
|
|
memcpy(Profile->SendSessionKey, digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
/////////
|
|
// Copy the logon domain.
|
|
/////////
|
|
|
|
memcpy(
|
|
Profile->LogonDomainName,
|
|
v1profile.LogonDomainName,
|
|
sizeof(Profile->LogonDomainName)
|
|
);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
DWORD
|
|
WINAPI
|
|
IASGetSendRecvSessionKeys( PBYTE pbUserSessionKey,
|
|
DWORD dwUserSessionKeyLen,
|
|
PBYTE pbResponse,
|
|
DWORD dwResponseLen,
|
|
OUT PBYTE pbSendKey,
|
|
OUT PBYTE pbRecvKey
|
|
)
|
|
{
|
|
DWORD dwRetCode = NO_ERROR;
|
|
A_SHA_CTX context;
|
|
BYTE digest[A_SHA_DIGEST_LEN], masterKey[A_SHA_DIGEST_LEN];
|
|
|
|
/////////
|
|
// Generate master key.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, pbUserSessionKey, dwUserSessionKeyLen);
|
|
A_SHAUpdate(&context, pbResponse, dwResponseLen);
|
|
A_SHAUpdate(&context, KeyMagic1, sizeof(KeyMagic1));
|
|
A_SHAFinal(&context, masterKey);
|
|
|
|
/////////
|
|
// Generate receive key.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, masterKey, 16);
|
|
A_SHAUpdate(&context, SHSpad1, sizeof(SHSpad1));
|
|
A_SHAUpdate(&context, KeyMagic2, sizeof(KeyMagic2));
|
|
A_SHAUpdate(&context, SHSpad2, sizeof(SHSpad2));
|
|
A_SHAFinal(&context, digest);
|
|
|
|
memcpy(pbRecvKey, digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
|
|
/////////
|
|
// Generate send key.
|
|
/////////
|
|
|
|
A_SHAInit(&context);
|
|
A_SHAUpdate(&context, masterKey, 16);
|
|
A_SHAUpdate(&context, SHSpad1, sizeof(SHSpad1));
|
|
A_SHAUpdate(&context, KeyMagic3, sizeof(KeyMagic3));
|
|
A_SHAUpdate(&context, SHSpad2, sizeof(SHSpad2));
|
|
A_SHAFinal(&context, digest);
|
|
|
|
memcpy(pbSendKey, digest, MSV1_0_USER_SESSION_KEY_LENGTH);
|
|
return dwRetCode;
|
|
}
|
|
|
|
|
|
|
|
#if 0
|
|
/////////////////////////////////////////////////////////////////////////////// //
|
|
// FUNCTION
|
|
//
|
|
// IASCheckAccountRestrictions
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Checks whether an account can be used for logon.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASCheckAccountRestrictions(
|
|
IN PLARGE_INTEGER AccountExpires,
|
|
IN PIAS_LOGON_HOURS LogonHours,
|
|
OUT PLARGE_INTEGER SessionTimeout
|
|
)
|
|
{
|
|
LARGE_INTEGER now;
|
|
TIME_ZONE_INFORMATION tzi;
|
|
SYSTEMTIME st;
|
|
DWORD unit;
|
|
LARGE_INTEGER KickoffTime;
|
|
LARGE_INTEGER LogoffTime;
|
|
ULONG LogoffUnitsIntoWeek;
|
|
USHORT i;
|
|
ULONG LogoffMsIntoWeek;
|
|
ULONG MillisecondsPerUnit;
|
|
ULONG DeltaMs;
|
|
ULONG CurrentUnitsIntoWeek;
|
|
LARGE_INTEGER Delta100Ns;
|
|
|
|
_ASSERT(SessionTimeout != NULL);
|
|
SessionTimeout->QuadPart = MAXLONGLONG;
|
|
KickoffTime.QuadPart = MAXLONGLONG;
|
|
LogoffTime.QuadPart = MAXLONGLONG;
|
|
|
|
GetSystemTimeAsFileTime((LPFILETIME)&now);
|
|
|
|
// An expiration time of zero means 'never'.
|
|
if ((AccountExpires->QuadPart != 0) &&
|
|
(AccountExpires->QuadPart < now.QuadPart))
|
|
{
|
|
return ERROR_ACCOUNT_EXPIRED;
|
|
}
|
|
|
|
// If LogonHours is empty, then we're done.
|
|
if (LogonHours->UnitsPerWeek == 0)
|
|
{
|
|
return NO_ERROR;
|
|
}
|
|
|
|
// The LogonHours array does not account for bias.
|
|
switch (GetTimeZoneInformation(&tzi))
|
|
{
|
|
case TIME_ZONE_ID_UNKNOWN:
|
|
case TIME_ZONE_ID_STANDARD:
|
|
// Bias is in minutes.
|
|
now.QuadPart -= 60 * 10000000 * (LONGLONG)tzi.StandardBias;
|
|
break;
|
|
|
|
case TIME_ZONE_ID_DAYLIGHT:
|
|
// Bias is in minutes.
|
|
now.QuadPart -= 60 * 10000000 * (LONGLONG)tzi.DaylightBias;
|
|
break;
|
|
|
|
default:
|
|
return ERROR_INVALID_LOGON_HOURS;
|
|
}
|
|
|
|
FileTimeToSystemTime(
|
|
(LPFILETIME)&now,
|
|
&st
|
|
);
|
|
|
|
// Number of milliseconds into the week.
|
|
unit = st.wMilliseconds +
|
|
st.wSecond * 1000 +
|
|
st.wMinute * 1000 * 60 +
|
|
st.wHour * 1000 * 60 * 60 +
|
|
st.wDayOfWeek * 1000 * 60 * 60 * 24;
|
|
|
|
// Convert this to 'units'.
|
|
unit /= (MSEC_PER_WEEK / (DWORD)LogonHours->UnitsPerWeek);
|
|
|
|
// Test the appropriate bit.
|
|
if ((LogonHours->LogonHours[unit / 8 ] & (1 << (unit % 8))) == 0)
|
|
{
|
|
return ERROR_INVALID_LOGON_HOURS;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Determine the next time that the user is NOT supposed to be logged
|
|
// in, and return that as LogoffTime.
|
|
//
|
|
i = 0;
|
|
LogoffUnitsIntoWeek = unit;
|
|
|
|
do
|
|
{
|
|
++i;
|
|
LogoffUnitsIntoWeek = ( LogoffUnitsIntoWeek + 1 )
|
|
% LogonHours->UnitsPerWeek;
|
|
}
|
|
while ( ( i <= LogonHours->UnitsPerWeek) &&
|
|
( LogonHours->LogonHours[ LogoffUnitsIntoWeek / 8 ] &
|
|
( 0x01 << ( LogoffUnitsIntoWeek % 8 ) ) ) );
|
|
|
|
if ( i > LogonHours->UnitsPerWeek )
|
|
{
|
|
//
|
|
// All times are allowed, so there's no logoff
|
|
// time. Return forever for both LogoffTime and
|
|
// KickoffTime.
|
|
//
|
|
LogoffTime.QuadPart = MAXLONGLONG;
|
|
KickoffTime.QuadPart = MAXLONGLONG;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// LogoffUnitsIntoWeek points at which time unit the
|
|
// user is to log off. Calculate actual time from
|
|
// the unit, and return it.
|
|
//
|
|
// CurrentTimeFields already holds the current
|
|
// time for some time during this week; just adjust
|
|
// to the logoff time during this week and convert
|
|
// to time format.
|
|
//
|
|
|
|
MillisecondsPerUnit = MSEC_PER_WEEK / LogonHours->UnitsPerWeek;
|
|
LogoffMsIntoWeek = MillisecondsPerUnit * LogoffUnitsIntoWeek;
|
|
|
|
if ( LogoffMsIntoWeek < unit )
|
|
{
|
|
DeltaMs = MSEC_PER_WEEK - ( unit - LogoffMsIntoWeek );
|
|
}
|
|
else
|
|
{
|
|
DeltaMs = LogoffMsIntoWeek - unit;
|
|
}
|
|
|
|
Delta100Ns.QuadPart = (LONGLONG) DeltaMs * 10000;
|
|
|
|
LogoffTime.QuadPart = min(now.QuadPart +
|
|
Delta100Ns.QuadPart,
|
|
LogoffTime.QuadPart);
|
|
}
|
|
// Get the minimum of the three values
|
|
KickoffTime.QuadPart = min(LogoffTime.QuadPart, KickoffTime.QuadPart);
|
|
KickoffTime.QuadPart = min(KickoffTime.QuadPart, AccountExpires->QuadPart);
|
|
|
|
// store the result
|
|
SessionTimeout->QuadPart = KickoffTime.QuadPart;
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////// //
|
|
// FUNCTION
|
|
//
|
|
// IASPurgeTicketCache
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Purges the Kerberos ticket cache.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASPurgeTicketCache( VOID )
|
|
{
|
|
KERB_PURGE_TKT_CACHE_REQUEST request;
|
|
NTSTATUS status, subStatus;
|
|
PVOID response;
|
|
ULONG responseLength;
|
|
|
|
memset(&request, 0, sizeof(request));
|
|
request.MessageType = KerbPurgeTicketCacheMessage;
|
|
|
|
response = NULL;
|
|
responseLength = 0;
|
|
subStatus = 0;
|
|
|
|
status = LsaCallAuthenticationPackage(
|
|
theLogonProcess,
|
|
theKerberosPackage,
|
|
&request,
|
|
sizeof(request),
|
|
&response,
|
|
&responseLength,
|
|
&subStatus
|
|
);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
LsaFreeReturnBuffer(response);
|
|
}
|
|
|
|
return RtlNtStatusToDosError(status);
|
|
}
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FUNCTION
|
|
//
|
|
// IASGetDcName
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Wrapper around DsGetDcNameW. Tries to do the right thing with regard
|
|
// to NETBIOS and DNS names.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASGetDcName(
|
|
IN LPCWSTR DomainName,
|
|
IN ULONG Flags,
|
|
OUT PDOMAIN_CONTROLLER_INFOW *DomainControllerInfo
|
|
)
|
|
{
|
|
DWORD status;
|
|
PDOMAIN_CONTROLLER_INFOW dci;
|
|
|
|
if (!(Flags & DS_IS_DNS_NAME)) { Flags |= DS_IS_FLAT_NAME; }
|
|
|
|
status = DsGetDcNameW(
|
|
NULL,
|
|
DomainName,
|
|
NULL,
|
|
NULL,
|
|
Flags,
|
|
DomainControllerInfo
|
|
);
|
|
|
|
if (status == NO_ERROR &&
|
|
!(Flags & DS_IS_DNS_NAME) &&
|
|
((*DomainControllerInfo)->Flags & DS_DS_FLAG))
|
|
{
|
|
// It's an NT5 DC, so we need the DNS name of the server.
|
|
Flags |= DS_RETURN_DNS_NAME;
|
|
|
|
// We always want a cache hit here.
|
|
Flags &= ~(ULONG)DS_FORCE_REDISCOVERY;
|
|
|
|
if (!DsGetDcNameW(
|
|
NULL,
|
|
DomainName,
|
|
NULL,
|
|
NULL,
|
|
Flags,
|
|
&dci
|
|
))
|
|
{
|
|
NetApiBufferFree(*DomainControllerInfo);
|
|
*DomainControllerInfo = dci;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FUNCTION
|
|
//
|
|
// IASChangePassword2
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Performs V2 password change.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASChangePassword2(
|
|
IN PCWSTR UserName,
|
|
IN PCWSTR Domain,
|
|
IN PBYTE OldNtHash,
|
|
IN PBYTE OldLmHash,
|
|
IN PBYTE NtEncPassword,
|
|
IN PBYTE LmEncPassword,
|
|
IN BOOL LmPresent
|
|
)
|
|
{
|
|
DWORD status;
|
|
PDOMAIN_CONTROLLER_INFOW dci;
|
|
UNICODE_STRING uniServerName, uniUserName;
|
|
|
|
//////////
|
|
// Get the name of the DC to connect to.
|
|
//////////
|
|
|
|
if (_wcsicmp(Domain, theAccountDomain) == 0)
|
|
{
|
|
//////////
|
|
// Local domain, so use theLocalServer.
|
|
//////////
|
|
|
|
dci = NULL;
|
|
|
|
RtlInitUnicodeString(
|
|
&uniServerName,
|
|
theLocalServer
|
|
);
|
|
}
|
|
else
|
|
{
|
|
//////////
|
|
// Remote domain, so use IASGetDcName.
|
|
//////////
|
|
|
|
status = IASGetDcName(
|
|
Domain,
|
|
DS_WRITABLE_REQUIRED,
|
|
&dci
|
|
);
|
|
if (status != NO_ERROR) { goto exit; }
|
|
|
|
RtlInitUnicodeString(
|
|
&uniServerName,
|
|
dci->DomainControllerName
|
|
);
|
|
}
|
|
|
|
RtlInitUnicodeString(
|
|
&uniUserName,
|
|
UserName
|
|
);
|
|
|
|
status = SamiChangePasswordUser2(
|
|
&uniServerName,
|
|
&uniUserName,
|
|
(PSAMPR_ENCRYPTED_USER_PASSWORD)NtEncPassword,
|
|
(PENCRYPTED_NT_OWF_PASSWORD)OldNtHash,
|
|
(BOOLEAN)LmPresent,
|
|
(PSAMPR_ENCRYPTED_USER_PASSWORD)LmEncPassword,
|
|
(PENCRYPTED_LM_OWF_PASSWORD)OldLmHash
|
|
);
|
|
status = RtlNtStatusToDosError(status);
|
|
|
|
if (dci)
|
|
{
|
|
NetApiBufferFree(dci);
|
|
}
|
|
|
|
exit:
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FUNCTION
|
|
//
|
|
// IASChangePassword3
|
|
//
|
|
// DESCRIPTION
|
|
//
|
|
// Performs MS-CHAP v2 change password.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
DWORD
|
|
WINAPI
|
|
IASChangePassword3(
|
|
IN PCWSTR UserName,
|
|
IN PCWSTR Domain,
|
|
IN PBYTE EncHash,
|
|
IN PBYTE EncPassword
|
|
)
|
|
{
|
|
return IASChangePassword2(
|
|
UserName,
|
|
Domain,
|
|
EncHash,
|
|
NULL,
|
|
EncPassword,
|
|
NULL,
|
|
FALSE
|
|
);
|
|
} |