/*++

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:

--*/

#include "msp.h"
#include "nlp.h"
#include <wcstr.h>
#include <rpc.h>
#include <samisrv.h>
#include <lsarpc.h>
#include <lsaisrv.h>
#include <lmcons.h>
#include <lmerr.h>
#include <lmapibuf.h>
#include <lmaccess.h>
#include <lmremutl.h>
#include <lmsname.h>
#include <lmwksta.h>
#include <winsvc.h>
#include "nlpcache.h"



NTSTATUS
MspStopImpersonating (
    VOID
    )

/*++

Routine Description:

    Stop impersonating.  This is used to stop impersonating either
    ourselves (see MspDisableAdminsAlis) or a client.

Arguments:

    None.

Return Value:

    STATUS_SUCCESS - Indicates the service completed successfully.

    Other values are unexpected, but will be those returned by
    NtSetInformationThread.

--*/

{

    NTSTATUS
        NtStatus;

    HANDLE
        Token = NULL;

    NtStatus = NtSetInformationThread(
                   NtCurrentThread(),
                   ThreadImpersonationToken,
                   (PVOID)&Token,
                   sizeof(Token)
                   );

#if DBG
    if ( !NT_SUCCESS(NtStatus) ) {

        KdPrint(("MspStopImpersonating: Cannot stop impersonating, status %x\n",
                 NtStatus));
    }
#endif \\DBG

    return( NtStatus );

}

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;
    SID_IDENTIFIER_AUTHORITY IdentifierAuthority = SECURITY_NT_AUTHORITY;
    PSID                     AdminSid = NULL;
    TOKEN_GROUPS             TokenGroups;

    //
    // Make sure we aren't impersonating anyone else
    // (that will prevent the RtlImpersonateSelf() call from succeeding).
    //

    Status = MspStopImpersonating();

    if ( !NT_SUCCESS(Status) ) {
        goto Cleanup;
    }
    //
    // Impersonate ourself so we can munge the token, then later stop
    // impersonating and revert to the original token.
    //

    Status = RtlImpersonateSelf( SecurityDelegation );

    if ( !NT_SUCCESS(Status) ) {
        goto Cleanup;
    }


    //
    // Open the impersonation token.
    //

    Status = NtOpenThreadToken(
                 NtCurrentThread(),
                 GENERIC_READ | GENERIC_WRITE,
                 TRUE,
                 &TokenHandle
                 );

    if ( !NT_SUCCESS(Status) ) {

        KdPrint(("MspDisableAdminsAlias: NtOpenThreadToken returns %x\n",
                 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 alias.
    //

    TokenGroups.GroupCount = 1;
    TokenGroups.Groups[0].Sid = AdminSid;
    TokenGroups.Groups[0].Attributes = 0;   // SE_GROUP_ENABLED not on.

    Status = NtAdjustGroupsToken(
                 TokenHandle,
                 FALSE,
                 &TokenGroups,
                 0,
                 NULL,
                 NULL
                 );

    if ( !NT_SUCCESS(Status) ) {

        KdPrint(("MspDisableAdminsAlias: NtAdjustGroupsToken returns %x\n",
                 Status));
        goto Cleanup;
    }

Cleanup:

    if (AdminSid) {
        RtlFreeSid(AdminSid);
    }

    if (TokenHandle) {
        NtClose(TokenHandle);
    }

    //
    // If we're failing, stop impersonation.
    //

    if ( !NT_SUCCESS(Status) ) {

        NTSTATUS TempStatus;

        TempStatus = MspStopImpersonating();
    }

    return Status;
}


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 == (USHORT) 0)) {

        *UncComputerName = *ComputerName;
        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] == 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 = RtlAllocateHeap(
                                 MspHeap,
                                 0,
                                 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;
}

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
    )

/*++

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).

Return Value:

    STATUS_SUCCESS - Indicates the service completed successfully.

    ...

--*/

