/*++ Copyright (c) 1992 Microsoft Corporation Module Name: ChangePw.c Abstract: This module implements password change from downlevel clients. XsChangePasswordSam is called by XsNetUserPasswordSet2 in apiuser.c. I've put this in a seperate file because it #includes a private SAM header. Author: Dave Hart (davehart) 31-Apr-1992 Revision History: --*/ #include "xactsrvp.h" #include #include #include #include #include #include "changepw.h" #include #include ///////////////////////////////////////////////////////////////////////////// // // // Internal function prototyptes. // // // ///////////////////////////////////////////////////////////////////////////// NTSTATUS RtlGetPrimaryDomain( IN ULONG SidLength, OUT PBOOLEAN PrimaryDomainPresent, OUT PUNICODE_STRING PrimaryDomainName, OUT PUSHORT RequiredNameLength, OUT PSID PrimaryDomainSid OPTIONAL, OUT PULONG RequiredSidLength ); ///////////////////////////////////////////////////////////////////////////// // // // Exported functions. // // // ///////////////////////////////////////////////////////////////////////////// NET_API_STATUS XsChangePasswordSam ( IN PUNICODE_STRING UserName, IN PVOID OldPassword, IN PVOID NewPassword, IN BOOLEAN Encrypted ) /*++ Routine Description: This routine is called by XsNetUserPasswordSet2 to change the password on a Windows NT machine. The code is based on lsa\msv1_0\nlmain.c MspChangePasswordSam. Arguments: UserName - Name of the user to change password for. OldPassword - Old password encrypted using new password as key. NewPassword - New password encrypted using old password as key. Return Value: --*/ { NTSTATUS Status; NT_PRODUCT_TYPE NtProductType; UNICODE_STRING DomainName; LPWSTR serverName = NULL; BOOLEAN DomainNameAllocated; BOOLEAN PrimaryDomainPresent; USHORT RequiredDomainNameLength; ULONG RequiredDomainSidLength; OBJECT_ATTRIBUTES ObjectAttributes; SECURITY_QUALITY_OF_SERVICE SecurityQos; PSID DomainSid = NULL; PULONG UserId = NULL; PSID_NAME_USE NameUse = NULL; SAM_HANDLE SamHandle = NULL; SAM_HANDLE DomainHandle = NULL; SAM_HANDLE UserHandle = NULL; HANDLE OpenedToken; // // We're going to _open the local account domain. The name of this // domain is "Account" on a WinNT machine, or the name of the // primary domain on a LanManNT machine. Figure out the product // type, assuming WinNT if RtlGetNtProductType fails. // DomainName.MaximumLength = 0; DomainName.Buffer = NULL; DomainNameAllocated = FALSE; NtProductType = NtProductWinNt; RtlGetNtProductType( &NtProductType ); if (NtProductLanManNt != NtProductType) { NET_API_STATUS error; // // The server name is the database name. // error = NetpGetComputerName( &serverName ); if ( error != NO_ERROR ) { return(error); } RtlInitUnicodeString( &DomainName, serverName ); } else { // // This is a LanManNT machine, so we need to find out the // name of the primary domain. First get the length of the // domain name, then make room for it and retrieve it. // Status = RtlGetPrimaryDomain( 0, &PrimaryDomainPresent, &DomainName, &RequiredDomainNameLength, NULL, &RequiredDomainSidLength ); if (STATUS_BUFFER_TOO_SMALL != Status && !NT_SUCCESS(Status)) { KdPrint(("XsChangePasswordSam: Unable to size primary " " domain name buffer, %8.8x\n", Status)); goto Cleanup; } DomainName.Buffer = RtlAllocateHeap( RtlProcessHeap(), 0, DomainName.MaximumLength = RequiredDomainNameLength ); DomainNameAllocated = TRUE; DomainSid = RtlAllocateHeap( RtlProcessHeap(), 0, RequiredDomainSidLength ); if (!DomainName.Buffer || !DomainSid) { KdPrint(("XsChangePasswordSam: Out of memory allocating %d and %d bytes.", RequiredDomainNameLength, RequiredDomainSidLength)); Status = STATUS_NO_MEMORY; goto Cleanup; } Status = RtlGetPrimaryDomain( RequiredDomainSidLength, &PrimaryDomainPresent, &DomainName, &RequiredDomainNameLength, DomainSid, &RequiredDomainSidLength ); RtlFreeHeap(RtlProcessHeap(), 0, DomainSid); DomainSid = NULL; if (!NT_SUCCESS(Status)) { KdPrint(("XsChangePasswordSam: Unable to retrieve domain " "name, %8.8x\n", Status)); goto Cleanup; } ASSERT(PrimaryDomainPresent); } // // Wrap an exception handler around this entire function, // since RPC raises exceptions to return errors. // try { // // Connect to local SAM. // 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( NULL, &SamHandle, GENERIC_EXECUTE, &ObjectAttributes ); if ( !NT_SUCCESS(Status) ) { KdPrint(("XsChangePasswordSam: SamConnect failed, status %8.8x\n", Status)); goto Cleanup; } // // Lookup the Domain SID. // Status = SamLookupDomainInSamServer( SamHandle, &DomainName, &DomainSid ); if ( !NT_SUCCESS(Status) ) { KdPrint(("XsChangePasswordSam: Cannot find domain %wZ, " "status %8.8x\n", &DomainName, Status)); Status = STATUS_CANT_ACCESS_DOMAIN_INFO; goto Cleanup; } // // Revert to Local System // Status = NtOpenThreadToken( NtCurrentThread(), MAXIMUM_ALLOWED, TRUE, &OpenedToken ); if( !NT_SUCCESS( Status ) ) { goto Cleanup; } RevertToSelf(); // // Open the account domain. // Status = SamOpenDomain( SamHandle, GENERIC_EXECUTE, DomainSid, &DomainHandle ); if ( !NT_SUCCESS(Status) ) { #if DBG UNICODE_STRING UnicodeSid; RtlConvertSidToUnicodeString( &UnicodeSid, DomainSid, TRUE ); KdPrint(("XsChangePasswordSam: Cannot open domain %wZ, status %8.8x, SAM handle %8.8x, Domain SID %wZ\n", &DomainName, Status, SamHandle, UnicodeSid)); RtlFreeUnicodeString(&UnicodeSid); #endif Status = STATUS_CANT_ACCESS_DOMAIN_INFO; goto Cleanup; } // // Find the ID for this username. // Status = SamLookupNamesInDomain( DomainHandle, 1, UserName, &UserId, &NameUse ); if ( !NT_SUCCESS(Status) ) { KdPrint(("XsChangePasswordSam: Cannot lookup user %wZ, " "status %8.8x\n", UserName, Status)); if (STATUS_NONE_MAPPED == Status) { Status = STATUS_NO_SUCH_USER; } goto Cleanup; } // // Re-impersonate the client // Status = NtSetInformationThread( NtCurrentThread(), ThreadImpersonationToken, &OpenedToken, sizeof( OpenedToken ) ); if( !NT_SUCCESS( Status ) ) { goto Cleanup; } // // Open the user object. // Status = SamOpenUser( DomainHandle, USER_CHANGE_PASSWORD, *UserId, &UserHandle ); if ( !NT_SUCCESS(Status) ) { KdPrint(("XsChangePasswordSam: Cannot open user %wZ, " "status %8.8x\n", UserName, Status)); goto Cleanup; } if (Encrypted) { // // The client is Windows for Workgroups, OS/2, or DOS running // the ENCRYPT service. Pass the cross-encrypted passwords // to SamiLmChangePasswordUser. // Status = SamiLmChangePasswordUser( UserHandle, OldPassword, NewPassword ); } else { // // The client is DOS not running the ENCRYPT service, and so // sent plaintext. Calculate the one-way functions and call // SamiChangePasswordUser. // LM_OWF_PASSWORD OldLmOwfPassword, NewLmOwfPassword; Status = RtlCalculateLmOwfPassword( OldPassword, &OldLmOwfPassword ); if (NT_SUCCESS(Status)) { Status = RtlCalculateLmOwfPassword( NewPassword, &NewLmOwfPassword ); } if (!NT_SUCCESS(Status)) { KdPrint(("XsChangePasswordSam: Unable to generate OWF " "passwords, %8.8x\n", Status)); goto Cleanup; } // // Ask SAM to change the LM password and not store a new // NT password. // Status = SamiChangePasswordUser( UserHandle, TRUE, &OldLmOwfPassword, &NewLmOwfPassword, FALSE, NULL, NULL ); } if ( !NT_SUCCESS(Status) ) { KdPrint(("XsChangePasswordSam: Cannot change password " "for %wZ, status %8.8x\n", UserName, Status)); goto Cleanup; } } except (Status = GetExceptionCode(), EXCEPTION_EXECUTE_HANDLER) { KdPrint(("XsChangePasswordSam: caught exception 0x%8.8x\n", Status)); if (RPC_S_SERVER_UNAVAILABLE == Status) { Status = STATUS_CANT_ACCESS_DOMAIN_INFO; } } Cleanup: NetApiBufferFree( serverName ); if (DomainNameAllocated && DomainName.Buffer) { RtlFreeHeap(RtlProcessHeap(), 0, DomainName.Buffer); } if (DomainSid) { SamFreeMemory(DomainSid); } if (UserId) { SamFreeMemory(UserId); } if (NameUse) { SamFreeMemory(NameUse); } if (UserHandle) { SamCloseHandle(UserHandle); } if (DomainHandle) { SamCloseHandle(DomainHandle); } if (SamHandle) { SamCloseHandle(SamHandle); } return RtlNtStatusToDosError(Status); } NTSTATUS XsSamOEMChangePasswordUser2_P ( API_HANDLER_PARAMETERS ) /*++ Routine Description: This routine handles a call to SamrOemChangePasswordUser2 coming in from Win 95 clients Arguments: API_HANDLER_PARAMETERS - information about the API call. See XsTypes.h for details. Return Value: NTSTATUS - STATUS_SUCCESS or reason for failure. --*/ { PXS_SAMOEMCHGPASSWORDUSER2_P parameters = Parameters; STRING UserName; SAMPR_ENCRYPTED_USER_PASSWORD EncryptedUserPassword; ENCRYPTED_LM_OWF_PASSWORD EncryptedOwfPassword; NTSTATUS ntstatus; API_HANDLER_PARAMETERS_REFERENCE; // Avoid warnings try { if( SmbGetUshort( ¶meters->BufLen ) != sizeof( EncryptedUserPassword) + sizeof( EncryptedOwfPassword ) ) { Header->Status = ERROR_INVALID_PARAMETER; return STATUS_SUCCESS; } RtlCopyMemory( &EncryptedUserPassword, parameters->Buffer, sizeof( EncryptedUserPassword ) ); RtlCopyMemory( &EncryptedOwfPassword, parameters->Buffer + sizeof( EncryptedUserPassword ), sizeof( EncryptedOwfPassword ) ); UserName.Buffer = parameters->UserName; UserName.Length = (USHORT) strlen( UserName.Buffer ); UserName.MaximumLength = UserName.Length; ntstatus = SamiOemChangePasswordUser2( NULL, &UserName, &EncryptedUserPassword, &EncryptedOwfPassword ); if( ntstatus == STATUS_NOT_SUPPORTED ) { Header->Status = NERR_InvalidAPI; } else { Header->Status = (WORD)NetpNtStatusToApiStatus( ntstatus ); } } except( EXCEPTION_EXECUTE_HANDLER ) { Header->Status = (WORD)RtlNtStatusToDosError( GetExceptionCode() ); } return STATUS_SUCCESS; } // XsSamOEMChangePasswordUser2_P ///////////////////////////////////////////////////////////////////////////// // // // Internal function implementation. // // // ///////////////////////////////////////////////////////////////////////////// // // Copied from ntos\dll\seurtl.c, where it is disabled. Remove if // it is enabled in ntdll. // NTSTATUS RtlGetPrimaryDomain( IN ULONG SidLength, OUT PBOOLEAN PrimaryDomainPresent, OUT PUNICODE_STRING PrimaryDomainName, OUT PUSHORT RequiredNameLength, OUT PSID PrimaryDomainSid OPTIONAL, OUT PULONG RequiredSidLength ) /*++ Routine Description: This procedure opens the LSA policy object and retrieves the primary domain information for this machine. Arguments: SidLength - Specifies the length of the PrimaryDomainSid parameter. PrimaryDomainPresent - Receives a boolean value indicating whether this machine has a primary domain or not. TRUE indicates the machine does have a primary domain. FALSE indicates the machine does not. PrimaryDomainName - Points to the unicode string to receive the primary domain name. This parameter will only be used if there is a primary domain. RequiredNameLength - Recevies the length of the primary domain name (in bytes). This parameter will only be used if there is a primary domain. PrimaryDomainSid - This optional parameter, if present, points to a buffer to receive the primary domain's SID. This parameter will only be used if there is a primary domain. RequiredSidLength - Recevies the length of the primary domain SID (in bytes). This parameter will only be used if there is a primary domain. Return Value: STATUS_SUCCESS - The requested information has been retrieved. STATUS_BUFFER_TOO_SMALL - One of the return buffers was not large enough to receive the corresponding information. The RequiredNameLength and RequiredSidLength parameter values have been set to indicate the needed length. Other status values as may be returned by: LsaOpenPolicy() LsaQueryInformationPolicy() RtlCopySid() --*/ { NTSTATUS Status, IgnoreStatus; OBJECT_ATTRIBUTES ObjectAttributes; LSA_HANDLE LsaHandle; SECURITY_QUALITY_OF_SERVICE SecurityQualityOfService; PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo; // // Set up the Security Quality Of Service // SecurityQualityOfService.Length = sizeof(SECURITY_QUALITY_OF_SERVICE); SecurityQualityOfService.ImpersonationLevel = SecurityImpersonation; SecurityQualityOfService.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQualityOfService.EffectiveOnly = FALSE; // // Set up the object attributes to open the Lsa policy object // InitializeObjectAttributes(&ObjectAttributes, NULL, 0L, (HANDLE)NULL, NULL); ObjectAttributes.SecurityQualityOfService = &SecurityQualityOfService; // // Open the local LSA policy object // Status = LsaOpenPolicy( NULL, &ObjectAttributes, POLICY_VIEW_LOCAL_INFORMATION, &LsaHandle ); if (NT_SUCCESS(Status)) { // // Get the primary domain info // Status = LsaQueryInformationPolicy(LsaHandle, PolicyPrimaryDomainInformation, (PVOID *)&PrimaryDomainInfo); IgnoreStatus = LsaClose(LsaHandle); ASSERT(NT_SUCCESS(IgnoreStatus)); } if (NT_SUCCESS(Status)) { // // Is there a primary domain? // if (PrimaryDomainInfo->Sid != NULL) { // // Yes // (*PrimaryDomainPresent) = TRUE; (*RequiredNameLength) = PrimaryDomainInfo->Name.Length; (*RequiredSidLength) = RtlLengthSid(PrimaryDomainInfo->Sid); // // Copy the name // if (PrimaryDomainName->MaximumLength >= PrimaryDomainInfo->Name.Length) { RtlCopyUnicodeString( PrimaryDomainName, &PrimaryDomainInfo->Name ); } else { Status = STATUS_BUFFER_TOO_SMALL; } // // Copy the SID (if appropriate) // if (PrimaryDomainSid != NULL && NT_SUCCESS(Status)) { Status = RtlCopySid(SidLength, PrimaryDomainSid, PrimaryDomainInfo->Sid ); } } else { (*PrimaryDomainPresent) = FALSE; } // // We're finished with the buffer returned by LSA // IgnoreStatus = LsaFreeMemory(PrimaryDomainInfo); ASSERT(NT_SUCCESS(IgnoreStatus)); } return(Status); }