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.
1221 lines
35 KiB
1221 lines
35 KiB
/*++
|
|
|
|
Copyright (c) Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
rassfm.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the subauthentication needed by the various RAS
|
|
protocols (ARAP, MD5 etc.).
|
|
It is adapted from the subauthentication sample from CliffV.
|
|
|
|
Author:
|
|
|
|
Shirish Koti 28-Feb-97
|
|
|
|
Revisions:
|
|
|
|
06/02/97 Steve Cobb, Added MD5-CHAP support
|
|
|
|
--*/
|
|
|
|
|
|
#include <nt.h>
|
|
#include <ntrtl.h>
|
|
#include <nturtl.h>
|
|
#include <ntsam.h>
|
|
#include <windows.h>
|
|
#include <ntmsv1_0.h>
|
|
#include <crypt.h>
|
|
#include <samrpc.h>
|
|
#include <lsarpc.h>
|
|
#define SECURITY_WIN32
|
|
#define SECURITY_PACKAGE
|
|
#include <security.h>
|
|
#include <secint.h>
|
|
#include <samisrv.h>
|
|
#include <lsaisrv.h>
|
|
#include <ntlsa.h>
|
|
#include <lmcons.h>
|
|
#include <logonmsv.h>
|
|
#include <macfile.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "rasman.h"
|
|
#include "rasfmsub.h"
|
|
#include "arapio.h"
|
|
#include "md5port.h"
|
|
#include "cleartxt.h"
|
|
|
|
#include "rassfm.h"
|
|
|
|
// Private heap used by the RASSFM module.
|
|
PVOID RasSfmPrivateHeap;
|
|
|
|
// Empty OWF password.
|
|
const NT_OWF_PASSWORD EMPTY_OWF_PASSWORD =
|
|
{
|
|
{
|
|
{ '\x31', '\xD6', '\xCF', '\xE0', '\xD1', '\x6A', '\xE9', '\x31' },
|
|
{ '\xB7', '\x3C', '\x59', '\xD7', '\xE0', '\xC0', '\x89', '\xC0' }
|
|
}
|
|
};
|
|
|
|
BOOL
|
|
RasSfmSubAuthEntry(
|
|
IN HANDLE hinstDll,
|
|
IN DWORD fdwReason,
|
|
IN LPVOID lpReserved
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Entry point into the dll
|
|
|
|
Arguments:
|
|
hinstDll - handle
|
|
fdwReason - why the entry
|
|
lpReserved -
|
|
|
|
Return Value:
|
|
|
|
TRUE
|
|
|
|
--*/
|
|
{
|
|
switch (fdwReason)
|
|
{
|
|
case DLL_PROCESS_ATTACH:
|
|
|
|
RasSfmPrivateHeap = RtlCreateHeap(
|
|
HEAP_GROWABLE,
|
|
NULL,
|
|
0,
|
|
0,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
DisableThreadLibraryCalls( hinstDll );
|
|
|
|
InitializeCriticalSection( &ArapDesLock );
|
|
|
|
break;
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
|
|
RtlDestroyHeap(RasSfmPrivateHeap);
|
|
|
|
break;
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
Msv1_0SubAuthenticationRoutineEx(
|
|
IN NETLOGON_LOGON_INFO_CLASS LogonLevel,
|
|
IN PVOID LogonInformation,
|
|
IN ULONG Flags,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
IN SAM_HANDLE UserHandle,
|
|
IN OUT PMSV1_0_VALIDATION_INFO ValidationInfo,
|
|
OUT PULONG ActionsPerformed
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the routine called in by the MSV package (if it was requested that
|
|
the subauth package be called in), as a result of calling LsaLogonUser.
|
|
This routine does RAS protocol specific authentication.
|
|
|
|
In case of both ARAP and MD5 CHAP, the only thing we do in this routine is
|
|
actual password authentication and leave everything else (logon hours, pwd
|
|
expiry etc.) to the MSV package.
|
|
|
|
Arguments:
|
|
|
|
LogonLevel - we don't use it
|
|
LogonInformation - contains the info our client side gave to us
|
|
Flags - we don't use this flag
|
|
UserAll - we get password creation,expiry times from this
|
|
UserHandle - we get the clear text password using this
|
|
ValidationInfo - set return info
|
|
ActionsPerformed - we always set this to NTLM_SUBAUTH_PASSWORD to indicate
|
|
to the package that all we did was check for password
|
|
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if there was no error.
|
|
|
|
STATUS_WRONG_PASSWORD: The password was invalid.
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS status;
|
|
PNETLOGON_NETWORK_INFO pLogonNetworkInfo;
|
|
PRAS_SUBAUTH_INFO pRasSubAuthInfo;
|
|
USHORT bufferLength;
|
|
|
|
pLogonNetworkInfo = (PNETLOGON_NETWORK_INFO) LogonInformation;
|
|
|
|
pRasSubAuthInfo = (PRAS_SUBAUTH_INFO)
|
|
pLogonNetworkInfo->NtChallengeResponse.Buffer;
|
|
bufferLength = pLogonNetworkInfo->NtChallengeResponse.Length;
|
|
|
|
if ( pRasSubAuthInfo == NULL || (bufferLength < sizeof(RAS_SUBAUTH_INFO)) )
|
|
{
|
|
return STATUS_INVALID_PARAMETER;
|
|
}
|
|
|
|
switch (pRasSubAuthInfo->ProtocolType)
|
|
{
|
|
//
|
|
// do the ARAP-specific authentication
|
|
//
|
|
case RAS_SUBAUTH_PROTO_ARAP:
|
|
|
|
status = ArapSubAuthentication(pLogonNetworkInfo,
|
|
UserAll,
|
|
UserHandle,
|
|
ValidationInfo);
|
|
|
|
|
|
ValidationInfo->Authoritative = TRUE;
|
|
|
|
*ActionsPerformed = MSV1_0_SUBAUTH_PASSWORD;
|
|
|
|
break;
|
|
|
|
|
|
// MD5 CHAP subauthentication.
|
|
//
|
|
case RAS_SUBAUTH_PROTO_MD5CHAP:
|
|
{
|
|
// Subauthenticate the user account.
|
|
//
|
|
status = MD5ChapSubAuthentication(
|
|
UserHandle,
|
|
UserAll,
|
|
pRasSubAuthInfo,
|
|
bufferLength
|
|
);
|
|
|
|
// No validation information is returned. Might want to return a
|
|
// session key here in the future.
|
|
//
|
|
ValidationInfo->WhichFields = 0;
|
|
ValidationInfo->Authoritative = TRUE;
|
|
|
|
*ActionsPerformed = MSV1_0_SUBAUTH_PASSWORD;
|
|
break;
|
|
}
|
|
|
|
// MD5 CHAP Ex subauthentication.
|
|
//
|
|
case RAS_SUBAUTH_PROTO_MD5CHAP_EX:
|
|
{
|
|
// Subauthenticate the user account.
|
|
//
|
|
status = MD5ChapExSubAuthentication(
|
|
UserHandle,
|
|
UserAll,
|
|
pRasSubAuthInfo,
|
|
bufferLength
|
|
);
|
|
|
|
// No validation information is returned. Might want to return a
|
|
// session key here in the future.
|
|
//
|
|
ValidationInfo->WhichFields = 0;
|
|
ValidationInfo->Authoritative = TRUE;
|
|
|
|
*ActionsPerformed = MSV1_0_SUBAUTH_PASSWORD;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
|
|
DBGPRINT("RASSFM subauth pkg: bad protocol type %d\n",
|
|
pRasSubAuthInfo->ProtocolType);
|
|
|
|
status = STATUS_WRONG_PASSWORD;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return(status);
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
Msv1_0SubAuthenticationRoutineGeneric(
|
|
IN PVOID SubmitBuffer,
|
|
IN ULONG SubmitBufferLength,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PVOID *ReturnBuffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the routine called in by the MSV package (if it was requested that
|
|
the subauth package be called in), as a result of calling
|
|
LsaCallAuthenticationPackage. This routine does RAS protocol specific
|
|
functions.
|
|
|
|
In case of ARAP, we implement change password functionality in this routine.
|
|
|
|
Arguments:
|
|
|
|
SubmitBuffer - the buffer containing password change info
|
|
SubmitBufferLength - length of this buffer
|
|
ReturnBufferLength - we don't use it
|
|
ReturnBuffer - we don't use it
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if there was no error.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
PARAP_SUBAUTH_REQ pArapSubAuthInfo;
|
|
PUNICODE_STRING pUserName;
|
|
PUNICODE_STRING pDomainName;
|
|
PRAS_SUBAUTH_INFO pRasSubAuthInfo;
|
|
NTSTATUS status;
|
|
|
|
|
|
|
|
|
|
pRasSubAuthInfo = (PRAS_SUBAUTH_INFO)SubmitBuffer;
|
|
|
|
switch (pRasSubAuthInfo->ProtocolType)
|
|
{
|
|
//
|
|
// do the ARAP-specific authentication
|
|
//
|
|
case RAS_SUBAUTH_PROTO_ARAP:
|
|
|
|
status = ArapChangePassword(pRasSubAuthInfo,
|
|
ReturnBufferLength,
|
|
ReturnBuffer);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
DBGPRINT("Msv1_0SubAuthenticationRoutineGeneric: bad protocol type\n");
|
|
|
|
ASSERT(0);
|
|
|
|
status = STATUS_UNSUCCESSFUL;
|
|
|
|
}
|
|
|
|
return(status);
|
|
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
ArapSubAuthentication(
|
|
IN OUT PNETLOGON_NETWORK_INFO pLogonNetworkInfo,
|
|
IN PUSER_ALL_INFORMATION UserAll,
|
|
IN SAM_HANDLE UserHandle,
|
|
IN OUT PMSV1_0_VALIDATION_INFO ValidationInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This is the routine that does the actuall authentication. It retrieves
|
|
the clear-text password, does the DES encryption of the challenge and
|
|
compares with what the Mac client has sent to determine if authentication
|
|
succeeded. Also, it returns a response to the challenge sent to us by
|
|
the Mac.
|
|
|
|
Arguments:
|
|
|
|
pLogonNetworkInfo - ptr to the NETLOGON_NETWORK_INFO struct
|
|
UserAll - ptr to the USER_ALL_INFORMATION struct
|
|
UserHandle - sam handle for the user
|
|
ValidationInfo - what we return to our caller
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if authentication succeeded, appropriate error otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
NTSTATUS status;
|
|
PARAP_SUBAUTH_REQ pArapSubAuthInfo;
|
|
ARAP_CHALLENGE Challenge;
|
|
PARAP_SUBAUTH_RESP pArapResp;
|
|
PUNICODE_STRING pUserName;
|
|
PUNICODE_STRING pDomainName;
|
|
UNICODE_STRING UnicodePassword;
|
|
ANSI_STRING AnsiPassword;
|
|
PRAS_SUBAUTH_INFO pRasSubAuthInfo;
|
|
DWORD Response1;
|
|
DWORD Response2;
|
|
UCHAR ClearTextPassword[64];
|
|
BOOLEAN fCallerIsArap;
|
|
|
|
|
|
pRasSubAuthInfo = (PRAS_SUBAUTH_INFO)
|
|
pLogonNetworkInfo->NtChallengeResponse.Buffer;
|
|
|
|
pArapSubAuthInfo = (PARAP_SUBAUTH_REQ)&pRasSubAuthInfo->Data[0];
|
|
|
|
//
|
|
// NOTE: this is a quick-n-dirty workaround to returning a clean buffer
|
|
// We use the KickoffTime,LogoffTime and SessionKey fields of ValidationInfo
|
|
// The SessionKey is a 16 byte field. We use only 12 bytes, but be careful
|
|
// not to exceed it!!
|
|
ASSERT(sizeof(ARAP_SUBAUTH_RESP) <= sizeof(USER_SESSION_KEY));
|
|
|
|
//
|
|
// store the password create and expiry date: we need to send it to Mac
|
|
//
|
|
ValidationInfo->KickoffTime = UserAll->PasswordLastSet;
|
|
ValidationInfo->LogoffTime = UserAll->PasswordMustChange;
|
|
|
|
ValidationInfo->WhichFields = ( MSV1_0_VALIDATION_LOGOFF_TIME |
|
|
MSV1_0_VALIDATION_KICKOFF_TIME |
|
|
MSV1_0_VALIDATION_SESSION_KEY |
|
|
MSV1_0_VALIDATION_USER_FLAGS );
|
|
|
|
ValidationInfo->UserFlags = 0;
|
|
|
|
pArapResp = (PARAP_SUBAUTH_RESP)&ValidationInfo->SessionKey;
|
|
|
|
if ((pArapSubAuthInfo->PacketType != ARAP_SUBAUTH_LOGON_PKT) &&
|
|
(pArapSubAuthInfo->PacketType != SFM_SUBAUTH_LOGON_PKT))
|
|
{
|
|
DBGPRINT("ARAPSubAuth: PacketType is not ARAP, returning failure\n");
|
|
pArapResp->Result = ARAPERR_BAD_FORMAT;
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
fCallerIsArap = (pArapSubAuthInfo->PacketType == ARAP_SUBAUTH_LOGON_PKT);
|
|
|
|
//
|
|
// presently no one calls with fGuestLogon. If in future, we need Guest logon,
|
|
// then we will have to check if (Flags & MSV1_0_GUEST_LOGON) is set to allow
|
|
// Guest logon. Right now, we fail the request.
|
|
//
|
|
if (pArapSubAuthInfo->Logon.fGuestLogon)
|
|
{
|
|
DBGPRINT("ARAPSubAuth: how come guest logon is reaching here??\n");
|
|
ASSERT(0);
|
|
pArapResp->Result = ARAPERR_AUTH_FAILURE;
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
pUserName = &pLogonNetworkInfo->Identity.UserName;
|
|
pDomainName = &pLogonNetworkInfo->Identity.LogonDomainName;
|
|
|
|
|
|
status = RetrieveCleartextPassword(UserHandle, UserAll, &UnicodePassword);
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ARAPSubAuth: RetrieveCleartextPassword failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
SecureZeroMemory(ClearTextPassword, sizeof(ClearTextPassword));
|
|
|
|
AnsiPassword.Length = AnsiPassword.MaximumLength = sizeof(ClearTextPassword);
|
|
AnsiPassword.Buffer = ClearTextPassword;
|
|
|
|
status = RtlUnicodeStringToAnsiString( &AnsiPassword, &UnicodePassword, FALSE );
|
|
|
|
SecureZeroMemory(UnicodePassword.Buffer, UnicodePassword.Length);
|
|
|
|
// we don't need the unicode password anymore
|
|
RtlFreeUnicodeString(&UnicodePassword);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DBGPRINT("ARAPSubAuth: RtlUnicodeStringToAnsiString failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
//
|
|
// Mac sends challenge to us as well: compute the response
|
|
//
|
|
Challenge.high = pArapSubAuthInfo->Logon.MacChallenge1;
|
|
Challenge.low = pArapSubAuthInfo->Logon.MacChallenge2;
|
|
|
|
|
|
EnterCriticalSection( &ArapDesLock );
|
|
|
|
if (fCallerIsArap)
|
|
{
|
|
DoDesInit(ClearTextPassword, TRUE);
|
|
}
|
|
|
|
//
|
|
// RandNum expects the low-bit of each byte (of password) to be cleared
|
|
// during key-generation
|
|
//
|
|
else
|
|
{
|
|
DoDesInit(ClearTextPassword, FALSE);
|
|
}
|
|
|
|
DoTheDESEncrypt((PBYTE)&Challenge);
|
|
|
|
|
|
//
|
|
// copy the response that needs to be sent back to the Mac
|
|
//
|
|
pArapResp->Response = Challenge;
|
|
|
|
|
|
//
|
|
// encrypt the challenge that we sent to find out if this Mac is honest
|
|
//
|
|
Challenge.high = pArapSubAuthInfo->Logon.NTChallenge1;
|
|
Challenge.low = pArapSubAuthInfo->Logon.NTChallenge2;
|
|
|
|
|
|
DoTheDESEncrypt((PBYTE)&Challenge);
|
|
|
|
|
|
Response1 = Challenge.high;
|
|
Response2 = Challenge.low;
|
|
|
|
DoDesEnd();
|
|
|
|
LeaveCriticalSection( &ArapDesLock );
|
|
|
|
//
|
|
// zero the clear text password: we don't need it hanging around
|
|
//
|
|
SecureZeroMemory(ClearTextPassword, sizeof(ClearTextPassword));
|
|
|
|
|
|
//
|
|
// does the response returned by the Mac match ours?
|
|
//
|
|
if ((Response1 == pArapSubAuthInfo->Logon.MacResponse1) &&
|
|
(Response2 == pArapSubAuthInfo->Logon.MacResponse2))
|
|
{
|
|
pArapResp->Result = ARAPERR_NO_ERROR;
|
|
status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
DBGPRINT("ARAPSubAuth: our Challenge: %lx %lx\n",
|
|
pArapSubAuthInfo->Logon.NTChallenge1,pArapSubAuthInfo->Logon.NTChallenge2);
|
|
|
|
DBGPRINT("ARAPSubAuth: Response don't match! (ours %lx %lx vs. Mac's %lx %lx)\n",
|
|
Response1,Response2,pArapSubAuthInfo->Logon.MacResponse1,
|
|
pArapSubAuthInfo->Logon.MacResponse2);
|
|
|
|
pArapResp->Response.high = 0;
|
|
pArapResp->Response.low = 0;
|
|
|
|
pArapResp->Result = ARAPERR_AUTH_FAILURE;
|
|
status = STATUS_WRONG_PASSWORD;
|
|
}
|
|
|
|
return(status);
|
|
|
|
}
|
|
|
|
NTSTATUS
|
|
ArapChangePassword(
|
|
IN OUT PRAS_SUBAUTH_INFO pRasSubAuthInfo,
|
|
OUT PULONG ReturnBufferLength,
|
|
OUT PVOID *ReturnBuffer
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to change the password of the user in question.
|
|
It first retrieves the clear-text password, does the DES decryption of the
|
|
munged old password and munged new password to get the clear-text old and
|
|
new passwords; makes sure that the old password matches with what we have
|
|
as the password and then finally, sets the new password.
|
|
|
|
Arguments:
|
|
|
|
pRasSubAuthInfo - ptr to RAS_SUBAUTH_INFO struct: input data
|
|
ReturnBufferLength - how much are we returning
|
|
ReturnBuffer - what we return: output data
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if password change succeeded, appropriate error otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS status;
|
|
PARAP_SUBAUTH_REQ pArapSubAuthInfo;
|
|
PARAP_SUBAUTH_RESP pArapResp;
|
|
UNICODE_STRING UserName;
|
|
UNICODE_STRING PackageName;
|
|
UNICODE_STRING UnicodePassword;
|
|
ANSI_STRING AnsiPassword;
|
|
USER_INFORMATION_CLASS UserInformationClass;
|
|
USER_ALL_INFORMATION UserAllInfo;
|
|
ARAP_CHALLENGE Challenge;
|
|
USER_PARAMETERS_INFORMATION *oldParmInfo=NULL;
|
|
PSAMPR_USER_ALL_INFORMATION UserParmInfo=NULL;
|
|
UCHAR OldPwd[32];
|
|
UCHAR NewPwd[32];
|
|
UCHAR MacsOldPwd[32];
|
|
WCHAR NtPassword[40];
|
|
UCHAR NewPwdLen;
|
|
UCHAR OldPwdLen;
|
|
UCHAR MacOldPwdLen;
|
|
SAMPR_HANDLE UserHandle;
|
|
PVOID Credentials;
|
|
DWORD CredentialSize;
|
|
PUCHAR pBufPtr;
|
|
BOOLEAN fCallerIsArap;
|
|
UCHAR FirstByte;
|
|
BOOLEAN fPasswordAvailable=TRUE;
|
|
UCHAR i;
|
|
|
|
|
|
|
|
|
|
*ReturnBuffer = MIDL_user_allocate( sizeof(ARAP_SUBAUTH_RESP) );
|
|
|
|
if (*ReturnBuffer == NULL)
|
|
{
|
|
DBGPRINT("ARAPChgPwd: MIDL_alloc failed!\n");
|
|
*ReturnBufferLength = 0;
|
|
return(STATUS_INSUFFICIENT_RESOURCES);
|
|
}
|
|
|
|
*ReturnBufferLength = sizeof(ARAP_SUBAUTH_RESP);
|
|
|
|
pArapResp = (PARAP_SUBAUTH_RESP)*ReturnBuffer;
|
|
|
|
|
|
pArapSubAuthInfo = (PARAP_SUBAUTH_REQ)&pRasSubAuthInfo->Data[0];
|
|
|
|
if ((pArapSubAuthInfo->PacketType != ARAP_SUBAUTH_CHGPWD_PKT) &&
|
|
(pArapSubAuthInfo->PacketType != SFM_SUBAUTH_CHGPWD_PKT))
|
|
{
|
|
DBGPRINT("ARAPChgPwd: bad packet type %d!\n",pArapSubAuthInfo->PacketType);
|
|
pArapResp->Result = ARAPERR_BAD_FORMAT;
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
fCallerIsArap = (pArapSubAuthInfo->PacketType == ARAP_SUBAUTH_CHGPWD_PKT);
|
|
|
|
UserName.Length = (sizeof(WCHAR) * wcslen(pArapSubAuthInfo->ChgPwd.UserName));
|
|
UserName.MaximumLength = UserName.Length;
|
|
|
|
UserName.Buffer = pArapSubAuthInfo->ChgPwd.UserName;
|
|
|
|
status = ArapGetSamHandle(&UserHandle, &UserName);
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("Arap: ArapGetSamHandle failed with %lx\n", status);
|
|
pArapResp->Result = ARAPERR_COULDNT_GET_SAMHANDLE;
|
|
return(status);
|
|
}
|
|
|
|
SecureZeroMemory(OldPwd, sizeof(OldPwd));
|
|
SecureZeroMemory(MacsOldPwd, sizeof(MacsOldPwd));
|
|
SecureZeroMemory(NewPwd, sizeof(NewPwd));
|
|
|
|
|
|
//
|
|
// are we on a DS?
|
|
//
|
|
if (SampUsingDsData())
|
|
{
|
|
RtlInitUnicodeString( &PackageName, CLEAR_TEXT_PWD_PACKAGE );
|
|
|
|
//
|
|
// get the clear text password
|
|
//
|
|
status = SamIRetrievePrimaryCredentials( (PVOID)UserHandle,
|
|
&PackageName,
|
|
&Credentials,
|
|
&CredentialSize );
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ARAPSubAuth: SamI...Credentials failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
SamrCloseHandle( &UserHandle );
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// if we are returned a null password, it could be that the password is really
|
|
// null, or that cleartext password isn't available for this user. If it's
|
|
// the latter, we need to bail out!
|
|
//
|
|
if (CredentialSize == 0)
|
|
{
|
|
// get the OWF for this user
|
|
status = SamrQueryInformationUser( UserHandle,
|
|
UserParametersInformation,
|
|
(PSAMPR_USER_INFO_BUFFER*)&oldParmInfo);
|
|
|
|
//
|
|
// if the call failed, or if the user's password is not null, bail out!
|
|
//
|
|
if ( !NT_SUCCESS(status) ||
|
|
(oldParmInfo->Parameters.Length != NT_OWF_PASSWORD_LENGTH) ||
|
|
(memcmp(oldParmInfo->Parameters.Buffer,
|
|
&EMPTY_OWF_PASSWORD,
|
|
NT_OWF_PASSWORD_LENGTH)) )
|
|
{
|
|
fPasswordAvailable = FALSE;
|
|
}
|
|
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
SamIFree_SAMPR_USER_INFO_BUFFER( (PSAMPR_USER_INFO_BUFFER)oldParmInfo,
|
|
UserParametersInformation);
|
|
}
|
|
|
|
if (!fPasswordAvailable)
|
|
{
|
|
DBGPRINT("ArapChangePassword: password not available\n");
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
LocalFree( Credentials );
|
|
SamrCloseHandle( &UserHandle );
|
|
return(status);
|
|
}
|
|
}
|
|
|
|
|
|
// convert to wide-char size
|
|
CredentialSize = (CredentialSize/sizeof(WCHAR));
|
|
|
|
|
|
if (CredentialSize > sizeof(OldPwd))
|
|
{
|
|
DBGPRINT("ArapChangePassword: pwd too long (%d bytes)\n",CredentialSize);
|
|
pArapResp->Result = ARAPERR_PASSWORD_TOO_LONG;
|
|
LocalFree( Credentials );
|
|
SamrCloseHandle( &UserHandle );
|
|
return(STATUS_WRONG_PASSWORD);
|
|
}
|
|
|
|
wcstombs(OldPwd, Credentials, CredentialSize);
|
|
|
|
SecureZeroMemory( Credentials, CredentialSize );
|
|
LocalFree( Credentials );
|
|
}
|
|
|
|
//
|
|
// we are not running on the DS, but on a Standalone (workgroup) box. We need to
|
|
// retrieve the cleartext pwd differently
|
|
//
|
|
else
|
|
{
|
|
// get the user parms
|
|
status = SamrQueryInformationUser( UserHandle,
|
|
UserAllInformation,
|
|
(PSAMPR_USER_INFO_BUFFER *)&UserParmInfo);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DBGPRINT("ARAPSubAuth: SamrQueryInformationUser failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
SamrCloseHandle( &UserHandle );
|
|
return(status);
|
|
}
|
|
|
|
status = IASParmsGetUserPassword(UserParmInfo->Parameters.Buffer,
|
|
&UnicodePassword.Buffer);
|
|
|
|
SamIFree_SAMPR_USER_INFO_BUFFER((PSAMPR_USER_INFO_BUFFER)UserParmInfo,
|
|
UserAllInformation);
|
|
|
|
if ((status != STATUS_SUCCESS) || (UnicodePassword.Buffer == NULL))
|
|
{
|
|
DBGPRINT("ARAPSubAuth: IASParmsGetUserPassword failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
SamrCloseHandle( &UserHandle );
|
|
return(STATUS_WRONG_PASSWORD);
|
|
}
|
|
|
|
UnicodePassword.MaximumLength =
|
|
UnicodePassword.Length = (USHORT)(wcslen(UnicodePassword.Buffer) * sizeof( WCHAR ));
|
|
|
|
AnsiPassword.Length = AnsiPassword.MaximumLength = sizeof(OldPwd);
|
|
AnsiPassword.Buffer = OldPwd;
|
|
|
|
status = RtlUnicodeStringToAnsiString( &AnsiPassword, &UnicodePassword, FALSE );
|
|
|
|
SecureZeroMemory(UnicodePassword.Buffer, UnicodePassword.Length);
|
|
|
|
// we don't need the unicode password anymore
|
|
RtlFreeUnicodeString(&UnicodePassword);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
DBGPRINT("ARAPSubAuth: RtlUnicodeStringToAnsiString failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_PASSWD_NOT_AVAILABLE;
|
|
SamrCloseHandle( &UserHandle );
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// password change happens differently for ARAP and SFM: in ARAP, the old pwd
|
|
// as well as the new pwd are encrypted using the old pwd. In SFM, old pwd is
|
|
// encrypted with the new pwd (the first 8 bytes in after username), and the
|
|
// new pwd is encrypted with the old pwd (the next 8 bytes)
|
|
//
|
|
if (fCallerIsArap)
|
|
{
|
|
|
|
//
|
|
// first, get the get the old password out (the way Mac knows it)
|
|
//
|
|
|
|
pBufPtr = &pArapSubAuthInfo->ChgPwd.OldMunge[0];
|
|
|
|
EnterCriticalSection( &ArapDesLock );
|
|
|
|
DoDesInit(OldPwd, TRUE);
|
|
|
|
// first 8 bytes of mangled old password
|
|
|
|
pBufPtr = &pArapSubAuthInfo->ChgPwd.OldMunge[0];
|
|
Challenge.high = (*((DWORD *)(pBufPtr)));
|
|
pBufPtr += 4;
|
|
Challenge.low = (*((DWORD *)(pBufPtr)));
|
|
|
|
DoTheDESDecrypt((PBYTE)&Challenge);
|
|
|
|
RtlCopyMemory(MacsOldPwd, (PBYTE)&Challenge, 8);
|
|
|
|
// next 8 bytes of mangled old password
|
|
|
|
pBufPtr += 4;
|
|
Challenge.high = (*((DWORD *)(pBufPtr)));
|
|
pBufPtr += 4;
|
|
Challenge.low = (*((DWORD *)(pBufPtr)));
|
|
|
|
DoTheDESDecrypt((PBYTE)&Challenge);
|
|
|
|
RtlCopyMemory(MacsOldPwd+8, (PBYTE)&Challenge, 8);
|
|
|
|
|
|
//
|
|
// now, get the new password
|
|
//
|
|
|
|
// first 8 bytes of the mangled new password
|
|
|
|
pBufPtr = &pArapSubAuthInfo->ChgPwd.NewMunge[0];
|
|
Challenge.high = (*((DWORD *)(pBufPtr)));
|
|
pBufPtr += 4;
|
|
Challenge.low = (*((DWORD *)(pBufPtr)));
|
|
|
|
DoTheDESDecrypt((PBYTE)&Challenge);
|
|
|
|
RtlCopyMemory(NewPwd, (PBYTE)&Challenge, 8);
|
|
|
|
// next 8 bytes of the mangled new password
|
|
|
|
pBufPtr += 4;
|
|
Challenge.high = (*((DWORD *)(pBufPtr)));
|
|
pBufPtr += 4;
|
|
Challenge.low = (*((DWORD *)(pBufPtr)));
|
|
|
|
DoTheDESDecrypt((PBYTE)&Challenge);
|
|
|
|
RtlCopyMemory(NewPwd+8, (PBYTE)&Challenge, 8);
|
|
|
|
DoDesEnd();
|
|
|
|
LeaveCriticalSection( &ArapDesLock );
|
|
|
|
MacOldPwdLen = MacsOldPwd[0];
|
|
NewPwdLen = NewPwd[0];
|
|
FirstByte = 1;
|
|
}
|
|
else
|
|
{
|
|
// using old pwd as the key, get the new pwd out
|
|
|
|
EnterCriticalSection( &ArapDesLock );
|
|
|
|
DoDesInit(OldPwd, FALSE); // clear low-bit
|
|
|
|
pBufPtr = &pArapSubAuthInfo->ChgPwd.NewMunge[0];
|
|
|
|
Challenge.high = (*((DWORD UNALIGNED *)(pBufPtr)));
|
|
pBufPtr += 4;
|
|
Challenge.low = (*((DWORD UNALIGNED *)(pBufPtr)));
|
|
|
|
DoTheDESDecrypt((PBYTE)&Challenge);
|
|
|
|
RtlCopyMemory(NewPwd, (PBYTE)&Challenge, 8);
|
|
|
|
DoDesEnd();
|
|
|
|
//
|
|
// now, we need to get the old pwd out so that we can make sure the
|
|
// guy really had the pwd to begin with
|
|
//
|
|
DoDesInit(NewPwd, FALSE); // clear low-bit
|
|
|
|
pBufPtr = &pArapSubAuthInfo->ChgPwd.OldMunge[0];
|
|
|
|
Challenge.high = (*((DWORD UNALIGNED *)(pBufPtr)));
|
|
pBufPtr += 4;
|
|
Challenge.low = (*((DWORD UNALIGNED *)(pBufPtr)));
|
|
|
|
DoTheDESDecrypt((PBYTE)&Challenge);
|
|
|
|
RtlCopyMemory(MacsOldPwd, (PBYTE)&Challenge, 8);
|
|
|
|
DoDesEnd();
|
|
|
|
LeaveCriticalSection( &ArapDesLock );
|
|
|
|
MacOldPwdLen = (UCHAR)strlen((PBYTE)MacsOldPwd);
|
|
NewPwdLen = (UCHAR)strlen((PBYTE)NewPwd);
|
|
FirstByte = 0;
|
|
}
|
|
|
|
|
|
OldPwdLen = (UCHAR)strlen((PBYTE)OldPwd);
|
|
|
|
|
|
if ((MacOldPwdLen != OldPwdLen) || (MacOldPwdLen > MAX_MAC_PWD_LEN))
|
|
{
|
|
DBGPRINT("ArapChangePassword: Length mismatch! old len %d, oldMacLen %d\n",
|
|
OldPwdLen,MacOldPwdLen);
|
|
|
|
pArapResp->Result = ARAPERR_PASSWORD_TOO_LONG;
|
|
|
|
SamrCloseHandle( &UserHandle );
|
|
|
|
SecureZeroMemory(OldPwd, sizeof(OldPwd));
|
|
SecureZeroMemory(MacsOldPwd, sizeof(MacsOldPwd));
|
|
SecureZeroMemory(NewPwd, sizeof(NewPwd));
|
|
|
|
return(STATUS_LOGON_FAILURE);
|
|
}
|
|
|
|
//
|
|
// make sure the guy really knew the password to begin with
|
|
//
|
|
for (i=0; i<MacOldPwdLen ; i++)
|
|
{
|
|
if (MacsOldPwd[FirstByte+i] != OldPwd[i])
|
|
{
|
|
// Use only when required
|
|
#if 0
|
|
DBGPRINT("ArapChgPwd: bad pwd: oldpwd=%s Mac's pwd=%s newpwd=%s\n",
|
|
OldPwd,&MacsOldPwd[1],&NewPwd[1]);
|
|
#endif
|
|
|
|
pArapResp->Result = ARAPERR_BAD_PASSWORD;
|
|
|
|
SamrCloseHandle( &UserHandle );
|
|
|
|
SecureZeroMemory(OldPwd, sizeof(OldPwd));
|
|
SecureZeroMemory(MacsOldPwd, sizeof(MacsOldPwd));
|
|
SecureZeroMemory(NewPwd, sizeof(NewPwd));
|
|
|
|
return(STATUS_LOGON_FAILURE);
|
|
}
|
|
}
|
|
|
|
|
|
SecureZeroMemory(NtPassword, sizeof(NtPassword));
|
|
|
|
//
|
|
// convert the thing to unicode..
|
|
// first byte in newpwd is length of the passwd
|
|
//
|
|
mbstowcs(NtPassword, &NewPwd[FirstByte], NewPwdLen);
|
|
|
|
NtPassword[NewPwdLen] = 0;
|
|
|
|
SecureZeroMemory( &UserAllInfo, sizeof(UserAllInfo) );
|
|
|
|
UserAllInfo.UserName.Length = UserName.Length;
|
|
UserAllInfo.UserName.MaximumLength = UserName.MaximumLength;
|
|
UserAllInfo.UserName.Buffer = UserName.Buffer;
|
|
|
|
UserAllInfo.WhichFields = USER_ALL_NTPASSWORDPRESENT;
|
|
UserAllInfo.NtPassword.Length = wcslen(NtPassword) * sizeof(WCHAR);
|
|
UserAllInfo.NtPassword.MaximumLength = wcslen(NtPassword) * sizeof(WCHAR);
|
|
UserAllInfo.NtPassword.Buffer = NtPassword;
|
|
|
|
status = SamrSetInformationUser( UserHandle,
|
|
UserAllInformation,
|
|
(PSAMPR_USER_INFO_BUFFER)&UserAllInfo);
|
|
|
|
|
|
SamrCloseHandle( &UserHandle );
|
|
|
|
|
|
//
|
|
// wipe out all the clear-text passwords
|
|
//
|
|
SecureZeroMemory(OldPwd, sizeof(OldPwd));
|
|
SecureZeroMemory(NewPwd, sizeof(NewPwd));
|
|
SecureZeroMemory((PUCHAR)NtPassword, sizeof(NtPassword));
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ARAPSubAuth: SamrSetInfo.. failed %lx\n",status);
|
|
pArapResp->Result = ARAPERR_SET_PASSWD_FAILED;
|
|
return(STATUS_UNSUCCESSFUL);
|
|
}
|
|
|
|
pArapResp->Result = ARAPERR_NO_ERROR;
|
|
|
|
|
|
return(STATUS_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
NTSTATUS
|
|
ArapGetSamHandle(
|
|
IN PVOID *pUserHandle,
|
|
IN PUNICODE_STRING pUserName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine gets sam handle to the specified user (when we get into the
|
|
subauth pkg for a password change, we don't have user's sam handle).
|
|
|
|
Arguments:
|
|
|
|
pUserHandle - sam handle, on return
|
|
pUserName - name of the user in question
|
|
|
|
Return Value:
|
|
|
|
STATUS_SUCCESS: if handle retrieved successfully,
|
|
appropriate error otherwise
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
NTSTATUS status;
|
|
PLSAPR_POLICY_INFORMATION PolicyInfo = NULL;
|
|
SAMPR_HANDLE SamHandle = NULL;
|
|
SAMPR_HANDLE DomainHandle = NULL;
|
|
SAMPR_ULONG_ARRAY RidArray;
|
|
SAMPR_ULONG_ARRAY UseArray;
|
|
|
|
|
|
|
|
RidArray.Element = NULL;
|
|
UseArray.Element = NULL;
|
|
|
|
status = LsaIQueryInformationPolicyTrusted(
|
|
PolicyAccountDomainInformation,
|
|
&PolicyInfo);
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ArapGetSamHandle: LsaI...Trusted failed with %lx\n", status);
|
|
goto ArapGetSamHandle_Exit;
|
|
}
|
|
|
|
|
|
status = SamIConnect(
|
|
NULL, // no server name
|
|
&SamHandle,
|
|
0, // no desired access
|
|
TRUE // trusted caller
|
|
);
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ArapGetSamHandle: SamIConnect failed with %lx\n", status);
|
|
goto ArapGetSamHandle_Exit;
|
|
}
|
|
|
|
status = SamrOpenDomain(
|
|
SamHandle,
|
|
0, // no desired access
|
|
(PRPC_SID) PolicyInfo->PolicyAccountDomainInfo.DomainSid,
|
|
&DomainHandle);
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ArapGetSamHandle: SamrOpenDomain failed with %lx\n", status);
|
|
goto ArapGetSamHandle_Exit;
|
|
}
|
|
|
|
status = SamrLookupNamesInDomain(
|
|
DomainHandle,
|
|
1,
|
|
(PRPC_UNICODE_STRING) pUserName,
|
|
&RidArray,
|
|
&UseArray);
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ArapGetSamHandle: Samr..Domain failed with %lx\n", status);
|
|
goto ArapGetSamHandle_Exit;
|
|
}
|
|
|
|
if (UseArray.Element[0] != SidTypeUser)
|
|
{
|
|
DBGPRINT("ArapGetSamHandle: didn't find our user\n");
|
|
goto ArapGetSamHandle_Exit;
|
|
}
|
|
|
|
|
|
//
|
|
// Finally open the user
|
|
//
|
|
status = SamrOpenUser(
|
|
DomainHandle,
|
|
0, // no desired access,
|
|
RidArray.Element[0],
|
|
pUserHandle);
|
|
|
|
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
DBGPRINT("ArapGetSamHandle: SamrOpenUser failed with %lx\n", status);
|
|
goto ArapGetSamHandle_Exit;
|
|
}
|
|
|
|
|
|
ArapGetSamHandle_Exit:
|
|
|
|
if (DomainHandle != NULL)
|
|
{
|
|
SamrCloseHandle( &DomainHandle );
|
|
}
|
|
|
|
if (SamHandle != NULL)
|
|
{
|
|
SamrCloseHandle( &SamHandle );
|
|
}
|
|
|
|
if (PolicyInfo != NULL)
|
|
{
|
|
LsaIFree_LSAPR_POLICY_INFORMATION(
|
|
PolicyAccountDomainInformation,
|
|
PolicyInfo);
|
|
}
|
|
|
|
SamIFree_SAMPR_ULONG_ARRAY( &UseArray );
|
|
|
|
SamIFree_SAMPR_ULONG_ARRAY( &RidArray );
|
|
|
|
return(status);
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
DeltaNotify(
|
|
IN PSID DomainSid,
|
|
IN SECURITY_DB_DELTA_TYPE DeltaType,
|
|
IN SECURITY_DB_OBJECT_TYPE ObjectType,
|
|
IN ULONG ObjectRid,
|
|
IN OPTIONAL PUNICODE_STRING ObjectName,
|
|
IN PLARGE_INTEGER ModifiedCount,
|
|
IN PSAM_DELTA_DATA DeltaData
|
|
)
|
|
{
|
|
DWORD dwRetCode;
|
|
AFP_SERVER_HANDLE hAfpServer;
|
|
AFP_SERVER_INFO afpInfo;
|
|
|
|
|
|
// ignore any changes other than those to user
|
|
if (ObjectType != SecurityDbObjectSamUser)
|
|
{
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
// we only care about guest account: ignore the notification for other users
|
|
if (ObjectRid != DOMAIN_USER_RID_GUEST)
|
|
{
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
// enable/disable of guest account is all that's interesting to us
|
|
if (DeltaType != SecurityDbChange)
|
|
{
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
// if there is no DeltaData, account enable/disable hasn't been affected
|
|
if (!DeltaData)
|
|
{
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
//
|
|
// ok, looks like Guest account was enabled (or disabled). Connect to the
|
|
// SFM service on this machine. If we fail, that means SFM is not started
|
|
// In that case, ignore this change
|
|
//
|
|
dwRetCode = AfpAdminConnect(NULL, &hAfpServer);
|
|
|
|
// if we couldn't connect, don't bother: just ignore this notification
|
|
if (dwRetCode != NO_ERROR)
|
|
{
|
|
DBGPRINT("DeltaNotify: AfpAdminConnect failed, dwRetCode = %ld\n",dwRetCode);
|
|
return(STATUS_SUCCESS);
|
|
}
|
|
|
|
SecureZeroMemory(&afpInfo, sizeof(AFP_SERVER_INFO));
|
|
|
|
//
|
|
// find out if the guest account is enabled or disabled and set the flag
|
|
// appropriately
|
|
//
|
|
if (!(DeltaData->AccountControl & USER_ACCOUNT_DISABLED))
|
|
{
|
|
afpInfo.afpsrv_options = AFP_SRVROPT_GUESTLOGONALLOWED;
|
|
}
|
|
|
|
dwRetCode = AfpAdminServerSetInfo(hAfpServer,
|
|
(LPBYTE)&afpInfo,
|
|
AFP_SERVER_GUEST_ACCT_NOTIFY);
|
|
|
|
AfpAdminDisconnect(hAfpServer);
|
|
|
|
return(STATUS_SUCCESS);
|
|
}
|