{
    NTSTATUS                    Status;
    OBJECT_ATTRIBUTES           ObjectAttributes;
    SECURITY_QUALITY_OF_SERVICE SecurityQos;
    PULONG                      UserId = NULL;
    PSID_NAME_USE               NameUse = NULL;
    SAM_HANDLE                  SamHandle = NULL;
    SAM_HANDLE                  DomainHandle = NULL;
    SAM_HANDLE                  UserHandle = NULL;
    LSA_HANDLE  LSAPolicyHandle = NULL;
    OBJECT_ATTRIBUTES LSAObjectAttributes;
    PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL;

    //
    // 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 = (*Lsap.ImpersonateClient)(ClientRequest);

    } else {

        //
        // 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.
        //

        Status = MspDisableAdminsAlias();
    }


    if (!NT_SUCCESS( Status )) {
        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;

    Status = SamConnect(
                 UncComputerName,
                 &SamHandle,
                 SAM_SERVER_LOOKUP_DOMAIN,
                 &ObjectAttributes
                 );

    if ( !NT_SUCCESS(Status) ) {

#ifdef COMPILED_BY_DEVELOPER
        KdPrint(("MspChangePasswordSam: SamConnect(%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                           &&
             Status != RPC_NT_SERVER_UNAVAILABLE     &&
             Status != RPC_S_SERVER_UNAVAILABLE   ) {

            Status = MspDisableAdminsAlias();

            if (!NT_SUCCESS(Status)) {
                goto Cleanup;
            }

            Status = SamConnect(
                         UncComputerName,
                         &SamHandle,
                         SAM_SERVER_LOOKUP_DOMAIN,
                         &ObjectAttributes
                         );

#ifdef COMPILED_BY_DEVELOPER
            if ( !NT_SUCCESS(Status) ) {
                KdPrint(("MspChangePasswordSam: SamConnect(%wZ) (2nd attempt) failed, status %x\n",
                 UncComputerName, Status));
                }
#endif // COMPILED_BY_DEVELOPER
        }

        //
        // Flag certain error codes to indicate we should try downlevel
        //

        if ( !NT_SUCCESS(Status) ) {
            if (Status == RPC_NT_SERVER_UNAVAILABLE ||
                Status == RPC_S_SERVER_UNAVAILABLE ) {

                Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
            }

            SamHandle = NULL;
            goto Cleanup;
        }
    }



    //
    // Get the SID of the account domain from LSA
    //

    InitializeObjectAttributes( &LSAObjectAttributes,
                                  NULL,             // Name
                                  0,                // Attributes
                                  NULL,             // Root
                                  NULL );           // Security Descriptor

    Status = LsaOpenPolicy( UncComputerName,
                            &LSAObjectAttributes,
                            POLICY_VIEW_LOCAL_INFORMATION,
                            &LSAPolicyHandle );

    if( !NT_SUCCESS(Status) ) {
        KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
                 UncComputerName, Status));
        LSAPolicyHandle = NULL;
        goto Cleanup;
    }

    Status = LsaQueryInformationPolicy(
                    LSAPolicyHandle,
                    PolicyAccountDomainInformation,
                    (PVOID *) &AccountDomainInfo );

    if( !NT_SUCCESS(Status) ) {
        KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
                 UncComputerName, Status));
        AccountDomainInfo = NULL;
        goto Cleanup;
    }



    //
    // Open the Account domain in SAM.
    //

    Status = SamOpenDomain(
                 SamHandle,
                 GENERIC_EXECUTE,
                 AccountDomainInfo->DomainSid,
                 &DomainHandle
                 );

    if ( !NT_SUCCESS(Status) ) {
        KdPrint(("MspChangePasswordSam: Cannot open domain on %wZ, status %x\n",
                 UncComputerName, Status));
        DomainHandle = NULL;
        goto Cleanup;
    }

    Status = SamLookupNamesInDomain(
                 DomainHandle,
                 1,
                 UserName,
                 &UserId,
                 &NameUse
                 );

    if ( !NT_SUCCESS(Status) ) {

        KdPrint(("MspChangePasswordSam: Cannot lookup user %wZ, status %x\n",
                 UserName, Status));

        if (Status == STATUS_NONE_MAPPED) {
            Status = STATUS_NO_SUCH_USER;
        }

        goto Cleanup;
    }

    Status = SamOpenUser(
                 DomainHandle,
                 USER_CHANGE_PASSWORD,
                 *UserId,
                 &UserHandle
                 );

    if ( !NT_SUCCESS(Status) ) {
        KdPrint(("MspChangePasswordSam: Cannot open user %wZ, status %x\n",
                 UserName, Status));
        UserHandle = NULL;
        goto Cleanup;
    }

    Status = SamChangePasswordUser(
                 UserHandle,
                 OldPassword,
                 NewPassword
                 );

    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 == STATUS_PASSWORD_RESTRICTION) {

            //
            // Get the password restrictions for this domain and return them
            //

            Status = SamQueryInformationDomain(
                            DomainHandle,
                            DomainPasswordInformation,
                            (PVOID *)DomainPasswordInfo );

            if (!NT_SUCCESS(Status)) {
                *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;


        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;

    }

    //
    // Stop impersonating.
    //

    {
        NTSTATUS TempStatus;

        TempStatus = MspStopImpersonating();

    }

    //
    // Free Locally used resources
    //

    if (UserId) {
        SamFreeMemory(UserId);
    }

    if (NameUse) {
        SamFreeMemory(NameUse);
    }

    if (UserHandle) {
        SamCloseHandle(UserHandle);
    }

    if (DomainHandle) {
        SamCloseHandle(DomainHandle);
    }

    if (SamHandle) {
        SamCloseHandle(SamHandle);
    }

    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
    )

