Leaked source code of windows server 2003
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.
 
 
 
 
 
 

3133 lines
90 KiB

/*++
Copyright (c) 1989 Microsoft Corporation
Module Name:
msvpaswd.c
Abstract:
This file contains the MSV1_0 Authentication Package password routines.
Author:
Dave Hart (davehart) 12-Mar-1992
Revision History:
Chandana Surlu 21-Jul-96 Stolen from \\kernel\razzle3\src\security\msv1_0\msvpaswd.c
--*/
#include <global.h>
#include "msp.h"
#include "nlp.h"
#include <lmcons.h>
#include <lmerr.h>
#include <lmapibuf.h>
#include <lmremutl.h>
#include <lmwksta.h>
#include "msvwow.h" // MsvConvertWOWChangePasswordBuffer()
NTSTATUS
MspDisableAdminsAlias (
VOID
)
/*++
Routine Description:
Remove the current thread from the Administrators alias. This
is accomplished by impersonating our own thread, then removing
the Administrators alias membership from the impersonation
token. Use MspStopImpersonating() to stop impersonating and
thereby restore the thread to the Administrators alias.
Arguments:
None.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
--*/
{
NTSTATUS Status;
HANDLE TokenHandle = NULL;
HANDLE FilteredToken = NULL;
SID_IDENTIFIER_AUTHORITY IdentifierAuthority = SECURITY_NT_AUTHORITY;
PSID AdminSid = NULL;
SID LocalSystemSid = {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID};
BYTE GroupBuffer[sizeof(TOKEN_GROUPS) + sizeof(SID_AND_ATTRIBUTES)];
PTOKEN_GROUPS TokenGroups = (PTOKEN_GROUPS) GroupBuffer;
//
// Make sure we aren't impersonating anyone else
// (that will prevent the RtlImpersonateSelf() call from succeeding).
//
RevertToSelf();
//
// Open our process token so we can filter it to disable the
// Administrators and LocalSystem SIDs
//
Status = RtlImpersonateSelf(SecurityDelegation);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
Status = NtOpenThreadToken(
NtCurrentThread(),
TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
TRUE, // open as self
&TokenHandle
);
if ( !NT_SUCCESS(Status) ) {
goto Cleanup;
}
//
// Build the SID for the Administrators alias. The Administrators
// alias SID is well known, S-1-5-32-544.
//
Status = RtlAllocateAndInitializeSid(
&IdentifierAuthority, // SECURITY_NT_AUTHORITY (5)
2, // SubAuthorityCount
SECURITY_BUILTIN_DOMAIN_RID, // 32
DOMAIN_ALIAS_RID_ADMINS, // 544
0,0,0,0,0,0,
&AdminSid
);
if ( !NT_SUCCESS(Status) ) {
KdPrint(("MspDisableAdminsAlias: RtlAllocateAndInitializeSid returns %x\n",
Status));
goto Cleanup;
}
//
// Disable the Administrators and LocalSystem aliases.
//
TokenGroups->GroupCount = 2;
TokenGroups->Groups[0].Sid = AdminSid;
TokenGroups->Groups[0].Attributes = 0; // SE_GROUP_ENABLED not on.
TokenGroups->Groups[1].Sid = &LocalSystemSid;
TokenGroups->Groups[1].Attributes = 0; // SE_GROUP_ENABLED not on.
Status = NtFilterToken(
TokenHandle,
0, // no flags
TokenGroups,
NULL, // no privileges
NULL, // no restricted sids
&FilteredToken
);
if ( !NT_SUCCESS(Status) ) {
KdPrint(("MspDisableAdminsAlias: NtFilter returns %x\n",
Status));
goto Cleanup;
}
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
&FilteredToken,
sizeof(HANDLE)
);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
Cleanup:
if (AdminSid) {
RtlFreeSid(AdminSid);
}
if (TokenHandle) {
NtClose(TokenHandle);
}
if (FilteredToken) {
NtClose(FilteredToken);
}
return Status;
}
NTSTATUS
MspImpersonateAnonymous(
VOID
)
/*++
Routine Description:
Remove the current thread from the Administrators alias. This
is accomplished by impersonating our own thread, then removing
the Administrators alias membership from the impersonation
token. Use RevertToSelf() to stop impersonating and
thereby restore the thread to the Administrators alias.
Arguments:
None.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
--*/
{
RevertToSelf();
if(!ImpersonateAnonymousToken( GetCurrentThread() ))
{
return STATUS_CANNOT_IMPERSONATE;
}
return STATUS_SUCCESS;
}
NTSTATUS
MspAddBackslashesComputerName(
IN PUNICODE_STRING ComputerName,
OUT PUNICODE_STRING UncComputerName
)
/*++
Routine Description:
This function makes a copy of a Computer Name, prepending backslashes
if they are not already present.
Arguments:
ComputerName - Pointer to Computer Name without backslashes.
UncComputerName - Pointer to Unicode String structure that will be
initialized to reference the computerName with backslashes
prepended if not already present. The Unicode Buffer will be
terminated with a Unicode NULL, so that it can be passed as
a parameter to routines expecting a null terminated Wide String.
When this string is finished with, the caller must free its
memory via RtlFreeHeap.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
BOOLEAN HasBackslashes = FALSE;
BOOLEAN IsNullTerminated = FALSE;
USHORT OutputNameLength;
USHORT OutputNameMaximumLength;
PWSTR StartBuffer = NULL;
//
// If the computername is NULL, a zero length string, or the name already begins with
// backslashes and is wide char null terminated, just use it unmodified.
//
if( (!ARGUMENT_PRESENT(ComputerName)) || ComputerName->Length == 0 ) {
UncComputerName->Buffer = NULL;
UncComputerName->Length = 0;
UncComputerName->MaximumLength = 0;
goto AddBackslashesComputerNameFinish;
}
//
// Name is not NULL or zero length. Check if name already has
// backslashes and a trailing Unicode Null
//
OutputNameLength = ComputerName->Length + (2 * sizeof(WCHAR));
OutputNameMaximumLength = OutputNameLength + sizeof(WCHAR);
if ((ComputerName && ComputerName->Length >= 2 * sizeof(WCHAR)) &&
(ComputerName->Buffer[0] == L'\\') &&
(ComputerName->Buffer[1] == L'\\')) {
HasBackslashes = TRUE;
OutputNameLength -= (2 * sizeof(WCHAR));
OutputNameMaximumLength -= (2 * sizeof(WCHAR));
}
if ((ComputerName->Length + (USHORT) sizeof(WCHAR) <= ComputerName->MaximumLength) &&
(ComputerName->Buffer[ComputerName->Length/sizeof(WCHAR)] == UNICODE_NULL)) {
IsNullTerminated = TRUE;
}
if (HasBackslashes && IsNullTerminated) {
*UncComputerName = *ComputerName;
goto AddBackslashesComputerNameFinish;
}
//
// Name either does not have backslashes or is not NULL terminated.
// Make a copy with leading backslashes and a wide NULL terminator.
//
UncComputerName->Length = OutputNameLength;
UncComputerName->MaximumLength = OutputNameMaximumLength;
UncComputerName->Buffer = I_NtLmAllocate(
OutputNameMaximumLength
);
if (UncComputerName->Buffer == NULL) {
KdPrint(("MspAddBackslashes...: Out of memory copying ComputerName.\n"));
Status = STATUS_NO_MEMORY;
goto AddBackslashesComputerNameError;
}
StartBuffer = UncComputerName->Buffer;
if (!HasBackslashes) {
UncComputerName->Buffer[0] = UncComputerName->Buffer[1] = L'\\';
StartBuffer +=2;
}
RtlCopyMemory(
StartBuffer,
ComputerName->Buffer,
ComputerName->Length
);
UncComputerName->Buffer[UncComputerName->Length / sizeof(WCHAR)] = UNICODE_NULL;
AddBackslashesComputerNameFinish:
return(Status);
AddBackslashesComputerNameError:
goto AddBackslashesComputerNameFinish;
}
#ifndef DONT_LOG_PASSWORD_CHANGES
#include <stdio.h>
HANDLE MsvPaswdLogFile = NULL;
#define MSVPASWD_LOGNAME L"\\debug\\PASSWD.LOG"
#define MSVPASWD_BAKNAME L"\\debug\\PASSWD.BAK"
ULONG
MsvPaswdInitializeLog(
VOID
)
/*++
Routine Description:
Initializes the debugging log file used by DCPROMO and the dssetup apis
Arguments:
None
Returns:
ERROR_SUCCESS - Success
--*/
{
ULONG dwErr = ERROR_SUCCESS;
WCHAR LogFileName[ MAX_PATH + 1 ], BakFileName[ MAX_PATH + 1 ];
if ( !GetWindowsDirectoryW( LogFileName,
sizeof( LogFileName )/sizeof( WCHAR ) ) ) {
dwErr = GetLastError();
} else {
wcscpy( BakFileName, LogFileName );
wcscat( LogFileName, MSVPASWD_LOGNAME );
wcscat( BakFileName, MSVPASWD_BAKNAME );
//
// Copy the existing (maybe) log file to a backup
//
//if ( CopyFile( LogFileName, BakFileName, FALSE ) == FALSE ) {
//
// }
MsvPaswdLogFile = CreateFileW( LogFileName,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL );
if ( MsvPaswdLogFile == INVALID_HANDLE_VALUE ) {
dwErr = GetLastError();
MsvPaswdLogFile = NULL;
} else {
if( SetFilePointer( MsvPaswdLogFile,
0, 0,
FILE_END ) == 0xFFFFFFFF ) {
dwErr = GetLastError();
CloseHandle( MsvPaswdLogFile );
MsvPaswdLogFile = NULL;
}
}
}
return( dwErr );
}
ULONG
MsvPaswdCloseLog(
VOID
)
/*++
Routine Description:
Closes the debugging log file used by DCPROMO and the dssetup apis
Arguments:
None
Returns:
ERROR_SUCCESS - Success
--*/
{
ULONG dwErr = ERROR_SUCCESS;
if ( MsvPaswdLogFile != NULL ) {
CloseHandle( MsvPaswdLogFile );
MsvPaswdLogFile = NULL;
}
return( dwErr );
}
//
// Stolen and hacked up from netlogon code
//
VOID
MsvPaswdDebugDumpRoutine(
IN LPSTR Format,
va_list arglist
)
{
char OutputBuffer[2049];
ULONG length;
ULONG BytesWritten;
SYSTEMTIME SystemTime;
static BeginningOfLine = TRUE;
int cbUsed = 0;
//
// If we don't have an open log file, just bail
//
if ( MsvPaswdLogFile == NULL ) {
return;
}
length = 0;
OutputBuffer[sizeof(OutputBuffer) - 1] = '\0';
//
// Handle the beginning of a new line.
//
//
if ( BeginningOfLine ) {
//
// If we're writing to the debug terminal,
// indicate this is a Netlogon message.
//
//
// Put the timestamp at the begining of the line.
//
GetLocalTime( &SystemTime );
length += (ULONG) sprintf( &OutputBuffer[length],
"%02u/%02u %02u:%02u:%02u ",
SystemTime.wMonth,
SystemTime.wDay,
SystemTime.wHour,
SystemTime.wMinute,
SystemTime.wSecond );
}
//
// Put a the information requested by the caller onto the line
//
// save two chars of spaces for the EOLs
//
cbUsed = (ULONG) _vsnprintf(&OutputBuffer[length], sizeof(OutputBuffer) - length - 1 - 2, Format, arglist);
if (cbUsed >= 0)
{
length += cbUsed;
}
BeginningOfLine = (length > 0 && OutputBuffer[length-1] == '\n' );
if ( BeginningOfLine ) {
OutputBuffer[length-1] = '\r';
OutputBuffer[length] = '\n';
OutputBuffer[length+1] = '\0';
length++;
}
ASSERT( length <= sizeof( OutputBuffer ) / sizeof( CHAR ) );
//
// Write the debug info to the log file.
//
if ( !WriteFile( MsvPaswdLogFile,
OutputBuffer,
length,
&BytesWritten,
NULL ) ) {
}
}
VOID
MsvPaswdLogPrintRoutine(
IN LPSTR Format,
...
)
{
va_list arglist;
va_start(arglist, Format);
MsvPaswdDebugDumpRoutine( Format, arglist );
va_end(arglist);
}
ULONG
MsvPaswdSetAndClearLog(
VOID
)
/*++
Routine Description:
Flushes the log and seeks to the end of the file
Arguments:
None
Returns:
ERROR_SUCCESS - Success
--*/
{
ULONG dwErr = ERROR_SUCCESS;
if ( MsvPaswdLogFile != NULL ) {
if( FlushFileBuffers( MsvPaswdLogFile ) == FALSE ) {
dwErr = GetLastError();
}
}
return( dwErr );
}
#endif // DONT_LOG_PASSWORD_CHANGES
NTSTATUS
MspChangePasswordSam(
IN PUNICODE_STRING UncComputerName,
IN PUNICODE_STRING UserName,
IN PUNICODE_STRING OldPassword,
IN PUNICODE_STRING NewPassword,
IN PLSA_CLIENT_REQUEST ClientRequest,
IN BOOLEAN Impersonating,
OUT PDOMAIN_PASSWORD_INFORMATION *DomainPasswordInfo,
OUT PPOLICY_PRIMARY_DOMAIN_INFO *PrimaryDomainInfo OPTIONAL,
OUT PBOOLEAN Authoritative
)
/*++
Routine Description:
This routine is called by MspChangePassword to change the password
on a Windows NT machine.
Arguments:
UncComputerName - Name of the target machine. This name must begin with
two backslashes.
UserName - Name of the user to change password for.
OldPassword - Plaintext current password.
NewPassword - Plaintext replacement password.
ClientRequest - Is a pointer to an opaque data structure
representing the client's request.
DomainPasswordInfo - Password restriction information (returned only if
status is STATUS_PASSWORD_RESTRICTION).
PrimaryDomainInfo - DomainNameInformation (returned only if status is
STATUS_BACKUP_CONTROLLER).
Authoritative - The failure was authoritative and no retries should be
made.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
...
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES ObjectAttributes;
SECURITY_QUALITY_OF_SERVICE SecurityQos;
SAM_HANDLE SamHandle = NULL;
SAM_HANDLE DomainHandle = NULL;
LSA_HANDLE LSAPolicyHandle = NULL;
OBJECT_ATTRIBUTES LSAObjectAttributes;
PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL;
BOOLEAN ImpersonatingAnonymous = FALSE;
BOOLEAN RetryAnonymous = FALSE;
UNREFERENCED_PARAMETER(ClientRequest);
//
// Assume all failures are authoritative
//
*Authoritative = TRUE;
//
// If we're impersonating (ie, winlogon impersonated its caller before calling us),
// impersonate again. This allows us to get the name of the caller for auditing.
//
if ( Impersonating ) {
Status = Lsa.ImpersonateClient();
} else {
UNICODE_STRING ComputerName;
BOOLEAN AvoidAnonymous = FALSE;
BOOLEAN LocalMachine = FALSE;
//
// Since the System context is a member of the Administrators alias,
// when we connect with the local SAM we come in as an Administrator.
// (When it's remote, we go over the null session and so have very
// low access). We don't want to be an Administrator because that
// would allow the user to change the password on an account whose
// ACL prohibits the user from changing the password. So we'll
// temporarily impersonate ourself and disable the Administrators
// alias in the impersonation token.
//
//
// find out if the referenced computer is the local machine.
//
ComputerName = *UncComputerName;
if( ComputerName.Length > 4 &&
ComputerName.Buffer[0] == L'\\' &&
ComputerName.Buffer[1] == L'\\' )
{
ComputerName.Buffer += 2;
ComputerName.Length -= 2 * sizeof(WCHAR);
}
if( NlpSamDomainName.Buffer )
{
LocalMachine = RtlEqualUnicodeString(
&ComputerName,
&NlpSamDomainName,
TRUE
);
}
if( !LocalMachine )
{
RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
LocalMachine = RtlEqualUnicodeString(
&ComputerName,
&NtLmGlobalUnicodeComputerNameString,
TRUE
);
RtlReleaseResource(&NtLmGlobalCritSect);
}
//
// Don't impersonateAnonymous if BLANKPWD flag is set
// AND the change is for the local machine.
//
if( (BOOLEAN) ((NtLmCheckProcessOption( MSV1_0_OPTION_ALLOW_BLANK_PASSWORD ) & MSV1_0_OPTION_ALLOW_BLANK_PASSWORD) != 0))
{
AvoidAnonymous = LocalMachine;
}
if( AvoidAnonymous )
{
Status = STATUS_SUCCESS;
ImpersonatingAnonymous = FALSE;
//
// allow a retry as anonymous on failure.
//
RetryAnonymous = TRUE;
} else {
//
// if the call is against the local machine, impersonate anonymous
// otherwise, impersonate a crippled SYSTEM token, so the call
// leaves the box as SYSTEM/machine creds.
//
if( !LocalMachine )
{
Status = MspDisableAdminsAlias ();
RetryAnonymous = TRUE;
} else {
Status = MspImpersonateAnonymous();
}
ImpersonatingAnonymous = TRUE;
}
}
if (!NT_SUCCESS( Status )) {
goto Cleanup;
}
try
{
Status = SamChangePasswordUser2(
UncComputerName,
UserName,
OldPassword,
NewPassword
);
}
except (EXCEPTION_EXECUTE_HANDLER)
{
Status = GetExceptionCode();
}
MsvPaswdLogPrint(("SamChangePasswordUser2 on machine %wZ for user %wZ returned 0x%x\n",
UncComputerName,
UserName,
Status
));
if ( !NT_SUCCESS(Status) ) {
#ifdef COMPILED_BY_DEVELOPER
KdPrint(("MspChangePasswordSam: SamChangePasswordUser2(%wZ) failed, status %x\n",
UncComputerName, Status));
#endif // COMPILED_BY_DEVELOPER
//
// If we failed to connect and we were impersonating a client
// then we may want to try again using the NULL session.
// Only try this if we found a server last try. Otherwise,
// we'll subject our user to another long timeout.
//
if (( Impersonating || RetryAnonymous ) &&
( Status != STATUS_WRONG_PASSWORD ) &&
( Status != STATUS_PASSWORD_RESTRICTION ) &&
( Status != STATUS_ACCOUNT_RESTRICTION ) &&
( Status != RPC_NT_SERVER_UNAVAILABLE) &&
( Status != STATUS_INVALID_DOMAIN_ROLE) ) {
Status = MspImpersonateAnonymous();
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
ImpersonatingAnonymous = TRUE;
Status = SamChangePasswordUser2(
UncComputerName,
UserName,
OldPassword,
NewPassword
);
MsvPaswdLogPrint(("SamChangePasswordUser2 retry on machine %wZ for user %wZ returned 0x%x\n",
UncComputerName,
UserName,
Status
));
#ifdef COMPILED_BY_DEVELOPER
if ( !NT_SUCCESS(Status) ) {
KdPrint(("MspChangePasswordSam: SamChangePasswordUser2(%wZ) (2nd attempt) failed, status %x\n",
UncComputerName, Status));
}
#endif // COMPILED_BY_DEVELOPER
}
}
//
// if we are impersonating Anonymous, RevertToSelf, so the password policy
// fetch attempt occurs using machine/system creds.
//
if( ImpersonatingAnonymous )
{
RevertToSelf();
}
if ( !NT_SUCCESS(Status) ) {
#ifdef COMPILED_BY_DEVELOPER
KdPrint(("MspChangePasswordSam: Cannot change password for %wZ, status %x\n",
UserName, Status));
#endif // COMPILED_BY_DEVELOPER
if (Status == RPC_NT_SERVER_UNAVAILABLE ||
Status == RPC_S_SERVER_UNAVAILABLE ) {
Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
} else if (Status == STATUS_PASSWORD_RESTRICTION) {
//
// don't whack the original status code.
//
NTSTATUS TempStatus;
//
// Get the password restrictions for this domain and return them
//
//
// Get the SID of the account domain from LSA
//
InitializeObjectAttributes( &LSAObjectAttributes,
NULL, // Name
0, // Attributes
NULL, // Root
NULL ); // Security Descriptor
TempStatus = LsaOpenPolicy( UncComputerName,
&LSAObjectAttributes,
POLICY_VIEW_LOCAL_INFORMATION,
&LSAPolicyHandle );
if( !NT_SUCCESS(TempStatus) ) {
KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
UncComputerName, TempStatus));
LSAPolicyHandle = NULL;
goto Cleanup;
}
TempStatus = LsaQueryInformationPolicy(
LSAPolicyHandle,
PolicyAccountDomainInformation,
(PVOID *) &AccountDomainInfo );
if( !NT_SUCCESS(TempStatus) ) {
KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
UncComputerName, TempStatus));
AccountDomainInfo = NULL;
goto Cleanup;
}
//
// Setup ObjectAttributes for SamConnect call.
//
InitializeObjectAttributes(&ObjectAttributes, NULL, 0, 0, NULL);
ObjectAttributes.SecurityQualityOfService = &SecurityQos;
SecurityQos.Length = sizeof(SecurityQos);
SecurityQos.ImpersonationLevel = SecurityIdentification;
SecurityQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
SecurityQos.EffectiveOnly = FALSE;
TempStatus = SamConnect(
UncComputerName,
&SamHandle,
SAM_SERVER_LOOKUP_DOMAIN,
&ObjectAttributes
);
if ( !NT_SUCCESS(TempStatus) ) {
KdPrint(("MspChangePasswordSam: Cannot open sam on %wZ, status %x\n",
UncComputerName, TempStatus));
DomainHandle = NULL;
goto Cleanup;
}
//
// Open the Account domain in SAM.
//
TempStatus = SamOpenDomain(
SamHandle,
DOMAIN_READ_PASSWORD_PARAMETERS,
AccountDomainInfo->DomainSid,
&DomainHandle
);
if ( !NT_SUCCESS(TempStatus) ) {
KdPrint(("MspChangePasswordSam: Cannot open domain on %wZ, status %x\n",
UncComputerName, TempStatus));
DomainHandle = NULL;
goto Cleanup;
}
TempStatus = SamQueryInformationDomain(
DomainHandle,
DomainPasswordInformation,
(PVOID *)DomainPasswordInfo );
if (!NT_SUCCESS(TempStatus)) {
KdPrint(("MspChangePasswordSam: Cannot queryinformationdomain on %wZ, status %x\n",
UncComputerName, TempStatus));
*DomainPasswordInfo = NULL;
} else {
Status = STATUS_PASSWORD_RESTRICTION;
}
}
goto Cleanup;
}
Cleanup:
//
// If the only problem is that this is a BDC,
// Return the domain name back to the caller.
//
if ( (Status == STATUS_BACKUP_CONTROLLER ||
Status == STATUS_INVALID_DOMAIN_ROLE) &&
PrimaryDomainInfo != NULL ) {
NTSTATUS TempStatus;
//
// Open the LSA if we haven't already.
//
if (LSAPolicyHandle == NULL) {
InitializeObjectAttributes( &LSAObjectAttributes,
NULL, // Name
0, // Attributes
NULL, // Root
NULL ); // Security Descriptor
TempStatus = LsaOpenPolicy( UncComputerName,
&LSAObjectAttributes,
POLICY_VIEW_LOCAL_INFORMATION,
&LSAPolicyHandle );
if( !NT_SUCCESS(TempStatus) ) {
KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
UncComputerName, TempStatus));
LSAPolicyHandle = NULL;
}
}
if (LSAPolicyHandle != NULL) {
TempStatus = LsaQueryInformationPolicy(
LSAPolicyHandle,
PolicyPrimaryDomainInformation,
(PVOID *) PrimaryDomainInfo );
if( !NT_SUCCESS(TempStatus) ) {
KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
UncComputerName, TempStatus));
*PrimaryDomainInfo = NULL;
#ifdef COMPILED_BY_DEVELOPER
} else {
KdPrint(("MspChangePasswordSam: %wZ is really a BDC in domain %wZ\n",
UncComputerName, &(*PrimaryDomainInfo)->Name));
#endif // COMPILED_BY_DEVELOPER
}
}
Status = STATUS_BACKUP_CONTROLLER;
}
//
// Check for non-authoritative failures
//
if (( Status != STATUS_ACCESS_DENIED) &&
( Status != STATUS_WRONG_PASSWORD ) &&
( Status != STATUS_NO_SUCH_USER ) &&
( Status != STATUS_PASSWORD_RESTRICTION ) &&
( Status != STATUS_ACCOUNT_RESTRICTION ) &&
( Status != STATUS_INVALID_DOMAIN_ROLE ) &&
( Status != STATUS_ACCOUNT_LOCKED_OUT ) ) {
*Authoritative = FALSE;
}
//
// Stop impersonating.
//
RevertToSelf();
//
// Free Locally used resources
//
if (SamHandle) {
SamCloseHandle(SamHandle);
}
if (DomainHandle) {
SamCloseHandle(DomainHandle);
}
if ( LSAPolicyHandle != NULL ) {
LsaClose( LSAPolicyHandle );
}
if ( AccountDomainInfo != NULL ) {
(VOID) LsaFreeMemory( AccountDomainInfo );
}
return Status;
}
NTSTATUS
MspChangePasswordDownlevel(
IN PUNICODE_STRING UncComputerName,
IN PUNICODE_STRING UserNameU,
IN PUNICODE_STRING OldPasswordU,
IN PUNICODE_STRING NewPasswordU,
OUT PBOOLEAN Authoritative
)
/*++
Routine Description:
This routine is called by MspChangePassword to change the password
on an OS/2 User-level server. First we try sending an encrypted
request to the server, failing that we fall back on plaintext.
Arguments:
UncComputerName - Pointer to Unicode String containing the Name of the
target machine. This name must begin with two backslashes and
must be null terminated.
UserNameU - Name of the user to change password for.
OldPasswordU - Plaintext current password.
NewPasswordU - Plaintext replacement password.
Authoritative - If the attempt failed with an error that would
otherwise cause the password attempt to fail, this flag, if false,
indicates that the error was not authoritative and the attempt
should proceed.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
--*/
{
NTSTATUS Status;
NET_API_STATUS NetStatus;
DWORD Length;
LPWSTR UserName = NULL;
LPWSTR OldPassword = NULL;
LPWSTR NewPassword = NULL;
*Authoritative = TRUE;
//
// Convert UserName from UNICODE_STRING to null-terminated wide string
// for use by RxNetUserPasswordSet.
//
Length = UserNameU->Length;
UserName = I_NtLmAllocate(
Length + sizeof(TCHAR)
);
if ( NULL == UserName ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( UserName, UserNameU->Buffer, Length );
UserName[ Length / sizeof(TCHAR) ] = 0;
//
// Convert OldPassword from UNICODE_STRING to null-terminated wide string.
//
Length = OldPasswordU->Length;
OldPassword = I_NtLmAllocate( Length + sizeof(TCHAR) );
if ( NULL == OldPassword ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( OldPassword, OldPasswordU->Buffer, Length );
OldPassword[ Length / sizeof(TCHAR) ] = 0;
//
// Convert NewPassword from UNICODE_STRING to null-terminated wide string.
//
Length = NewPasswordU->Length;
NewPassword = I_NtLmAllocate( Length + sizeof(TCHAR) );
if ( NULL == NewPassword ) {
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory( NewPassword, NewPasswordU->Buffer, Length );
NewPassword[ Length / sizeof(TCHAR) ] = 0;
#ifdef COMPILED_BY_DEVELOPER
KdPrint(("MSV1_0: Changing password on downlevel server:\n"
"\tUncComputerName: %wZ\n"
"\tUserName: %ws\n"
"\tOldPassword: %ws\n"
"\tNewPassword: %ws\n",
UncComputerName,
UserName,
OldPassword,
NewPassword
));
#endif // COMPILED_BY_DEVELOPER
//
// Attempt to change password on downlevel server.
//
NetStatus = RxNetUserPasswordSet(
UncComputerName->Buffer,
UserName,
OldPassword,
NewPassword);
MsvPaswdLogPrint(("RxNetUserPasswordSet on machine %ws for user %ws returned 0x%x\n",
UncComputerName->Buffer,
UserName,
NetStatus
));
#ifdef COMPILED_BY_DEVELOPER
KdPrint(("MSV1_0: RxNUserPasswordSet returns %d.\n", NetStatus));
#endif // COMPILED_BY_DEVELOPER
// Since we overload the computername as the domain name,
// map NERR_InvalidComputer to STATUS_NO_SUCH_DOMAIN, since
// that will give the user a nice error message.
//
// ERROR_PATH_NOT_FOUND is returned on a standalone workstation that
// doesn't have the network installed.
//
if (NetStatus == NERR_InvalidComputer ||
NetStatus == ERROR_PATH_NOT_FOUND) {
Status = STATUS_NO_SUCH_DOMAIN;
// ERROR_SEM_TIMEOUT can be returned when the computer name doesn't
// exist.
//
// ERROR_REM_NOT_LIST can also be returned when the computer name
// doesn't exist.
//
} else if ( NetStatus == ERROR_SEM_TIMEOUT ||
NetStatus == ERROR_REM_NOT_LIST) {
Status = STATUS_BAD_NETWORK_PATH;
} else if ( (NetStatus == ERROR_INVALID_PARAMETER) &&
((wcslen(NewPassword) > LM20_PWLEN) ||
(wcslen(OldPassword) > LM20_PWLEN)) ) {
//
// The net api returns ERROR_INVALID_PARAMETER if the password
// could not be converted to the LM OWF password. Return
// STATUS_PASSWORD_RESTRICTION for this.
//
Status = STATUS_PASSWORD_RESTRICTION;
//
// We never made it to the other machine, so we should continue
// trying to change the password.
//
*Authoritative = FALSE;
} else {
Status = NetpApiStatusToNtStatus( NetStatus );
}
Cleanup:
//
// Free UserName if used.
//
if (UserName) {
I_NtLmFree(UserName);
}
//
// Free OldPassword if used. (Don't let password make it to page file)
//
if (OldPassword) {
RtlZeroMemory( OldPassword, wcslen(OldPassword) * sizeof(WCHAR) );
I_NtLmFree(OldPassword);
}
//
// Free NewPassword if used. (Don't let password make it to page file)
//
if (NewPassword) {
RtlZeroMemory( NewPassword, wcslen(NewPassword) * sizeof(WCHAR) );
I_NtLmFree(NewPassword);
}
return Status;
}
NTSTATUS
MspChangePassword(
IN OUT PUNICODE_STRING ComputerName,
IN PUNICODE_STRING UserName,
IN PUNICODE_STRING OldPassword,
IN PUNICODE_STRING NewPassword,
IN PLSA_CLIENT_REQUEST ClientRequest,
IN BOOLEAN Impersonating,
OUT PDOMAIN_PASSWORD_INFORMATION *DomainPasswordInfo,
OUT PPOLICY_PRIMARY_DOMAIN_INFO *PrimaryDomainInfo OPTIONAL,
OUT PBOOLEAN Authoritative
)
/*++
Routine Description:
This routine is called by MspLM20ChangePassword to change the password
on the specified server. The server may be either NT or Downlevel.
Arguments:
ComputerName - Name of the target machine. This name may or may not
begin with two backslashes.
UserName - Name of the user to change password for.
OldPassword - Plaintext current password.
NewPassword - Plaintext replacement password.
ClientRequest - Is a pointer to an opaque data structure
representing the client's request.
DomainPasswordInfo - Password restriction information (returned only if
status is STATUS_PASSWORD_RESTRICTION).
PrimaryDomainInfo - DomainNameInformation (returned only if status is
STATUS_BACKUP_CONTROLLER).
Authoritative - Indicates that the error code is authoritative
and it indicates that password changing should stop. If false,
password changing should continue.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_PASSWORD_RESTRICTION - Password changing is restricted.
--*/
{
NTSTATUS Status;
UNICODE_STRING UncComputerName;
*Authoritative = TRUE;
//
// Ensure the server name is a UNC server name.
//
Status = MspAddBackslashesComputerName( ComputerName, &UncComputerName );
if (!NT_SUCCESS(Status)) {
KdPrint(("MspChangePassword: MspAddBackslashes..(%wZ) failed, status %x\n",
ComputerName, Status));
return(Status);
}
//
// Assume the Server is an NT server and try to change the password.
//
Status = MspChangePasswordSam(
&UncComputerName,
UserName,
OldPassword,
NewPassword,
ClientRequest,
Impersonating,
DomainPasswordInfo,
PrimaryDomainInfo,
Authoritative );
//
// If MspChangePasswordSam returns anything other than
// STATUS_CANT_ACCESS_DOMAIN_INFO, it was able to connect
// to the remote computer so we won't try downlevel.
//
if (Status == STATUS_CANT_ACCESS_DOMAIN_INFO) {
NET_API_STATUS NetStatus;
DWORD OptionsSupported;
//
// only if target machine doesn't support SAM protocol do we attempt
// downlevel.
// MspAddBackslashesComputerName() NULL terminates the buffer.
//
NetStatus = NetRemoteComputerSupports(
(LPWSTR)UncComputerName.Buffer,
SUPPORTS_RPC | SUPPORTS_LOCAL | SUPPORTS_SAM_PROTOCOL,
&OptionsSupported
);
if ( NetStatus == NERR_Success && !(OptionsSupported & SUPPORTS_SAM_PROTOCOL) ) {
Status = MspChangePasswordDownlevel(
&UncComputerName,
UserName,
OldPassword,
NewPassword,
Authoritative );
}
}
//
// Free UncComputerName.Buffer if different from ComputerName.
//
if ( UncComputerName.Buffer != ComputerName->Buffer ) {
I_NtLmFree(UncComputerName.Buffer);
}
return(Status);
}
//
// the following structures are used to map win32 errors to NTSTATUS
//
typedef LONG (WINAPI * I_RPCMAPWIN32STATUS)(
IN ULONG Win32Status
);
typedef struct _STATUS_MAPPING {
DWORD Error;
NTSTATUS NtStatus;
} STATUS_MAPPING;
NTSTATUS
MspMapNtdsApiError(
IN DWORD DsStatus,
IN NTSTATUS DefaultStatus
)
/*++
Routine Description:
This routine maps DS API error codes to appropriate NTSTATUS codes
Arguments:
DsStatus - Status code from DS APIs
DefaultStatus - Default status code if no other code is found
Return Value:
NtStatus code
--*/
{
NTSTATUS Status = DsStatus;
I_RPCMAPWIN32STATUS pFuncI_RpcMapWin32Status = NULL;
HMODULE hLib = NULL;
static const STATUS_MAPPING StatusMap[] = {
{ERROR_NO_SUCH_DOMAIN, STATUS_NO_SUCH_DOMAIN},
{ERROR_INVALID_DOMAINNAME, STATUS_INVALID_PARAMETER},
{DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING, STATUS_NO_SUCH_USER},
{ERROR_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL}
};
if (SUCCEEDED(Status))
{
int i;
//
// handle expected win32 errors
//
for (i = 0; i < RTL_NUMBER_OF(StatusMap); i++)
{
if (StatusMap[i].Error == (DWORD) Status)
{
return (StatusMap[i].NtStatus);
}
}
//
// handle RPC status
//
hLib = LoadLibraryW(L"rpcrt4.dll");
if (hLib)
{
pFuncI_RpcMapWin32Status = (I_RPCMAPWIN32STATUS) GetProcAddress( hLib, "I_RpcMapWin32Status" );
if (pFuncI_RpcMapWin32Status)
{
Status = pFuncI_RpcMapWin32Status(Status);
}
FreeLibrary(hLib);
}
}
//
// not mapped? use default status
//
if (NT_SUCCESS(Status) || (Status == STATUS_UNSUCCESSFUL))
{
Status = DefaultStatus;
}
return Status;
}
NTSTATUS
MspImpersonateNetworkService(
VOID
)
/*++
Routine Description:
This routine impersonates network servcie.
Arguments:
None
Return Value:
NtStatus code
--*/
{
NTSTATUS Status;
HANDLE TokenHandle = NULL;
LUID NetworkServiceLuid = NETWORKSERVICE_LUID;
Status = LsaFunctions->OpenTokenByLogonId(&NetworkServiceLuid, &TokenHandle);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
&TokenHandle,
sizeof(TokenHandle)
);
if (!NT_SUCCESS( Status))
{
goto Cleanup;
}
Cleanup:
if (TokenHandle)
{
NtClose(TokenHandle);
}
return Status;
}
BOOL
MspIsRpcServerUnavailableError(
IN DWORD Error
)
/*++
Routine Description:
This routine determines if an error code means the server is not available.
Arguments:
Error - An RPC status code or Win32 error code
Return Value:
True if the error code means the server is not avaiable and false otherwise
--*/
{
// This list of error codes blessed by MazharM on 4/20/99.
switch ( Error )
{
case RPC_S_SERVER_UNAVAILABLE: // can't get there from here
case EPT_S_NOT_REGISTERED: // demoted or in DS repair mode
case RPC_S_UNKNOWN_IF: // demoted or in DS repair mode
case RPC_S_INTERFACE_NOT_FOUND: // demoted or in DS repair mode
case RPC_S_COMM_FAILURE: // can't get there from here
return (TRUE);
}
return (FALSE);
}
NTSTATUS
MspConstructSPN(
IN PCWSTR DomainControllerName,
IN PCWSTR DnsDomainName,
OUT PWSTR * Spn
)
/*++
Routine Description:
This routine constructs a SPN with the "@domainName" suffix. The suffix
serves as a hint for kerberos.
Arguments:
DomainControllerName - Name of the domain controller
DnsDomainName - DNS domain name
Spn - Receives SPN for the domain controller at DNS domain name
Return Value:
NTSTATUS code
--*/
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
DWORD Error = ERROR_SUCCESS;
DWORD CharCount = 0;
DWORD TotalCharCount = 0;
PCWSTR ServiceName = NULL;
PCWSTR InstanceName = NULL;
PCWSTR SvcClass = L"ldap";
//
// the following temporary structures need to be free'ed
//
PWSTR TmpSpn = NULL;
PWSTR TmpService = NULL;
PWSTR TmpInstance = NULL;
if (!DomainControllerName)
{
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
if ( DnsDomainName )
{
// Caller gave all components needed to construct full 3-part SPN.
InstanceName = DomainControllerName;
ServiceName = DnsDomainName;
}
else
{
// Construct SPN of form: LDAP/ntdsdc4.ntdev.microsoft.com
InstanceName = DomainControllerName;
ServiceName = DomainControllerName;
}
//
// Skip past leading "\\" if present. This is not circumventing
// a client who has passed NetBIOS names mistakenly but rather
// helping the client which has passed args as returned by
// DsGetDcName which prepends "\\" even when DS_RETURN_DNS_NAME
// was requested.
//
if (0 == wcsncmp(InstanceName, L"\\\\", 2))
{
InstanceName += 2;
}
if (0 == wcsncmp(ServiceName, L"\\\\", 2))
{
ServiceName += 2;
}
//
// Strip trailing '.' if it exists. We do this as we know the server side
// registers dot-less names only. We can't whack in place as the input
// args are const.
//
CharCount = (ULONG) wcslen(InstanceName);
if ( L'.' == InstanceName[CharCount - 1] )
{
TmpInstance = (WCHAR *) NtLmAllocatePrivateHeap(CharCount * sizeof(WCHAR));
if (!TmpInstance)
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory(TmpInstance, InstanceName, (CharCount - 1) * sizeof(WCHAR));
TmpInstance[CharCount - 1] = L'\0';
InstanceName = TmpInstance;
}
CharCount = (ULONG) wcslen(ServiceName);
if ( L'.' == ServiceName[CharCount - 1] )
{
TmpService = (WCHAR *) NtLmAllocatePrivateHeap(CharCount * sizeof(WCHAR));
if (!TmpService)
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
RtlCopyMemory(TmpService, ServiceName, (CharCount - 1) * sizeof(WCHAR));
TmpService[CharCount - 1] = L'\0';
ServiceName = TmpService;
}
CharCount = 0;
Error = DsMakeSpnW(SvcClass, ServiceName, InstanceName, 0,
NULL, &CharCount, NULL);
if ( Error != ERROR_SUCCESS && (ERROR_BUFFER_OVERFLOW != Error) )
{
Status = MspMapNtdsApiError(Error, STATUS_INVALID_PARAMETER);
goto Cleanup;
}
if (DnsDomainName)
{
TotalCharCount = CharCount + 1 + (ULONG) wcslen(ServiceName); // 1 char for @ sign
}
else
{
TotalCharCount = CharCount;
}
TmpSpn = (WCHAR *) NtLmAllocatePrivateHeap(sizeof(WCHAR) * TotalCharCount);
if ( !TmpSpn )
{
Status = STATUS_NO_MEMORY;
goto Cleanup;
}
Error = DsMakeSpnW(SvcClass, ServiceName, InstanceName, 0,
NULL, &CharCount, TmpSpn);
if ( Error != ERROR_SUCCESS)
{
Status = MspMapNtdsApiError(Error, STATUS_INVALID_PARAMETER);
goto Cleanup;
}
if (DnsDomainName && (CharCount < TotalCharCount))
{
wcsncat(TmpSpn, L"@", TotalCharCount - CharCount);
wcsncat(TmpSpn, ServiceName, TotalCharCount - CharCount - 1);
}
Status = STATUS_SUCCESS;
*Spn = TmpSpn;
TmpSpn = NULL; // do not free it
Cleanup:
if (TmpInstance)
{
NtLmFreePrivateHeap(TmpInstance);
}
if (TmpService)
{
NtLmFreePrivateHeap(TmpService);
}
if (TmpSpn) {
NtLmFreePrivateHeap(TmpSpn);
}
return Status;
}
//
// maximum number of DC force rediscovery retries for cracking UPNs in changepassword
//
#define MAX_DC_REDISCOVERY_RETRIES 2
//
// determine if it is an authentication error, here I leverage two CREDUI macros
//
// ISSUE: Is downgrade error fatal? (CREDUI_IS_AUTHENTICATION_ERROR includes
// downgrade errors)
//
#define IS_BAD_CREDENTIALS_ERROR(x) (CREDUI_NO_PROMPT_AUTHENTICATION_ERROR((x)) \
|| CREDUI_IS_AUTHENTICATION_ERROR((x)))
NTSTATUS
MspLm20ChangePassword (
IN PLSA_CLIENT_REQUEST ClientRequest,
IN PVOID ProtocolSubmitBuffer,
IN PVOID ClientBufferBase,
IN ULONG SubmitBufferSize,
OUT PVOID *ProtocolReturnBuffer,
OUT PULONG ReturnBufferSize,
OUT PNTSTATUS ProtocolStatus
)
/*++
Routine Description:
This routine is the dispatch routine for LsaCallAuthenticationPackage()
with a message type of MsV1_0ChangePassword. This routine changes an
account's password by either calling SamXxxPassword (for NT domains) or
RxNetUserPasswordSet (for downlevel domains and standalone servers).
Arguments:
The arguments to this routine are identical to those of LsaApCallPackage.
Only the special attributes of these parameters as they apply to
this routine are mentioned here.
Return Value:
STATUS_SUCCESS - Indicates the service completed successfully.
STATUS_PASSWORD_RESTRICTION - The password change failed because the
- password doesn't meet one or more domain restrictions. The
- response buffer is allocated. If the PasswordInfoValid flag is
- set it contains valid information otherwise it contains no
- information because this was a down-level change.
STATUS_BACKUP_CONTROLLER - The named machine is a Backup Domain Controller.
Changing password is only allowed on the Primary Domain Controller.
--*/
{
PMSV1_0_CHANGEPASSWORD_REQUEST ChangePasswordRequest = NULL;
PMSV1_0_CHANGEPASSWORD_RESPONSE ChangePasswordResponse;
NTSTATUS Status = STATUS_SUCCESS;
NTSTATUS SavedStatus = STATUS_SUCCESS;
LPWSTR DomainName = NULL;
PDOMAIN_CONTROLLER_INFO DCInfo = NULL;
UNICODE_STRING DCNameString;
UNICODE_STRING ClientNetbiosDomain = {0};
PUNICODE_STRING ClientDsGetDcDomain;
UNICODE_STRING ClientDnsDomain = {0};
UNICODE_STRING ClientUpn = {0};
UNICODE_STRING ClientName = {0};
UNICODE_STRING ValidatedAccountName;
UNICODE_STRING ValidatedDomainName;
LPWSTR ValidatedOldPasswordBuffer;
LPWSTR ValidatedNewPasswordBuffer;
NET_API_STATUS NetStatus;
PPOLICY_LSA_SERVER_ROLE_INFO PolicyLsaServerRoleInfo = NULL;
PDOMAIN_PASSWORD_INFORMATION DomainPasswordInfo = NULL;
PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo = NULL;
PWKSTA_INFO_100 WkstaInfo100 = NULL;
BOOLEAN PasswordBufferValidated = FALSE;
CLIENT_BUFFER_DESC ClientBufferDesc;
PSECURITY_SEED_AND_LENGTH SeedAndLength;
PDS_NAME_RESULTW NameResult = NULL;
HANDLE DsHandle = NULL;
PWSTR SpnForDC = NULL;
UCHAR Seed;
BOOLEAN Authoritative = TRUE;
BOOLEAN AttemptRediscovery = FALSE;
BOOLEAN Validated = TRUE;
#if _WIN64
PVOID pTempSubmitBuffer = ProtocolSubmitBuffer;
SECPKG_CALL_INFO CallInfo;
BOOL fAllocatedSubmitBuffer = FALSE;
#endif
RtlInitUnicodeString(
&DCNameString,
NULL
);
//
// Sanity checks.
//
NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
#if _WIN64
//
// Expand the ProtocolSubmitBuffer to 64-bit pointers if this
// call came from a WOW client.
//
if (!LsaFunctions->GetCallInfo(&CallInfo))
{
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
{
Status = MsvConvertWOWChangePasswordBuffer(ProtocolSubmitBuffer,
ClientBufferBase,
&SubmitBufferSize,
&pTempSubmitBuffer);
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
fAllocatedSubmitBuffer = TRUE;
//
// Some macros below expand out to use ProtocolSubmitBuffer directly.
// We've secretly replaced their usual ProtocolSubmitBuffer with
// pTempSubmitBuffer -- let's see if they can tell the difference.
//
ProtocolSubmitBuffer = pTempSubmitBuffer;
}
#endif // _WIN64
if ( SubmitBufferSize < sizeof(MSV1_0_CHANGEPASSWORD_REQUEST) ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
ChangePasswordRequest = (PMSV1_0_CHANGEPASSWORD_REQUEST) ProtocolSubmitBuffer;
ASSERT( ChangePasswordRequest->MessageType == MsV1_0ChangePassword ||
ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword );
RELOCATE_ONE( &ChangePasswordRequest->DomainName );
RELOCATE_ONE( &ChangePasswordRequest->AccountName );
if ( ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ) {
NULL_RELOCATE_ONE( &ChangePasswordRequest->OldPassword );
} else {
RELOCATE_ONE_ENCODED( &ChangePasswordRequest->OldPassword );
}
RELOCATE_ONE_ENCODED( &ChangePasswordRequest->NewPassword );
//
// save away copies of validated buffers to check later.
//
RtlCopyMemory( &ValidatedDomainName, &ChangePasswordRequest->DomainName, sizeof(ValidatedDomainName) );
RtlCopyMemory( &ValidatedAccountName, &ChangePasswordRequest->AccountName, sizeof(ValidatedAccountName) );
ValidatedOldPasswordBuffer = ChangePasswordRequest->OldPassword.Buffer;
ValidatedNewPasswordBuffer = ChangePasswordRequest->NewPassword.Buffer;
SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangePasswordRequest->OldPassword.Length;
Seed = SeedAndLength->Seed;
SeedAndLength->Seed = 0;
if (Seed != 0) {
try {
RtlRunDecodeUnicodeString(
Seed,
&ChangePasswordRequest->OldPassword
);
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = STATUS_ILL_FORMED_PASSWORD;
goto Cleanup;
}
}
SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangePasswordRequest->NewPassword.Length;
Seed = SeedAndLength->Seed;
SeedAndLength->Seed = 0;
if (Seed != 0) {
if ( ChangePasswordRequest->NewPassword.Buffer !=
ValidatedNewPasswordBuffer ) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
try {
RtlRunDecodeUnicodeString(
Seed,
&ChangePasswordRequest->NewPassword
);
} except (EXCEPTION_EXECUTE_HANDLER) {
Status = STATUS_ILL_FORMED_PASSWORD;
goto Cleanup;
}
}
//
// sanity check that we didn't whack over buffers.
//
if (!RtlCompareMemory(
&ValidatedDomainName,
&ChangePasswordRequest->DomainName,
sizeof(ValidatedDomainName)
)
||
!RtlCompareMemory(
&ValidatedAccountName,
&ChangePasswordRequest->AccountName,
sizeof(ValidatedAccountName)
)
||
(ValidatedOldPasswordBuffer != ChangePasswordRequest->OldPassword.Buffer)
||
(ValidatedNewPasswordBuffer != ChangePasswordRequest->NewPassword.Buffer)
) {
Status= STATUS_INVALID_PARAMETER;
goto Cleanup;
}
//
// validate domain name and account name. Account name allows UPN
//
if ( (ChangePasswordRequest->DomainName.Length / sizeof(WCHAR) > DNS_MAX_NAME_LENGTH)
|| (ChangePasswordRequest->AccountName.Length / sizeof(WCHAR) > (UNLEN + 1 + DNS_MAX_NAME_LENGTH)) )
{
SspPrint((SSP_CRITICAL, "MspLm20ChangePassword invalid parameter: DomainName.Length %#x, AccountName.Length %#x\n",
ChangePasswordRequest->DomainName.Length, ChangePasswordRequest->AccountName.Length));
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
*ReturnBufferSize = 0;
*ProtocolReturnBuffer = NULL;
*ProtocolStatus = STATUS_PENDING;
PasswordBufferValidated = TRUE;
MsvPaswdLogPrint(("Attempting password change server/domain %wZ for user %wZ\n",
&ChangePasswordRequest->DomainName,
&ChangePasswordRequest->AccountName
));
#ifdef COMPILED_BY_DEVELOPER
KdPrint(("MSV1_0:\n"
"\tDomain:\t%wZ\n"
"\tAccount:\t%wZ\n"
"\tOldPassword(%d)\n"
"\tNewPassword(%d)\n",
&ChangePasswordRequest->DomainName,
&ChangePasswordRequest->AccountName,
(int) ChangePasswordRequest->OldPassword.Length,
(int) ChangePasswordRequest->NewPassword.Length
));
#endif // COMPILED_BY_DEVELOPER
SspPrint((SSP_UPDATES, "MspLm20ChangePassword %wZ\\%wZ, message type %#x%s, impersonating ? %s\n",
&ChangePasswordRequest->DomainName,
&ChangePasswordRequest->AccountName,
ChangePasswordRequest->MessageType,
(MsV1_0ChangeCachedPassword == ChangePasswordRequest->MessageType) ? " (cached)" : "",
ChangePasswordRequest->Impersonating ? "true" : "false"));
//
// If the client supplied a non-nt4 name, go ahead and convert it here.
//
if (ChangePasswordRequest->DomainName.Length == 0) {
DWORD DsStatus;
HANDLE NullTokenHandle = NULL;
WCHAR NameBuffer[UNLEN + 1];
ULONG Index;
BOOLEAN useSimpleCrackName = FALSE;
PWSTR DcName = NULL;
PWSTR DomainName = NULL;
DWORD DsGetDcNameFlags = DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME;
BOOLEAN StandaloneWorkstation = FALSE;
DWORD DcRediscoveryRetries = 0;
if (ChangePasswordRequest->AccountName.Length / sizeof(WCHAR) > UNLEN) {
Status = STATUS_INVALID_PARAMETER;
goto Cleanup;
}
RtlCopyMemory(
NameBuffer,
ChangePasswordRequest->AccountName.Buffer,
ChangePasswordRequest->AccountName.Length
);
NameBuffer[ChangePasswordRequest->AccountName.Length/sizeof(WCHAR)] = L'\0';
RtlInitUnicodeString( &ClientUpn, NameBuffer );
if ( NlpWorkstation ) {
RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
StandaloneWorkstation = (BOOLEAN) (NtLmGlobalTargetFlags == NTLMSSP_TARGET_TYPE_SERVER);
RtlReleaseResource(&NtLmGlobalCritSect);
}
if (StandaloneWorkstation) {
SspPrint(( SSP_WARNING, "MspLm20ChangePassword use simple crack for standalone machines\n" ));
useSimpleCrackName = TRUE;
}
//
// bind and crack names
//
// we allow the bind to fail on a member workstation, which allows
// us to attempt a 'manual' crack.
//
while (!useSimpleCrackName) {
if (DcName == NULL) {
if ( DCInfo != NULL ) {
NetApiBufferFree(DCInfo);
DCInfo = NULL;
}
SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: DsGetDcNameW for %ws, DsGetDcNameFlags %#x\n", DomainName, DsGetDcNameFlags ));
NetStatus = DsGetDcNameW(
NULL, // no computername
DomainName,
NULL, // no domain guid
NULL, // no site name
DsGetDcNameFlags,
&DCInfo
);
if (NetStatus == NERR_Success) {
DcName = DCInfo->DomainControllerName;
} else {
SspPrint(( SSP_WARNING, "MspLm20ChangePassword: did not find a DC for %ws, NetStatus %#x\n", DomainName, NetStatus ));
if (!DomainName && !NlpWorkstation) { // DC can not bind to local forest is fatal
Status = NetpApiStatusToNtStatus(NetStatus);
if ( Status == STATUS_INTERNAL_ERROR ) {
Status = STATUS_NO_SUCH_DOMAIN;
}
goto Cleanup;
}
useSimpleCrackName = TRUE;
break;
}
}
if (DsHandle) {
DsUnBindW(
&DsHandle
);
DsHandle = NULL;
}
if (SpnForDC) {
NtLmFreePrivateHeap(SpnForDC);
SpnForDC = NULL;
}
Status = MspConstructSPN(DcName, DomainName, &SpnForDC);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
//
// impersonate for DsBind:
//
// 1) use machine credentials first, this can fail for
// workstations in resource domains with one way trust
// 2) if machine credentials does not work, try to impersonate
// the client iff asked to do so
//
// Notes:
//
// the client can have a wrong password that he/she is trying to
// change, this can happen when the workstation is unlocked with
// a new password and the unlock is validated by NTLM (which does
// not parse unlock logonId or update old logon session credentials
// at this point) or in most common cases, the password is either
// expired or must be changed at the next logon etc
//
Status = MspImpersonateNetworkService(); // note if we get here, the machine is always joined
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: binding to %ws with machine identity, spn %ws\n", DcName, SpnForDC ));
DsStatus = DsBindWithSpnExW(
DcName, // DC name
DcName == NULL ? DomainName : NULL, // domain name
NULL, // no AuthIdentity
SpnForDC, // SPN
0, // No delegation
&DsHandle
);
if ( IS_BAD_CREDENTIALS_ERROR(DsStatus) && ChangePasswordRequest->Impersonating ) {
Status = LsaFunctions->ImpersonateClient();
if (!NT_SUCCESS(Status)) {
NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
&NullTokenHandle,
sizeof(NullTokenHandle)
);
goto Cleanup;
}
SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: dsbind failed with %#x, rebinding to %ws with client identity, spn %ws\n", DsStatus, DcName, SpnForDC ));
DsStatus = DsBindWithSpnExW(
DcName, // DC name
DcName == NULL ? DomainName : NULL, // domain name
NULL, // no AuthIdentity
SpnForDC, // SPN
0, // No delegation
&DsHandle
);
}
//
// always revert to self
//
Status = NtSetInformationThread(
NtCurrentThread(),
ThreadImpersonationToken,
&NullTokenHandle,
sizeof(NullTokenHandle)
);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
if (DsStatus != ERROR_SUCCESS) {
SspPrint(( SSP_WARNING, "MspLm20ChangePassword: could not bind %#x\n", DsStatus ));
if ( MspIsRpcServerUnavailableError(DsStatus) ) {
if ( DCInfo != NULL ) {
NetApiBufferFree(DCInfo);
DCInfo = NULL;
}
SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: force re-dicover DCs for %ws, DsGetDcNameFlags %#x\n", DomainName, DsGetDcNameFlags | DS_FORCE_REDISCOVERY ));
NetStatus = DsGetDcNameW(
NULL, // no computername
DomainName,
NULL, // no domain guid
NULL, // no site name
DsGetDcNameFlags | DS_FORCE_REDISCOVERY,
&DCInfo
);
//
// try again if a different DC is found
//
if (NetStatus == NERR_Success ) {
ASSERT(DcName != NULL);
if (_wcsicmp(DcName, DCInfo->DomainControllerName) != 0) {
if (++DcRediscoveryRetries <= MAX_DC_REDISCOVERY_RETRIES) {
DcName = DCInfo->DomainControllerName;
continue;
} else {
SspPrint(( SSP_WARNING, "MspLm20ChangePassword: exceeded retry limits %#x\n", DcRediscoveryRetries ));
}
}
//
// use simple crack if necessary
//
} else { // pick up the new error code if rediscovery fails, use simple crack if necessary
DsStatus = NetpApiStatusToNtStatus(NetStatus);
if ( DsStatus == STATUS_INTERNAL_ERROR ) {
DsStatus = (DWORD) STATUS_NO_SUCH_DOMAIN;
}
}
SspPrint(( SSP_WARNING, "MspLm20ChangePassword: could not redicovery a DC %#x\n", NetStatus ));
}
if ( !DomainName && !NlpWorkstation ) { // DC can not bind to local forest is fatal
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsBindW returned 0x%lx.\n", DsStatus ));
Status = MspMapNtdsApiError( DsStatus, STATUS_NO_SUCH_DOMAIN );
goto Cleanup;
}
useSimpleCrackName = TRUE;
break;
}
if (NameResult) {
DsFreeNameResult(NameResult);
NameResult = NULL;
}
DsStatus = DsCrackNamesW(
DsHandle,
DomainName ? 0 : DS_NAME_FLAG_TRUST_REFERRAL, // do not follow referral in the remote forest
DS_UNKNOWN_NAME,
DS_NT4_ACCOUNT_NAME,
1,
&ClientUpn.Buffer,
&NameResult
);
if (DsStatus != ERROR_SUCCESS) {
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW returned 0x%lx.\n", DsStatus ));
Status = MspMapNtdsApiError( DsStatus, STATUS_NO_SUCH_DOMAIN );
goto Cleanup;
}
//
// Look for the name in the result
//
if (NameResult->cItems != 1) {
ASSERT(!"Not exactly one result returned, this can not happen");
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW returned not exactly 1 item\n" ));
Status = STATUS_INTERNAL_ERROR;
goto Cleanup;
}
//
// if not cracked on DC, try GC if it is avaiable
//
if (NameResult->rItems[0].status == DS_NAME_ERROR_NOT_FOUND
|| NameResult->rItems[0].status == DS_NAME_ERROR_DOMAIN_ONLY) {
if ( DCInfo != NULL ) {
if (DCInfo->Flags & DS_GC_FLAG) {
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW failed on GC %#x\n", NameResult->rItems[0].status ));
useSimpleCrackName = TRUE;
break;
}
NetApiBufferFree(DCInfo);
DCInfo = NULL;
}
DsGetDcNameFlags |= DS_GC_SERVER_REQUIRED;
NetStatus = DsGetDcNameW(
NULL, // no computername
DomainName,
NULL, // no domain guid
NULL, // no site name
DsGetDcNameFlags,
&DCInfo
);
if (NetStatus == NERR_Success) {
DcName = DCInfo->DomainControllerName;
continue; // try to crack name again with GC
} else {
SspPrint(( SSP_WARNING, "MspLm20ChangePassword: could not find GC %#x\n", NetStatus ));
useSimpleCrackName = TRUE; // crack name fails, use manual crack
break;
}
} else if (!DomainName && (NameResult->rItems[0].status == DS_NAME_ERROR_TRUST_REFERRAL)) { // follow referral only in the local forest
//
// always try GC in the remote forest, this assumes there must
// be at lest one GC in a forest
//
DcName = NULL;
DsGetDcNameFlags = DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME | DS_GC_SERVER_REQUIRED;
DomainName = NameResult->rItems[0].pDomain;
continue; // try again to follow the referral path
} else if (NameResult->rItems[0].status != DS_NAME_NO_ERROR) {
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW failed %#x\n", NameResult->rItems[0].status ));
useSimpleCrackName = TRUE; // crack name fails, use manual crack
break;
}
//
// crack name succeeded, break out
//
ASSERT(useSimpleCrackName == FALSE);
break;
}
if (DsHandle != NULL) {
DsUnBindW(
&DsHandle
);
DsHandle = NULL;
}
if ( DCInfo != NULL ) {
NetApiBufferFree(DCInfo);
DCInfo = NULL;
}
if ( useSimpleCrackName ) { // crack name failed
SspPrint(( SSP_WARNING, "MspLm20ChangePassword: using simple crack\n" ));
//
// The name wasn't mapped. Try converting it manually by
// splitting it at the '@'
//
RtlInitUnicodeString(
&ClientName,
NameBuffer
);
// shortest possible is 3 Unicode chars (eg: a@a)
if (ClientName.Length < (sizeof(WCHAR) * 3)) {
Status = STATUS_NO_SUCH_USER;
goto Cleanup;
}
for (Index = (ClientName.Length / sizeof(WCHAR)) - 1; Index != 0 ; Index-- ) {
if (ClientName.Buffer[Index] == L'@') {
RtlInitUnicodeString(
&ClientDnsDomain,
&ClientName.Buffer[Index+1]
);
ClientName.Buffer[Index] = L'\0';
ClientName.Length = (USHORT) Index * sizeof(WCHAR);
break;
}
}
//
// If the name couldn't be parsed, give up and go home
//
if (ClientDnsDomain.Length == 0) {
Status = STATUS_NO_SUCH_USER;
goto Cleanup;
}
//
// This isn't really the Netbios Domain name, but it is the best we have.
//
ClientNetbiosDomain = ClientDnsDomain;
for (Index = 0; Index < (ClientNetbiosDomain.Length / sizeof(WCHAR)) ; Index++ ) {
//
// truncate the netbios domain to the first DOT
//
if ( ClientNetbiosDomain.Buffer[Index] == L'.' ) {
ClientNetbiosDomain.Length = (USHORT)(Index * sizeof(WCHAR));
ClientNetbiosDomain.MaximumLength = ClientNetbiosDomain.Length;
break;
}
}
}
else // crack name succeeded, look for the cracked results
{
RtlInitUnicodeString(
&ClientDnsDomain,
NameResult->rItems[0].pDomain
);
RtlInitUnicodeString(
&ClientName,
NameResult->rItems[0].pName
);
RtlInitUnicodeString(
&ClientNetbiosDomain,
NameResult->rItems[0].pName
);
//
// Move the pointer for the name up to the first "\" in the name
//
for (Index = 0; Index < ClientName.Length / sizeof(WCHAR) ; Index++ ) {
if (ClientName.Buffer[Index] == L'\\') {
RtlInitUnicodeString(
&ClientName,
&ClientName.Buffer[Index+1]
);
// Set the Netbios Domain Name to the string to the left of the backslash
ClientNetbiosDomain.Length = (USHORT)(Index * sizeof(WCHAR));
break;
}
}
}
SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: UPN cracked %wZ\\%wZ, %wZ\n", &ClientNetbiosDomain, &ClientName, &ClientDnsDomain ));
} else {
ClientName = ChangePasswordRequest->AccountName;
ClientNetbiosDomain = ChangePasswordRequest->DomainName;
}
//
// If we're just changing the cached password, skip changing the password
// on the domain.
//
if ( ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ) {
Status = STATUS_SUCCESS;
Validated = FALSE;
goto PasswordChangeSuccessfull;
}
// Make sure that NlpSamInitialized is TRUE. If we logon using
// Kerberos, this may not be true.
if ( !NlpSamInitialized)
{
Status = NlSamInitialize( SAM_STARTUP_TIME );
if (!NT_SUCCESS(Status))
{
goto Cleanup;
}
}
//
// Check to see if the name provided is a domain name. If it has no
// leading "\\" and does not match the name of the computer, it may be.
//
if ((( ClientNetbiosDomain.Length < 3 * sizeof(WCHAR)) ||
( ClientNetbiosDomain.Buffer[0] != L'\\' &&
ClientNetbiosDomain.Buffer[1] != L'\\' ) ) &&
!RtlEqualDomainName(
&NlpComputerName,
&ClientNetbiosDomain )) {
//
// Check if we are a DC in this domain.
// If so, use this DC.
//
if ( !NlpWorkstation &&
RtlEqualDomainName(
&NlpSamDomainName,
&ClientNetbiosDomain )) {
DCNameString = NlpComputerName;
}
if (DCNameString.Buffer == NULL) {
if ( ClientDnsDomain.Length != 0 ) {
ClientDsGetDcDomain = &ClientDnsDomain;
} else {
ClientDsGetDcDomain = &ClientNetbiosDomain;
}
//
// Build a zero terminated domain name.
//
DomainName = I_NtLmAllocate(
ClientDsGetDcDomain->Length + sizeof(WCHAR)
);
if ( DomainName == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory( DomainName,
ClientDsGetDcDomain->Buffer,
ClientDsGetDcDomain->Length );
DomainName[ClientDsGetDcDomain->Length / sizeof(WCHAR)] = 0;
NetStatus = DsGetDcNameW(
NULL,
DomainName,
NULL, // no domain guid
NULL, // no site name
DS_WRITABLE_REQUIRED,
&DCInfo );
if ( NetStatus != NERR_Success ) {
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW %ws returned %#x\n", DomainName, NetStatus ));
Status = NetpApiStatusToNtStatus( NetStatus );
if ( Status == STATUS_INTERNAL_ERROR )
Status = STATUS_NO_SUCH_DOMAIN;
} else {
RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
}
//
// ISSUE: attempting DC rediscovery when DsGetDcName failed or
// password change failed non authoritatively this seemed to be
// wrong
//
AttemptRediscovery = TRUE;
}
if (NT_SUCCESS(Status)) {
Status = MspChangePassword(
&DCNameString,
&ClientName,
&ChangePasswordRequest->OldPassword,
&ChangePasswordRequest->NewPassword,
ClientRequest,
ChangePasswordRequest->Impersonating,
&DomainPasswordInfo,
NULL,
&Authoritative );
//
// If we succeeded or got back an authoritative answer
if ( NT_SUCCESS(Status) || Authoritative) {
goto PasswordChangeSuccessfull;
}
}
}
//
// Free the DC info so we can call DsGetDcName again.
//
if ( DCInfo != NULL ) {
NetApiBufferFree(DCInfo);
DCInfo = NULL;
}
//
// attempt re-discovery.
//
if ( AttemptRediscovery ) {
NetStatus = DsGetDcNameW(
NULL,
DomainName,
NULL, // no domain guid
NULL, // no site name
DS_FORCE_REDISCOVERY | DS_WRITABLE_REQUIRED,
&DCInfo );
if ( NetStatus != NERR_Success ) {
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW (re-discover) %ws returned %#x\n", DomainName, NetStatus ));
DCInfo = NULL;
Status = NetpApiStatusToNtStatus( NetStatus );
if ( Status == STATUS_INTERNAL_ERROR )
Status = STATUS_NO_SUCH_DOMAIN;
} else {
RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
Status = MspChangePassword(
&DCNameString,
&ClientName,
&ChangePasswordRequest->OldPassword,
&ChangePasswordRequest->NewPassword,
ClientRequest,
ChangePasswordRequest->Impersonating,
&DomainPasswordInfo,
NULL,
&Authoritative );
//
// If we succeeded or got back an authoritative answer
if ( NT_SUCCESS(Status) || Authoritative) {
goto PasswordChangeSuccessfull;
}
//
// Free the DC info so we can call DsGetDcName again.
//
if ( DCInfo != NULL ) {
NetApiBufferFree(DCInfo);
DCInfo = NULL;
}
}
}
if (Status != STATUS_BACKUP_CONTROLLER) {
//
// Change the password assuming the DomainName is really a server name
//
// The domain name is overloaded to be either a domain name or a server
// name. The server name is useful when changing the password on a LM2.x
// standalone server, which is a "member" of a domain but uses a private
// account database.
//
Status = MspChangePassword(
&ClientNetbiosDomain,
&ClientName,
&ChangePasswordRequest->OldPassword,
&ChangePasswordRequest->NewPassword,
ClientRequest,
ChangePasswordRequest->Impersonating,
&DomainPasswordInfo,
&PrimaryDomainInfo,
&Authoritative );
//
// If DomainName is actually a server name,
// just return the status to the caller.
//
if ( Authoritative &&
( Status != STATUS_BAD_NETWORK_PATH ||
( ClientNetbiosDomain.Length >= 3 * sizeof(WCHAR) &&
ClientNetbiosDomain.Buffer[0] == L'\\' &&
ClientNetbiosDomain.Buffer[1] == L'\\' ) ) ) {
//
// If \\xxx was specified, but xxx doesn't exist,
// return the status code that the DomainName field is bad.
//
if ( Status == STATUS_BAD_NETWORK_PATH ) {
Status = STATUS_NO_SUCH_DOMAIN;
}
}
//
// If we didn't get an error that this was a backup controller,
// we are out of here.
//
if (Status != STATUS_BACKUP_CONTROLLER) {
goto PasswordChangeSuccessfull;
}
}
//
// If the specified machine was a BDC in a domain,
// Pretend the caller passed us the domain name in the first place.
//
if ( Status == STATUS_BACKUP_CONTROLLER && PrimaryDomainInfo != NULL ) {
ClientNetbiosDomain = PrimaryDomainInfo->Name;
Status = STATUS_BAD_NETWORK_PATH;
} else {
goto PasswordChangeSuccessfull;
}
//
// Build a zero terminated domain name.
//
// BUGBUG: Should really pass both names to internal version of DsGetDcName
if ( ClientDnsDomain.Length != 0 ) {
ClientDsGetDcDomain = &ClientDnsDomain;
} else {
ClientDsGetDcDomain = &ClientNetbiosDomain;
}
if ( DomainName )
{
I_NtLmFree( DomainName );
}
DomainName = I_NtLmAllocate(
ClientDsGetDcDomain->Length + sizeof(WCHAR)
);
if ( DomainName == NULL ) {
Status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}
RtlCopyMemory( DomainName,
ClientDsGetDcDomain->Buffer,
ClientDsGetDcDomain->Length );
DomainName[ClientDsGetDcDomain->Length / sizeof(WCHAR)] = 0;
AttemptRediscovery = FALSE;
retry:
{
DWORD dwGetDcFlags = 0;
if ( AttemptRediscovery )
dwGetDcFlags |= DS_FORCE_REDISCOVERY;
//
// Determine the PDC of the named domain so we can change the password there.
//
NetStatus = DsGetDcNameW(
NULL,
DomainName,
NULL, // no domain guid
NULL, // no site name
dwGetDcFlags | DS_WRITABLE_REQUIRED,
&DCInfo );
if ( NetStatus != NERR_Success ) {
SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW (retry) %ws, dwGetDcFlags %#x returned %#x\n", DomainName, dwGetDcFlags, NetStatus));
Status = NetpApiStatusToNtStatus( NetStatus );
if ( Status == STATUS_INTERNAL_ERROR )
Status = STATUS_NO_SUCH_DOMAIN;
goto Cleanup;
}
RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
Status = MspChangePassword(
&DCNameString,
&ClientName,
&ChangePasswordRequest->OldPassword,
&ChangePasswordRequest->NewPassword,
ClientRequest,
ChangePasswordRequest->Impersonating,
&DomainPasswordInfo,
NULL,
&Authoritative );
if ( !NT_SUCCESS(Status) && !Authoritative && !AttemptRediscovery ) {
//
// ISSUE: only do rediscovery if the DC is not available, seem to
// be over-reactive here
//
AttemptRediscovery = TRUE;
goto retry;
}
}
PasswordChangeSuccessfull:
//
// Allocate and initialize the response buffer.
//
SavedStatus = Status;
*ReturnBufferSize = sizeof(MSV1_0_CHANGEPASSWORD_RESPONSE);
Status = NlpAllocateClientBuffer( &ClientBufferDesc,
sizeof(MSV1_0_CHANGEPASSWORD_RESPONSE),
*ReturnBufferSize );
if ( !NT_SUCCESS( Status ) ) {
KdPrint(("MSV1_0: MspLm20ChangePassword: cannot alloc client buffer\n" ));
*ReturnBufferSize = 0;
goto Cleanup;
}
ChangePasswordResponse = (PMSV1_0_CHANGEPASSWORD_RESPONSE) ClientBufferDesc.MsvBuffer;
ChangePasswordResponse->MessageType = MsV1_0ChangePassword;
//
// Copy the DomainPassword restrictions out to the caller depending on
// whether it was passed to us.
//
// Mark the buffer as valid or invalid to let the caller know.
//
// if STATUS_PASSWORD_RESTRICTION is returned. This status can be
// returned by either SAM or a down-level change. Only SAM will return
// valid data so we have a flag in the buffer that says whether the data
// is valid or not.
//
if ( DomainPasswordInfo == NULL ) {
ChangePasswordResponse->PasswordInfoValid = FALSE;
} else {
ChangePasswordResponse->DomainPasswordInfo = *DomainPasswordInfo;
ChangePasswordResponse->PasswordInfoValid = TRUE;
}
//
// Flush the buffer to the client's address space.
//
Status = NlpFlushClientBuffer( &ClientBufferDesc,
ProtocolReturnBuffer );
//
// Update cached credentials with the new password.
//
// This is done by calling NlpChangePassword,
// which takes encrypted passwords, so encrypt 'em.
//
if ( NT_SUCCESS(SavedStatus) ) {
BOOLEAN Impersonating;
NTSTATUS TempStatus;
//
// Failure of NlpChangePassword is OK, that means that the
// account we've been working with isn't the one we're
// caching credentials for.
//
TempStatus = NlpChangePassword(
Validated,
&ClientNetbiosDomain,
&ClientName,
&ChangePasswordRequest->NewPassword
);
//
// for ChangeCachedPassword, set the ProtocolStatus if an error
// occured updating.
//
if ( !NT_SUCCESS(TempStatus) &&
(ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword)
)
{
SavedStatus = TempStatus;
//
// STATUS_PRIVILEGE_NOT_HELD means the caller is not allowed to change
// cached passwords, if so bail out now
//
if (STATUS_PRIVILEGE_NOT_HELD == SavedStatus)
{
Status = SavedStatus;
goto Cleanup;
}
}
//
// Notify the LSA itself of the password change
//
Impersonating = FALSE;
if ( ChangePasswordRequest->Impersonating ) {
TempStatus = Lsa.ImpersonateClient();
if ( NT_SUCCESS(TempStatus)) {
Impersonating = TRUE;
}
}
LsaINotifyPasswordChanged(
&ClientNetbiosDomain,
&ClientName,
ClientDnsDomain.Length == 0 ? NULL : &ClientDnsDomain,
ClientUpn.Length == 0 ? NULL : &ClientUpn,
ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ?
NULL :
&ChangePasswordRequest->OldPassword,
&ChangePasswordRequest->NewPassword,
Impersonating );
if ( Impersonating ) {
RevertToSelf();
}
}
Status = SavedStatus;
Cleanup:
//
// Free Locally allocated resources
//
if (DomainName != NULL) {
I_NtLmFree(DomainName);
}
if ( DCInfo != NULL ) {
NetApiBufferFree(DCInfo);
}
if ( WkstaInfo100 != NULL ) {
NetApiBufferFree(WkstaInfo100);
}
if ( DomainPasswordInfo != NULL ) {
SamFreeMemory(DomainPasswordInfo);
}
if ( PrimaryDomainInfo != NULL ) {
(VOID) LsaFreeMemory( PrimaryDomainInfo );
}
if (NameResult) {
DsFreeNameResult(NameResult);
}
if ( DsHandle ) {
DsUnBindW(
&DsHandle
);
}
if (SpnForDC) {
NtLmFreePrivateHeap(SpnForDC);
}
//
// Free Policy Server Role Information if used.
//
if (PolicyLsaServerRoleInfo != NULL) {
I_LsaIFree_LSAPR_POLICY_INFORMATION(
PolicyLsaServerRoleInformation,
(PLSAPR_POLICY_INFORMATION) PolicyLsaServerRoleInfo
);
}
//
// Free the return buffer.
//
NlpFreeClientBuffer( &ClientBufferDesc );
//
// Don't let the password stay in the page file.
//
if ( PasswordBufferValidated ) {
RtlEraseUnicodeString( &ChangePasswordRequest->OldPassword );
RtlEraseUnicodeString( &ChangePasswordRequest->NewPassword );
}
//
// Flush the log to disk
//
MsvPaswdSetAndClearLog();
#if _WIN64
//
// Do this last since some of the cleanup code above may refer to addresses
// inside the pTempSubmitBuffer/ProtocolSubmitBuffer (e.g., erasing the old
// and new passwords, etc).
//
if (fAllocatedSubmitBuffer)
{
NtLmFreePrivateHeap( pTempSubmitBuffer );
}
#endif // _WIN64
//
// Return status to the caller.
//
*ProtocolStatus = Status;
return STATUS_SUCCESS;
}