671 lines
15 KiB
C
671 lines
15 KiB
C
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (c) 1998, Microsoft Corp. All rights reserved.
|
|
//
|
|
// FILE
|
|
//
|
|
// subauth.c
|
|
//
|
|
// SYNOPSIS
|
|
//
|
|
// Declares the subauthentication and password change notification routines
|
|
// for MD5-CHAP.
|
|
//
|
|
// MODIFICATION HISTORY
|
|
//
|
|
// 09/01/1998 Original version.
|
|
// 11/02/1998 Handle change notifications on a separate thread.
|
|
// 11/03/1998 NewPassword may be NULL.
|
|
// 11/12/1998 Use private heap.
|
|
// Use CreateThread.
|
|
// 03/08/1999 Only store passwords for user accounts.
|
|
// 03/29/1999 Initialize out parameters to NULL when calling
|
|
// SamrQueryInformationUser.
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <ntlsa.h>
|
|
#include <windows.h>
|
|
|
|
#include <samrpc.h>
|
|
#include <samisrv.h>
|
|
#include <lsarpc.h>
|
|
#include <lsaisrv.h>
|
|
|
|
#include <lmcons.h>
|
|
#include <logonmsv.h>
|
|
#include <rasfmsub.h>
|
|
|
|
#include <cleartxt.h>
|
|
#include <md5.h>
|
|
#include <md5port.h>
|
|
#include <rassfmhp.h>
|
|
|
|
// Non-zero if the API is locked.
|
|
static LONG theLock;
|
|
|
|
//////////
|
|
// Macros to lock/unlock the API during intialization.
|
|
//////////
|
|
#define API_LOCK() \
|
|
while (InterlockedExchange(&theLock, 1)) Sleep(5)
|
|
|
|
#define API_UNLOCK() \
|
|
InterlockedExchange(&theLock, 0)
|
|
|
|
// Cached handle to the local account domain.
|
|
SAMPR_HANDLE theAccountDomain;
|
|
|
|
// TRUE if we have a handle to the local account domain.
|
|
static BOOL theConnectFlag;
|
|
|
|
//////////
|
|
// Macro that ensures we have a connection and bails on failure.
|
|
//////////
|
|
#define CHECK_CONNECT() \
|
|
if (!theConnectFlag) { \
|
|
status = ConnectToDomain(); \
|
|
if (!NT_SUCCESS(status)) { return status; } \
|
|
}
|
|
|
|
//////////
|
|
// Initializes the cached handle to the local account domain.
|
|
//////////
|
|
NTSTATUS
|
|
NTAPI
|
|
ConnectToDomain( VOID )
|
|
{
|
|
NTSTATUS status;
|
|
PLSAPR_POLICY_INFORMATION policyInfo;
|
|
SAMPR_HANDLE hServer;
|
|
|
|
API_LOCK();
|
|
|
|
// If we've already been initialized, there's nothing to do.
|
|
if (theConnectFlag)
|
|
{
|
|
status = STATUS_SUCCESS;
|
|
goto exit;
|
|
}
|
|
|
|
//////////
|
|
// Open a handle to the local account domain.
|
|
//////////
|
|
|
|
policyInfo = NULL;
|
|
status = LsaIQueryInformationPolicyTrusted(
|
|
PolicyAccountDomainInformation,
|
|
&policyInfo
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto exit; }
|
|
|
|
status = SamIConnect(
|
|
NULL,
|
|
&hServer,
|
|
0,
|
|
TRUE
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto free_info; }
|
|
|
|
status = SamrOpenDomain(
|
|
hServer,
|
|
0,
|
|
(PRPC_SID)policyInfo->PolicyAccountDomainInfo.DomainSid,
|
|
&theAccountDomain
|
|
);
|
|
|
|
// Did we succeed ?
|
|
if (NT_SUCCESS(status)) { theConnectFlag = TRUE; }
|
|
|
|
SamrCloseHandle(&hServer);
|
|
|
|
free_info:
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyAccountDomainInformation,
|
|
policyInfo
|
|
);
|
|
|
|
exit:
|
|
API_UNLOCK();
|
|
return status;
|
|
}
|
|
|
|
//////////
|
|
// Returns a SAM handle for the local account domain.
|
|
// This handle must not be closed.
|
|
//////////
|
|
NTSTATUS
|
|
NTAPI
|
|
GetDomainHandle(
|
|
OUT SAMPR_HANDLE *DomainHandle
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
|
|
CHECK_CONNECT();
|
|
|
|
*DomainHandle = theAccountDomain;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//////////
|
|
// Returns a SAM handle for the given user.
|
|
// The caller is responsible for closing the returned handle.
|
|
//////////
|
|
NTSTATUS
|
|
NTAPI
|
|
GetUserHandle(
|
|
IN PUNICODE_STRING UserName,
|
|
OUT SAMPR_HANDLE *UserHandle
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
SAMPR_ULONG_ARRAY RidArray;
|
|
SAMPR_ULONG_ARRAY UseArray;
|
|
|
|
CHECK_CONNECT();
|
|
|
|
RidArray.Element = NULL;
|
|
UseArray.Element = NULL;
|
|
|
|
status = SamrLookupNamesInDomain(
|
|
theAccountDomain,
|
|
1,
|
|
(PRPC_UNICODE_STRING)UserName,
|
|
&RidArray,
|
|
&UseArray
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto exit; }
|
|
|
|
if (UseArray.Element[0] != SidTypeUser)
|
|
{
|
|
status = STATUS_NONE_MAPPED;
|
|
goto free_arrays;
|
|
}
|
|
|
|
status = SamrOpenUser(
|
|
theAccountDomain,
|
|
0,
|
|
RidArray.Element[0],
|
|
UserHandle
|
|
);
|
|
|
|
free_arrays:
|
|
SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
|
|
SamIFree_SAMPR_ULONG_ARRAY( &RidArray );
|
|
|
|
exit:
|
|
return status;
|
|
}
|
|
|
|
/////////
|
|
// Process an MD5-CHAP authentication.
|
|
/////////
|
|
NTSTATUS
|
|
NTAPI
|
|
ProcessMD5ChapAuthentication(
|
|
IN SAM_HANDLE UserHandle,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
IN UCHAR ChallengeId,
|
|
IN DWORD ChallengeLength,
|
|
IN PUCHAR Challenge,
|
|
IN PUCHAR Response
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
UNICODE_STRING uniPwd;
|
|
ANSI_STRING ansiPwd;
|
|
MD5_CTX context;
|
|
|
|
/////////
|
|
// Retrieve the cleartext password.
|
|
/////////
|
|
|
|
status = RetrieveCleartextPassword(
|
|
UserHandle,
|
|
UserAll,
|
|
&uniPwd
|
|
);
|
|
if (status != STATUS_SUCCESS) { return status; }
|
|
|
|
//////////
|
|
// Convert the password to ANSI.
|
|
//////////
|
|
|
|
status = RtlUnicodeStringToAnsiString(
|
|
&ansiPwd,
|
|
&uniPwd,
|
|
TRUE
|
|
);
|
|
|
|
// We're through with the Unicode password.
|
|
RtlFreeUnicodeString(&uniPwd);
|
|
|
|
if (!NT_SUCCESS(status)) { return STATUS_WRONG_PASSWORD; }
|
|
|
|
//////////
|
|
// Compute the correct response.
|
|
//////////
|
|
|
|
MD5Init(&context);
|
|
MD5Update(&context, &ChallengeId, 1);
|
|
MD5Update(&context, (PBYTE)ansiPwd.Buffer, ansiPwd.Length);
|
|
MD5Update(&context, Challenge, ChallengeLength);
|
|
MD5Final(&context);
|
|
|
|
// We're through with the ANSI password.
|
|
RtlFreeAnsiString(&ansiPwd);
|
|
|
|
//////////
|
|
// Does the actual response match the correct response ?
|
|
//////////
|
|
|
|
if (memcmp(context.digest, Response, 16) == 0)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
return STATUS_WRONG_PASSWORD;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
MD5ChapSubAuthentication(
|
|
IN SAM_HANDLE UserHandle,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
IN PRAS_SUBAUTH_INFO RasInfo
|
|
)
|
|
{
|
|
MD5CHAP_SUBAUTH_INFO* info = (MD5CHAP_SUBAUTH_INFO*)(RasInfo->Data);
|
|
return ProcessMD5ChapAuthentication(
|
|
UserHandle,
|
|
UserAll,
|
|
info->uchChallengeId,
|
|
sizeof(info->uchChallenge),
|
|
info->uchChallenge,
|
|
info->uchResponse
|
|
);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NTAPI
|
|
MD5ChapExSubAuthentication(
|
|
IN SAM_HANDLE UserHandle,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
IN PRAS_SUBAUTH_INFO RasInfo
|
|
)
|
|
{
|
|
MD5CHAP_EX_SUBAUTH_INFO* info = (MD5CHAP_EX_SUBAUTH_INFO*)(RasInfo->Data);
|
|
DWORD challengeLength = RasInfo->DataSize -
|
|
sizeof(MD5CHAP_EX_SUBAUTH_INFO) + 1;
|
|
return ProcessMD5ChapAuthentication(
|
|
UserHandle,
|
|
UserAll,
|
|
info->uchChallengeId,
|
|
challengeLength,
|
|
info->uchChallenge,
|
|
info->uchResponse
|
|
);
|
|
}
|
|
|
|
//////////
|
|
// Entry point for the subauthentication DLL.
|
|
//////////
|
|
NTSTATUS
|
|
NTAPI
|
|
Msv1_0SubAuthenticationRoutine(
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN ULONG Flags,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
OUT PULONG WhichFields,
|
|
OUT PULONG UserFlags,
|
|
OUT PBOOLEAN Authoritative,
|
|
OUT PLARGE_INTEGER LogoffTime,
|
|
OUT PLARGE_INTEGER KickoffTime
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
LARGE_INTEGER logonTime;
|
|
PNETLOGON_NETWORK_INFO logonInfo;
|
|
PRAS_SUBAUTH_INFO rasInfo;
|
|
MD5CHAP_SUBAUTH_INFO *chapInfo;
|
|
PWSTR password;
|
|
UNICODE_STRING uniPassword;
|
|
SAMPR_HANDLE hUser;
|
|
|
|
/////////
|
|
// Initialize the out parameters.
|
|
/////////
|
|
|
|
*WhichFields = 0;
|
|
*UserFlags = 0;
|
|
*Authoritative = TRUE;
|
|
|
|
/////////
|
|
// Check some basic restrictions.
|
|
/////////
|
|
|
|
if (LogonLevel != NetlogonNetworkInformation)
|
|
{
|
|
return STATUS_INVALID_INFO_CLASS;
|
|
}
|
|
|
|
if (UserAll->UserAccountControl & USER_ACCOUNT_DISABLED)
|
|
{
|
|
return STATUS_ACCOUNT_DISABLED;
|
|
}
|
|
|
|
NtQuerySystemTime(&logonTime);
|
|
|
|
if (UserAll->AccountExpires.QuadPart != 0 &&
|
|
UserAll->AccountExpires.QuadPart <= logonTime.QuadPart)
|
|
{
|
|
return STATUS_ACCOUNT_EXPIRED;
|
|
}
|
|
|
|
if (UserAll->PasswordMustChange.QuadPart <= logonTime.QuadPart)
|
|
{
|
|
return UserAll->PasswordLastSet.QuadPart ? STATUS_PASSWORD_EXPIRED
|
|
: STATUS_PASSWORD_MUST_CHANGE;
|
|
}
|
|
|
|
/////////
|
|
// Extract the MD5CHAP_SUBAUTH_INFO struct.
|
|
/////////
|
|
|
|
logonInfo = (PNETLOGON_NETWORK_INFO)LogonInformation;
|
|
rasInfo = (PRAS_SUBAUTH_INFO)logonInfo->NtChallengeResponse.Buffer;
|
|
|
|
if (rasInfo == NULL ||
|
|
logonInfo->NtChallengeResponse.Length < sizeof(RAS_SUBAUTH_INFO) ||
|
|
rasInfo->ProtocolType != RAS_SUBAUTH_PROTO_MD5CHAP ||
|
|
rasInfo->DataSize != sizeof(MD5CHAP_SUBAUTH_INFO))
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
chapInfo = (MD5CHAP_SUBAUTH_INFO*)rasInfo->Data;
|
|
|
|
/////////
|
|
// Open a handle to the user object.
|
|
/////////
|
|
|
|
status = GetUserHandle(
|
|
&(logonInfo->Identity.UserName),
|
|
&hUser
|
|
);
|
|
if (status != NO_ERROR) { return status; }
|
|
|
|
/////////
|
|
// Verify the MD5-CHAP password.
|
|
/////////
|
|
|
|
status = ProcessMD5ChapAuthentication(
|
|
hUser,
|
|
UserAll,
|
|
chapInfo->uchChallengeId,
|
|
sizeof(chapInfo->uchChallenge),
|
|
chapInfo->uchChallenge,
|
|
chapInfo->uchResponse
|
|
);
|
|
if (status != NO_ERROR) { goto close_user; }
|
|
|
|
/////////
|
|
// Check account restrictions.
|
|
/////////
|
|
|
|
status = SamIAccountRestrictions(
|
|
hUser,
|
|
NULL,
|
|
NULL,
|
|
&UserAll->LogonHours,
|
|
LogoffTime,
|
|
KickoffTime
|
|
);
|
|
|
|
close_user:
|
|
SamrCloseHandle(&hUser);
|
|
|
|
return status;
|
|
}
|
|
|
|
/////////
|
|
// Info needed to process a change notification.
|
|
/////////
|
|
typedef struct _PWD_CHANGE_INFO {
|
|
ULONG RelativeId;
|
|
WCHAR NewPassword[1];
|
|
} PWD_CHANGE_INFO, *PPWD_CHANGE_INFO;
|
|
|
|
/////////
|
|
// Start routine for notification worker thread.
|
|
/////////
|
|
DWORD
|
|
WINAPI
|
|
PasswordChangeNotifyWorker(
|
|
IN PPWD_CHANGE_INFO ChangeInfo
|
|
)
|
|
{
|
|
NTSTATUS status;
|
|
SAMPR_HANDLE hUser;
|
|
PUSER_CONTROL_INFORMATION uci;
|
|
ULONG accountControl;
|
|
USER_PARAMETERS_INFORMATION *oldInfo, newInfo;
|
|
BOOL cleartextAllowed;
|
|
PWSTR oldUserParms, newUserParms;
|
|
|
|
//////////
|
|
// Ensure we're connected to the SAM domain.
|
|
//////////
|
|
|
|
if (!theConnectFlag)
|
|
{
|
|
status = ConnectToDomain();
|
|
if (!NT_SUCCESS(status)) { goto exit; }
|
|
}
|
|
|
|
//////////
|
|
// Retrieve the UserParameters
|
|
//////////
|
|
|
|
status = SamrOpenUser(
|
|
theAccountDomain,
|
|
0,
|
|
ChangeInfo->RelativeId,
|
|
&hUser
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto exit; }
|
|
|
|
uci = NULL;
|
|
status = SamrQueryInformationUser(
|
|
hUser,
|
|
UserControlInformation,
|
|
(PSAMPR_USER_INFO_BUFFER*)&uci
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto close_user; }
|
|
|
|
// Save the info ...
|
|
accountControl = uci->UserAccountControl;
|
|
|
|
// ... and free the buffer.
|
|
SamIFree_SAMPR_USER_INFO_BUFFER(
|
|
(PSAMPR_USER_INFO_BUFFER)uci,
|
|
UserControlInformation
|
|
);
|
|
|
|
// We're only interested in normal accounts.
|
|
if (!(accountControl & USER_NORMAL_ACCOUNT)) { goto close_user; }
|
|
|
|
oldInfo = NULL;
|
|
status = SamrQueryInformationUser(
|
|
hUser,
|
|
UserParametersInformation,
|
|
(PSAMPR_USER_INFO_BUFFER*)&oldInfo
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto close_user; }
|
|
|
|
//////////
|
|
// Make a null-terminated copy.
|
|
//////////
|
|
|
|
oldUserParms = (PWSTR)
|
|
RtlAllocateHeap(
|
|
RasSfmHeap(),
|
|
0,
|
|
oldInfo->Parameters.Length + sizeof(WCHAR)
|
|
);
|
|
if (oldUserParms == NULL)
|
|
{
|
|
status = STATUS_NO_MEMORY;
|
|
goto free_user_info;
|
|
}
|
|
|
|
memcpy(
|
|
oldUserParms,
|
|
oldInfo->Parameters.Buffer,
|
|
oldInfo->Parameters.Length
|
|
);
|
|
|
|
oldUserParms[oldInfo->Parameters.Length / sizeof(WCHAR)] = L'\0';
|
|
|
|
//////////
|
|
// Should we store the cleartext password in UserParameters?
|
|
//////////
|
|
|
|
status = IsCleartextEnabled(
|
|
hUser,
|
|
&cleartextAllowed
|
|
);
|
|
if (!NT_SUCCESS(status)) { goto free_user_parms; }
|
|
|
|
newUserParms = NULL;
|
|
|
|
if (cleartextAllowed)
|
|
{
|
|
// We either set the new password ...
|
|
status = IASParmsSetUserPassword(
|
|
oldUserParms,
|
|
ChangeInfo->NewPassword,
|
|
&newUserParms
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// ... or we erase the old one.
|
|
status = IASParmsClearUserPassword(
|
|
oldUserParms,
|
|
&newUserParms
|
|
);
|
|
}
|
|
|
|
// Write the UserParameters back to SAM if necessary.
|
|
if (NT_SUCCESS(status) && newUserParms != NULL)
|
|
{
|
|
newInfo.Parameters.Length = (USHORT)(sizeof(WCHAR) * (lstrlenW(newUserParms) + 1));
|
|
newInfo.Parameters.MaximumLength = newInfo.Parameters.Length;
|
|
newInfo.Parameters.Buffer = newUserParms;
|
|
|
|
status = SamrSetInformationUser(
|
|
hUser,
|
|
UserParametersInformation,
|
|
(PSAMPR_USER_INFO_BUFFER)&newInfo
|
|
);
|
|
|
|
IASParmsFreeUserParms(newUserParms);
|
|
}
|
|
|
|
free_user_parms:
|
|
RtlFreeHeap(
|
|
RasSfmHeap(),
|
|
0,
|
|
oldUserParms
|
|
);
|
|
|
|
free_user_info:
|
|
SamIFree_SAMPR_USER_INFO_BUFFER(
|
|
(PSAMPR_USER_INFO_BUFFER)oldInfo,
|
|
UserParametersInformation
|
|
);
|
|
|
|
close_user:
|
|
SamrCloseHandle(&hUser);
|
|
|
|
exit:
|
|
RtlFreeHeap(
|
|
RasSfmHeap(),
|
|
0,
|
|
ChangeInfo
|
|
);
|
|
|
|
return status;
|
|
}
|
|
|
|
//////////
|
|
// Password change DLL entry point.
|
|
//////////
|
|
NTSTATUS
|
|
NTAPI
|
|
PasswordChangeNotify(
|
|
IN PUNICODE_STRING UserName,
|
|
IN ULONG RelativeId,
|
|
IN PUNICODE_STRING NewPassword
|
|
)
|
|
{
|
|
ULONG length;
|
|
PPWD_CHANGE_INFO info;
|
|
HANDLE hWorker;
|
|
DWORD threadId;
|
|
|
|
// Calculate the length of the new password.
|
|
length = NewPassword ? NewPassword->Length : 0;
|
|
|
|
// Allocate the PWD_CHANGE_INFO struct.
|
|
info = (PPWD_CHANGE_INFO)
|
|
RtlAllocateHeap(
|
|
RasSfmHeap(),
|
|
0,
|
|
sizeof(PWD_CHANGE_INFO) + length
|
|
);
|
|
if (info == NULL) { return STATUS_NO_MEMORY; }
|
|
|
|
// Save the RelativeId.
|
|
info->RelativeId = RelativeId;
|
|
|
|
// Save the NewPassword.
|
|
if (length) { memcpy(info->NewPassword, NewPassword->Buffer, length); }
|
|
|
|
// Make sure it's null-terminated.
|
|
info->NewPassword[length / sizeof(WCHAR)] = L'\0';
|
|
|
|
// Create a worker thread.
|
|
hWorker = CreateThread(
|
|
NULL,
|
|
0,
|
|
PasswordChangeNotifyWorker,
|
|
info,
|
|
0,
|
|
&threadId
|
|
);
|
|
|
|
if (hWorker)
|
|
{
|
|
CloseHandle(hWorker);
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
RtlFreeHeap(
|
|
RasSfmHeap(),
|
|
0,
|
|
info
|
|
);
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|