/*++

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.

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;

    //
    // Convert UserName from UNICODE_STRING to null-terminated wide string
    // for use by RxNetUserPasswordSet.
    //

    Length = UserNameU->Length;

    UserName = RtlAllocateHeap(
                   MspHeap, 0,
                   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 = RtlAllocateHeap( MspHeap, 0, 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 = RtlAllocateHeap( MspHeap, 0, 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 = (*NlpRxNetUserPasswordSet)(
                    UncComputerName->Buffer,
                    UserName,
                    OldPassword,
                    NewPassword);

#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;
    } else {
        Status = (*NlpNetpApiStatusToNtStatus)( NetStatus );
    }

Cleanup:

    //
    // Free UserName if used.
    //

    if (UserName) {

        RtlFreeHeap(MspHeap, 0, UserName);
    }

    //
    // Free OldPassword if used. (Don't let password make it to page file)
    //

    if (OldPassword) {
        RtlZeroMemory( OldPassword, wcslen(OldPassword)*sizeof(WCHAR) );
        RtlFreeHeap(MspHeap, 0, OldPassword);
    }

    //
    // Free NewPassword if used. (Don't let password make it to page file)
    //

    if (NewPassword) {
        RtlZeroMemory( NewPassword, wcslen(NewPassword)*sizeof(WCHAR) );
        RtlFreeHeap(MspHeap, 0, 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
    )

/*++

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).


Return Value:

    STATUS_SUCCESS - Indicates the service completed successfully.

    STATUS_PASSWORD_RESTRICTION - Password changing is restricted.

--*/

{
    NTSTATUS Status;
    UNICODE_STRING UncComputerName;

    //
    // 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 );

    //
    // 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) {

        Status = MspChangePasswordDownlevel(
                     &UncComputerName,
                     UserName,
                     OldPassword,
                     NewPassword );
    }


    //
    // Free UncComputerName.Buffer if different from ComputerName.
    //

    if ( UncComputerName.Buffer != ComputerName->Buffer ) {
        RtlFreeHeap(MspHeap, 0, UncComputerName.Buffer);
    }

    return(Status);
}



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;
    PMSV1_0_CHANGEPASSWORD_RESPONSE ChangePasswordResponse;
    NTSTATUS        Status;
    NTSTATUS        SavedStatus;
    LPWSTR          DomainName = NULL;
    LPWSTR          DCName = NULL;
    UNICODE_STRING  DCNameString;
    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;

    //
    // Sanity checks.
    //

    NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );

    if ( SubmitBufferSize < sizeof(MSV1_0_CHANGEPASSWORD_REQUEST) ) {
        Status = STATUS_INVALID_PARAMETER;
        goto Cleanup;
    }

    ChangePasswordRequest = (PMSV1_0_CHANGEPASSWORD_REQUEST) ProtocolSubmitBuffer;

    ASSERT( ChangePasswordRequest->MessageType == MsV1_0ChangePassword );

    RELOCATE_ONE( &ChangePasswordRequest->DomainName );

    RELOCATE_ONE( &ChangePasswordRequest->AccountName );

    RELOCATE_ONE( &ChangePasswordRequest->OldPassword );

    RELOCATE_ONE( &ChangePasswordRequest->NewPassword );

    *ReturnBufferSize = 0;
    *ProtocolReturnBuffer = NULL;
    *ProtocolStatus = STATUS_PENDING;
    PasswordBufferValidated = TRUE;

#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

    //
    // This function uses Net apis, and so requires that Netapi.dll be loaded.
    //

    if (!NlpNetapiDllLoaded) {

        NlpLoadNetapiDll();

        if (!NlpNetapiDllLoaded) {
            Status = STATUS_NO_SUCH_FILE;
            goto Cleanup;
        }
    }

    //
    // 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(
                 &ChangePasswordRequest->DomainName,
                 &ChangePasswordRequest->AccountName,
                 &ChangePasswordRequest->OldPassword,
                 &ChangePasswordRequest->NewPassword,
                 ClientRequest,
                 ChangePasswordRequest->Impersonating,
                 &DomainPasswordInfo,
                 &PrimaryDomainInfo );

    if ( NT_SUCCESS(Status) || Status == STATUS_PASSWORD_RESTRICTION ) {
        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 ) {

        ChangePasswordRequest->DomainName = PrimaryDomainInfo->Name;
        Status = STATUS_BAD_NETWORK_PATH;
    }


    //
    // If DomainName is actually a server name,
    //  just return the status to the caller.
    //

    if ( Status != STATUS_BAD_NETWORK_PATH ||
         ( ChangePasswordRequest->DomainName.Length >= 3 * sizeof(WCHAR) &&
           ChangePasswordRequest->DomainName.Buffer[0] == L'\\' &&
           ChangePasswordRequest->DomainName.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;
        }
        goto Cleanup;

    }


    //
    // Build a zero terminated domain name.
    //

    DomainName = RtlAllocateHeap(
                    MspHeap,
                    0,
                    ChangePasswordRequest->DomainName.Length + sizeof(WCHAR));

    if ( DomainName == NULL ) {
        Status = STATUS_NO_MEMORY;
        goto Cleanup;
    }

    RtlCopyMemory( DomainName,
                   ChangePasswordRequest->DomainName.Buffer,
                   ChangePasswordRequest->DomainName.Length );
    DomainName[ChangePasswordRequest->DomainName.Length / sizeof(WCHAR)] = 0;


    //
    // If we are the PDC of the specified domain,
    //  don't bother calling NetGetDCName.
    //

    if ( !NlpWorkstation &&
               RtlEqualDomainName(
                   &NlpSamDomainName,
                   &ChangePasswordRequest->DomainName )) {

        //
        // We are a Domain Controller for the specified domain.  If our
        // role is PDC, we already have the PDC ComputerName.  Otherwise,
        // we need to call NlpNetGetDCName.
        //

        Status = LsarQueryInformationPolicy(
                     NlpPolicyHandle,
                     PolicyLsaServerRoleInformation,
                     (PLSAPR_POLICY_INFORMATION *) &PolicyLsaServerRoleInfo
                     );

        if (!NT_SUCCESS(Status)) {
            goto Cleanup;
        }

        if (PolicyLsaServerRoleInfo->LsaServerRole == PolicyServerRolePrimary) {

            //
            // Just change the password locally.
            //

            Status = MspChangePassword(
                         &NlpComputerName,
                         &ChangePasswordRequest->AccountName,
                         &ChangePasswordRequest->OldPassword,
                         &ChangePasswordRequest->NewPassword,
                         ClientRequest,
                         ChangePasswordRequest->Impersonating,
                         &DomainPasswordInfo,
                         NULL );

            if ( NT_SUCCESS(Status) || Status == STATUS_PASSWORD_RESTRICTION ) {
                goto PasswordChangeSuccessfull;
            }

        }

    }



    //
    // Determine the PDC of the named domain so we can change the password there.
    //

    NetStatus = (*NlpNetGetDCName)(
                        NULL,
                        DomainName,
                        (LPBYTE *)&DCName );

    if ( NetStatus != NERR_Success ) {
        Status = STATUS_NO_SUCH_DOMAIN;
        goto Cleanup;
    }

    RtlInitUnicodeString( &DCNameString, DCName );

    Status = MspChangePassword(
                 &DCNameString,
                 &ChangePasswordRequest->AccountName,
                 &ChangePasswordRequest->OldPassword,
                 &ChangePasswordRequest->NewPassword,
                 ClientRequest,
                 ChangePasswordRequest->Impersonating,
                 &DomainPasswordInfo,
                 NULL );

    if ( NT_SUCCESS(Status) || Status == STATUS_PASSWORD_RESTRICTION ) {
        goto PasswordChangeSuccessfull;
    }

    goto Cleanup;


    //
    // The password change was successfull, or there was a password restriction
    //
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) ) {
        ANSI_STRING     LmPasswordString;
        CHAR            LmPassword[LM20_PWLEN+1];
        LM_OWF_PASSWORD LmOwfPassword;
        NT_OWF_PASSWORD NtOwfPassword;

        LmPasswordString.Buffer = LmPassword;
        LmPasswordString.Length = 0;
        LmPasswordString.MaximumLength = sizeof( LmPassword );
        Status = RtlUpcaseUnicodeStringToOemString( &LmPasswordString,
                                                     &ChangePasswordRequest->NewPassword,
                                                     FALSE
                                                   );
        if ( NT_SUCCESS(Status) ) {
            Status = RtlCalculateLmOwfPassword( LmPassword, &LmOwfPassword );

            ASSERT( NT_SUCCESS(Status) );

            Status = RtlCalculateNtOwfPassword( &ChangePasswordRequest->NewPassword,
                                                &NtOwfPassword );

            ASSERT( NT_SUCCESS(Status) );

            //
            // Failure of NlpChangePassword is OK, that means that the
            // account we've been working with isn't the one we're
            // caching credentials for.
            //

            (VOID) NlpChangePassword(
                         &ChangePasswordRequest->DomainName,
                         &ChangePasswordRequest->AccountName,
                         &LmOwfPassword,
                         &NtOwfPassword
                         );
        }
    }


    Status = SavedStatus;


Cleanup:


    //
    // Free Locally allocated resources
    //

    if (DomainName != NULL) {
        RtlFreeHeap(MspHeap, 0, DomainName);
    }

    if ( DCName != NULL ) {
        (*NlpNetApiBufferFree)(DCName);
    }

    if ( WkstaInfo100 != NULL ) {
        (*NlpNetApiBufferFree)(WkstaInfo100);
    }

    if ( DomainPasswordInfo != NULL ) {
        SamFreeMemory(DomainPasswordInfo);
    }

    if ( PrimaryDomainInfo != NULL ) {
        (VOID) LsaFreeMemory( PrimaryDomainInfo );
    }


    //
    // Free Policy Server Role Information if used.
    //

    if (PolicyLsaServerRoleInfo != NULL) {

        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 );
    }

    //
    // Return status to the caller.
    //


    *ProtocolStatus = Status;
    return STATUS_SUCCESS;
}
