windows-nt/Source/XPSP1/NT/ds/netapi/svcdlls/wkssvc/server/join.c
2020-09-26 16:20:57 +08:00

2724 lines
74 KiB
C
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*++
Copyright (c) 1998-1998 Microsoft Corporation
Module Name:
join.c
Abstract:
This module contains the worker routines for the NetJoin APIs
implemented in the Workstation service.
Author:
Mac McLain (macm) 06-Jan-1998
Revision History:
--*/
#include "wsutil.h"
#include "wsconfig.h"
#include <lmerrlog.h>
#include <lmaccess.h>
#define __LMJOIN_H__
#include <netsetup.h>
#include <icanon.h>
#include <crypt.h>
#include <rc4.h>
#include <md5.h>
#if(_WIN32_WINNT >= 0x0500)
#include <dnsapi.h>
#include <ntdsapi.h>
#include <dsgetdc.h>
#include <dsgetdcp.h>
#include <winldap.h>
#include <ntldap.h>
#endif
#if(_WIN32_WINNT < 0x0500)
#include <winreg.h>
#endif
#define COMPUTERNAME_ROOT \
L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\ComputerName"
#define NEW_COMPUTERNAME_VALUE_NAME L"ComputerName"
NET_API_STATUS
JoinpDecryptPasswordWithKey(
IN handle_t RpcBindingHandle,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword,
IN BOOL EncodePassword,
OUT LPWSTR *EncodedPassword
)
/*++
Routine Description:
Decrypts a password encrypted with the user session key.
Arguments:
RpcBindingHandle - Rpc Binding handle describing the session key to use.
EncryptedPassword - Encrypted password to decrypt.
EncodePassword - If TRUE, the returned password will be encoded
and the first WCHAR of the password buffer will be the seed
EncodedPassword - Returns the (optionally Encoded) password.
Buffer should be freed using NetpMemoryFree.
Return Value:
--*/
{
NET_API_STATUS NetStatus;
NTSTATUS Status;
USER_SESSION_KEY UserSessionKey;
RC4_KEYSTRUCT Rc4Key;
MD5_CTX Md5Context;
UNICODE_STRING EncodedPasswordU;
LPWSTR PasswordPart;
UCHAR Seed = 0;
PJOINPR_USER_PASSWORD Password = (PJOINPR_USER_PASSWORD) EncryptedPassword;
//
// Handle the trivial case
//
*EncodedPassword = NULL;
if ( EncryptedPassword == NULL ) {
return NO_ERROR;
}
//
// Get the session key
//
Status = RtlGetUserSessionKeyServer(
(RPC_BINDING_HANDLE)RpcBindingHandle,
&UserSessionKey );
if (!NT_SUCCESS(Status)) {
return NetpNtStatusToApiStatus( Status );
}
//
// The UserSessionKey is the same for the life of the session. RC4'ing multiple
// strings with a single key is weak (if you crack one you've cracked them all).
// So compute a key that's unique for this particular encryption.
//
//
MD5Init(&Md5Context);
MD5Update( &Md5Context, (LPBYTE)&UserSessionKey, sizeof(UserSessionKey) );
MD5Update( &Md5Context, Password->Obfuscator, sizeof(Password->Obfuscator) );
MD5Final( &Md5Context );
rc4_key( &Rc4Key, MD5DIGESTLEN, Md5Context.digest );
//
// Decrypt the Buffer
//
rc4( &Rc4Key, sizeof(Password->Buffer)+sizeof(Password->Length), (LPBYTE) Password->Buffer );
//
// Check that the length is valid. If it isn't bail here.
//
if (Password->Length > JOIN_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) {
return ERROR_INVALID_PASSWORD;
}
//
// Return the password to the caller.
//
*EncodedPassword = NetpMemoryAllocate( Password->Length + sizeof(WCHAR) + sizeof(WCHAR) );
if ( *EncodedPassword == NULL ) {
return ERROR_NOT_ENOUGH_MEMORY;
}
//
// Copy the password into the buffer
//
// If we are to encode the password, reserve
// the first character for the seed
//
if ( EncodePassword ) {
PasswordPart = ( *EncodedPassword ) + 1;
} else {
PasswordPart = ( *EncodedPassword );
}
RtlCopyMemory( PasswordPart,
((PCHAR) Password->Buffer) +
(JOIN_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
Password->Length,
Password->Length );
PasswordPart[Password->Length/sizeof(WCHAR)] = L'\0';
RtlZeroMemory( ((PCHAR) Password->Buffer) +
(JOIN_MAX_PASSWORD_LENGTH * sizeof(WCHAR)) -
Password->Length,
Password->Length );
//
// Run Encode it so we can pass it around the process with impunity
//
if ( EncodePassword ) {
RtlInitUnicodeString( &EncodedPasswordU, PasswordPart );
RtlRunEncodeUnicodeString( &Seed, &EncodedPasswordU );
*( PWCHAR )( *EncodedPassword ) = ( WCHAR )Seed;
}
return NO_ERROR;
}
NET_API_STATUS
NET_API_FUNCTION
NetrJoinDomain2(
IN handle_t RpcBindingHandle,
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpDomain,
IN LPWSTR lpMachineAccountOU,
IN LPWSTR lpAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN DWORD fOptions
)
/*++
Routine Description:
Joins the machine to the domain.
Arguments:
lpServer -- Name of the machine being run on
lpDomain -- Domain to join
lpMachineAccountOU -- Optional name of the OU under which to create the machine account
lpAccount -- Account to use for join
EncryptedPassword - Encrypted password for lpAccount.
fOptions -- Options to use when joining the domain
Returns:
NERR_Success -- Success
ERROR_INVALID_PARAMETER -- A bad parameter was given
--*/
{
NET_API_STATUS NetStatus = NERR_Success;
LPTSTR ComputerName = NULL;
LPWSTR EncodedPassword = NULL;
//
// Check the parameters we can
//
if (lpDomain == NULL ) {
NetStatus = ERROR_INVALID_PARAMETER;
}
//
// Decrypt the password.
//
if ( NetStatus == NERR_Success ) {
NetStatus = JoinpDecryptPasswordWithKey(
RpcBindingHandle,
EncryptedPassword,
TRUE, // encode the password
&EncodedPassword );
}
//
// Get the current machine name, so we are sure we always have it in flat format
//
if ( NetStatus == NERR_Success ) {
NetStatus = NetpGetComputerName( &ComputerName );
if ( NetStatus == NERR_Success ) {
lpServer = ComputerName;
}
}
//
// Do the impersonation
//
if ( NetStatus == NERR_Success ) {
NetStatus = WsImpersonateClient();
}
//
// Then, see about the join...
//
if ( NetStatus == NERR_Success ) {
NetStatus = NetpDoDomainJoin( lpServer, lpDomain, lpMachineAccountOU, lpAccount,
EncodedPassword, fOptions );
//
// Revert back to ourselves
//
WsRevertToSelf();
}
//
// Write event log stating that we successfully joined the domain/workgroup
//
if ( NetStatus == NERR_Success ) {
LPWSTR StringArray[2];
if ( fOptions & NETSETUP_JOIN_DOMAIN ) {
StringArray[0] = L"domain";
} else {
StringArray[0] = L"workgroup";
}
StringArray[1] = lpDomain;
WsLogEvent( NELOG_Joined_Domain,
EVENTLOG_INFORMATION_TYPE,
2,
StringArray,
NERR_Success );
}
//
// Free the memory for the machine name if we need to
//
if ( ComputerName != NULL ) {
NetApiBufferFree( ComputerName );
}
if ( EncodedPassword != NULL ) {
NetpMemoryFree( EncodedPassword );
}
return( NetStatus );
}
NET_API_STATUS
NET_API_FUNCTION
NetrUnjoinDomain2(
IN handle_t RpcBindingHandle,
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN DWORD fJoinOptions
)
/*++
Routine Description:
Unjoins from the joined domain
Arguments:
lpServer -- Name of the machine being run on
lpAccount -- Account to use for unjoining
lpPassword -- Password matching the account. The password is encoded. The first WCHAR is
the seed
fOptions -- Options to use when unjoining the domain
Returns:
NERR_Success -- Name is valid
NERR_SetupNotJoined -- This machine was not joined to a domain
NERR_SetupDomainController -- This machine is a domain controller and
cannot be unjoined
NERR_InternalError -- Can't determine product type
--*/
{
NET_API_STATUS NetStatus = NERR_Success;
PPOLICY_PRIMARY_DOMAIN_INFO pPolicyPDI;
PPOLICY_DNS_DOMAIN_INFO pPolicyDNS;
NT_PRODUCT_TYPE ProductType;
LPWSTR EncodedPassword = NULL;
//
// Decrypt the password.
//
NetStatus = JoinpDecryptPasswordWithKey(
RpcBindingHandle,
EncryptedPassword,
TRUE, // encode the password
&EncodedPassword );
if ( NetStatus != NO_ERROR ) {
return NetStatus;
}
//
// Do the impersonation
//
NetStatus = WsImpersonateClient();
//
// First, get the primary domain info... We'll need it later
//
if ( NetStatus == NERR_Success ) {
NetStatus = NetpGetLsaPrimaryDomain( NULL,
NULL,
&pPolicyPDI,
&pPolicyDNS,
NULL );
if ( NetStatus == NERR_Success ) {
if ( !IS_CLIENT_JOINED(pPolicyPDI) ) {
NetStatus = NERR_SetupNotJoined;
} else {
//
// See if it's a DC...
//
if ( RtlGetNtProductType( &ProductType ) == FALSE ) {
NetStatus = NERR_InternalError;
} else {
if ( ProductType == NtProductLanManNt ) {
NetStatus = NERR_SetupDomainController;
}
}
}
//
// Ok, now if all that worked, we'll go ahead and do the removal
//
if ( NetStatus == NERR_Success ) {
NetStatus = NetpUnJoinDomain( pPolicyPDI, lpAccount, EncodedPassword,
fJoinOptions );
}
LsaFreeMemory( pPolicyPDI );
LsaFreeMemory( pPolicyDNS );
}
//
// Revert back to ourselves
//
WsRevertToSelf();
}
if ( EncodedPassword != NULL ) {
NetpMemoryFree( EncodedPassword );
}
return(NetStatus);
}
NET_API_STATUS
NET_API_FUNCTION
NetrValidateName2(
IN handle_t RpcBindingHandle,
IN LPWSTR lpMachine,
IN LPWSTR lpName,
IN LPWSTR lpAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN NETSETUP_NAME_TYPE NameType
)
/*++
Routine Description:
Ensures that the given name is valid for a name of that type
Arguments:
lpMachine -- Name of the machine being run on
lpName -- Name to validate
lpAccount -- Account to use for name validation
lpPassword -- Password matching the account. The password is encoded. The first WCHAR is
the seed
NameType -- Type of the name to validate
Returns:
NERR_Success -- Name is valid
ERROR_INVALID_PARAMETER -- A bad parameter was given
NERR_InvalidComputer -- The name format given is bad
ERROR_DUP_NAME -- The name is invalid for this type
--*/
{
NET_API_STATUS NetStatus = NERR_Success;
UNICODE_STRING EncodedPasswordU;
UCHAR Seed;
LPWSTR EncodedPassword = NULL;
//
// Decrypt the password.
//
NetStatus = JoinpDecryptPasswordWithKey(
RpcBindingHandle,
EncryptedPassword,
TRUE, // encode the password
&EncodedPassword );
if ( NetStatus != NO_ERROR ) {
return NetStatus;
}
if ( EncodedPassword ) {
Seed = *( PUCHAR )EncodedPassword;
RtlInitUnicodeString( &EncodedPasswordU, EncodedPassword + 1 );
} else {
RtlZeroMemory( &EncodedPasswordU, sizeof( UNICODE_STRING ) );
}
//
// Do the impersonation
//
NetStatus = WsImpersonateClient();
if ( NetStatus == NERR_Success ) {
RtlRunDecodeUnicodeString( Seed, &EncodedPasswordU );
NetStatus = NetpValidateName( lpMachine,
lpName,
lpAccount,
EncodedPasswordU.Buffer,
NameType );
RtlRunEncodeUnicodeString( &Seed, &EncodedPasswordU );
//
// Revert back to ourselves
//
WsRevertToSelf();
}
if ( EncodedPassword != NULL ) {
NetpMemoryFree( EncodedPassword );
}
return( NetStatus );
}
NET_API_STATUS
NET_API_FUNCTION
NetrGetJoinInformation(
IN LPWSTR lpServer OPTIONAL,
OUT LPWSTR *lpNameBuffer,
OUT PNETSETUP_JOIN_STATUS BufferType
)
/*++
Routine Description:
Gets information on the state of the workstation. The information
obtainable is whether the machine is joined to a workgroup or a domain,
and optionally, the name of that workgroup/domain.
Arguments:
lpNameBuffer -- Where the domain/workgroup name is returned.
lpNameBufferSize -- Size of the passed in buffer, in WCHARs. If 0, the
workgroup/domain name is not returned.
BufferType -- Whether the machine is joined to a workgroup or a domain
Returns:
NERR_Success -- Name is valid
ERROR_INVALID_PARAMETER -- A bad parameter was given
ERROR_NOT_ENOUGH_MEMORY -- A memory allocation failed
--*/
{
NET_API_STATUS NetStatus = NERR_Success;
//
// Check the parameters
//
if ( lpNameBuffer == NULL ) {
return( ERROR_INVALID_PARAMETER );
}
//
// Do the impersonation
//
NetStatus = WsImpersonateClient();
if ( NetStatus == NERR_Success ) {
NetStatus = NetpGetJoinInformation( lpServer,
lpNameBuffer,
BufferType );
//
// Revert back to ourselves
//
WsRevertToSelf();
}
return( NetStatus );
}
NET_API_STATUS
NET_API_FUNCTION
NetrRenameMachineInDomain2(
IN handle_t RpcBindingHandle,
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpNewMachineName OPTIONAL,
IN LPWSTR lpAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN DWORD fRenameOptions
)
/*++
Routine Description:
Renames a machine currently joined to a domain.
Arguments:
lpServer -- Name of the machine being run on
lpNewMachineName -- New name for this machine. If the name is specified, it is used
for the new machine name. If it is not specified, it is assumed that SetComputerName
has already been invoked, and that name will be used.
lpAccount -- Account to use for the rename
lpPassword -- Password matching the account. The password has been encoded. The first
WCHAR of the string is the seed.
fOptions -- Options to use for the rename
Returns:
NERR_Success -- Success
ERROR_INVALID_PARAMETER -- A bad parameter was given
--*/
{
NET_API_STATUS NetStatus = NERR_Success;
PPOLICY_PRIMARY_DOMAIN_INFO pPolicyPDI;
PPOLICY_DNS_DOMAIN_INFO pPolicyDNS;
LPTSTR ComputerName = NULL;
LPTSTR NewComputerName = NULL;
LPTSTR DomainName = NULL;
HKEY ComputerNameRootKey;
ULONG Length;
LPWSTR EncodedPassword = NULL;
//
// Get the current machine name
//
NetStatus = NetpGetComputerName( &ComputerName );
if ( NetStatus == NERR_Success ) {
lpServer = ComputerName;
}
//
// Decrypt the password.
//
if ( NetStatus == NERR_Success ) {
NetStatus = JoinpDecryptPasswordWithKey(
RpcBindingHandle,
EncryptedPassword,
TRUE, // encode the password
&EncodedPassword );
}
//
// Get the new machine name if it isn't specified
//
if ( NetStatus == NERR_Success && lpNewMachineName == NULL ) {
NetStatus = NetpGetNewMachineName( &NewComputerName );
lpNewMachineName = NewComputerName;
}
//
// Get the current domain information
//
if ( NetStatus == NERR_Success ) {
NetStatus = NetpGetLsaPrimaryDomain( NULL,
NULL,
&pPolicyPDI,
&pPolicyDNS,
NULL );
if ( NetStatus == NERR_Success ) {
NetStatus = NetApiBufferAllocate( pPolicyPDI->Name.Length + sizeof( WCHAR ),
( LPVOID * )&DomainName );
if ( NetStatus == NERR_Success ) {
RtlCopyMemory( DomainName, pPolicyPDI->Name.Buffer, pPolicyPDI->Name.Length );
DomainName[ pPolicyPDI->Name.Length / sizeof( WCHAR ) ] = UNICODE_NULL;
}
LsaFreeMemory( pPolicyPDI );
LsaFreeMemory( pPolicyDNS );
}
}
//
// Do the impersonation
//
if ( NetStatus == NERR_Success ) {
NetStatus = WsImpersonateClient();
if ( NetStatus == NERR_Success ) {
//
// A machine rename
//
NetStatus = NetpMachineValidToJoin( lpNewMachineName, TRUE );
if ( NetStatus == NERR_SetupAlreadyJoined ||
NetStatus == NERR_SetupDomainController ) { // Allow DC rename
NetStatus = NetpChangeMachineName( lpServer,
lpNewMachineName,
DomainName,
lpAccount,
EncodedPassword,
fRenameOptions );
if ( NetStatus == NERR_Success && lpNewMachineName ) {
if ( SetComputerNameEx( ComputerNamePhysicalDnsHostname,
lpNewMachineName ) == FALSE ) {
NetStatus = GetLastError();
}
}
} else if ( NetStatus == NERR_Success ) {
NetStatus = NERR_SetupNotJoined;
}
//
// Revert back to ourselves
//
WsRevertToSelf();
}
}
//
// Free the memory for the machine name(s) if we need to
//
if ( ComputerName != NULL ) {
NetApiBufferFree( ComputerName );
}
if ( NewComputerName != NULL ) {
NetApiBufferFree( NewComputerName );
}
if ( DomainName != NULL ) {
NetApiBufferFree( DomainName );
}
if ( EncodedPassword != NULL ) {
NetpMemoryFree( EncodedPassword );
}
return( NetStatus );
}
NET_API_STATUS
NET_API_FUNCTION
NetrGetJoinableOUs2(
IN handle_t RpcBindingHandle,
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpDomain,
IN LPWSTR lpAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
OUT DWORD *OUCount,
OUT LPWSTR **OUs
)
/*++
Routine Description:
Renames a machine currently joined to a domain.
Arguments:
lpServer -- Name of the machine being run on
lpDomain -- Domain to join
lpAccount -- Account to use for join
lpPassword -- Password matching the account. The password has been encoded and the first
WCHAR of the name is the seed
MachineAccountOUs -- Where the information is returned.
Returns:
NERR_Success -- Success
ERROR_INVALID_PARAMETER -- A bad parameter was given
--*/
{
NET_API_STATUS NetStatus = NERR_Success;
LPWSTR EncodedPassword = NULL;
NetStatus = WsImpersonateClient();
//
// Decrypt the password.
//
if ( NetStatus == NERR_Success ) {
NetStatus = JoinpDecryptPasswordWithKey(
RpcBindingHandle,
EncryptedPassword,
TRUE, // encode the password
&EncodedPassword );
}
if ( NetStatus == NERR_Success ) {
//
// Read the current information
//
NetStatus = NetpGetListOfJoinableOUs( lpDomain,
lpAccount,
EncodedPassword,
OUCount,
OUs );
}
//
// Revert back to ourselves
//
WsRevertToSelf();
if ( EncodedPassword != NULL ) {
NetpMemoryFree( EncodedPassword );
}
return( NetStatus );
}
NET_API_STATUS
NET_API_FUNCTION
NetrJoinDomain(
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpDomain,
IN LPWSTR lpMachineAccountOU,
IN LPWSTR lpAccount OPTIONAL,
IN LPWSTR lpPassword OPTIONAL,
IN DWORD fOptions
)
/*++
Routine Description:
Joins the machine to the domain.
Arguments:
lpServer -- Name of the machine being run on
lpDomain -- Domain to join
lpMachineAccountOU -- Optional name of the OU under which to create the machine account
lpAccount -- Account to use for join
lpPassword -- Password matching the account. The password is encoded. The first WCHAR
is the seed.
fOptions -- Options to use when joining the domain
Returns:
NERR_Success -- Success
ERROR_INVALID_PARAMETER -- A bad parameter was given
--*/
{
//
// This version that takes a clear text password isn't supported.
//
return ERROR_NOT_SUPPORTED;
}
NET_API_STATUS
NET_API_FUNCTION
NetrUnjoinDomain(
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpAccount OPTIONAL,
IN LPWSTR lpPassword OPTIONAL,
IN DWORD fJoinOptions
)
/*++
Routine Description:
Unjoins from the joined domain
Arguments:
lpServer -- Name of the machine being run on
lpAccount -- Account to use for unjoining
lpPassword -- Password matching the account. The password is encoded. The first WCHAR is
the seed
fOptions -- Options to use when unjoining the domain
Returns:
NERR_Success -- Name is valid
NERR_SetupNotJoined -- This machine was not joined to a domain
NERR_SetupDomainController -- This machine is a domain controller and
cannot be unjoined
NERR_InternalError -- Can't determine product type
--*/
{
//
// This version that takes a clear text password isn't supported.
//
return ERROR_NOT_SUPPORTED;
}
NET_API_STATUS
NET_API_FUNCTION
NetrValidateName(
IN LPWSTR lpMachine,
IN LPWSTR lpName,
IN LPWSTR lpAccount OPTIONAL,
IN LPWSTR lpPassword OPTIONAL,
IN NETSETUP_NAME_TYPE NameType
)
/*++
Routine Description:
Ensures that the given name is valid for a name of that type
Arguments:
lpMachine -- Name of the machine being run on
lpName -- Name to validate
lpAccount -- Account to use for name validation
lpPassword -- Password matching the account. The password is encoded. The first WCHAR is
the seed
NameType -- Type of the name to validate
Returns:
NERR_Success -- Name is valid
ERROR_INVALID_PARAMETER -- A bad parameter was given
NERR_InvalidComputer -- The name format given is bad
ERROR_DUP_NAME -- The name is invalid for this type
--*/
{
//
// This version that takes a clear text password isn't supported.
//
return ERROR_NOT_SUPPORTED;
}
NET_API_STATUS
NET_API_FUNCTION
NetrRenameMachineInDomain(
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpNewMachineName OPTIONAL,
IN LPWSTR lpAccount OPTIONAL,
IN LPWSTR lpPassword OPTIONAL,
IN DWORD fRenameOptions
)
/*++
Routine Description:
Renames a machine currently joined to a domain.
Arguments:
lpServer -- Name of the machine being run on
lpNewMachineName -- New name for this machine. If the name is specified, it is used
for the new machine name. If it is not specified, it is assumed that SetComputerName
has already been invoked, and that name will be used.
lpAccount -- Account to use for the rename
lpPassword -- Password matching the account. The password has been encoded. The first
WCHAR of the string is the seed.
fOptions -- Options to use for the rename
Returns:
NERR_Success -- Success
ERROR_INVALID_PARAMETER -- A bad parameter was given
--*/
{
//
// This version that takes a clear text password isn't supported.
//
return ERROR_NOT_SUPPORTED;
}
NET_API_STATUS
NET_API_FUNCTION
NetrGetJoinableOUs(
IN LPWSTR lpServer OPTIONAL,
IN LPWSTR lpDomain,
IN LPWSTR lpAccount OPTIONAL,
IN LPWSTR lpPassword OPTIONAL,
OUT DWORD *OUCount,
OUT LPWSTR **OUs
)
/*++
Routine Description:
Renames a machine currently joined to a domain.
Arguments:
lpServer -- Name of the machine being run on
lpDomain -- Domain to join
lpAccount -- Account to use for join
lpPassword -- Password matching the account. The password has been encoded and the first
WCHAR of the name is the seed
MachineAccountOUs -- Where the information is returned.
Returns:
NERR_Success -- Success
ERROR_INVALID_PARAMETER -- A bad parameter was given
--*/
{
//
// This version that takes a clear text password isn't supported.
//
return ERROR_NOT_SUPPORTED;
}
//
// Computer rename preparation APIs
//
NET_API_STATUS
NetpSetPrimarySamAccountName(
IN LPWSTR DomainController,
IN LPWSTR CurrentSamAccountName,
IN LPWSTR NewSamAccountName,
IN LPWSTR DomainAccountName,
IN LPWSTR DomainAccountPassword
)
/*++
Routine Description:
Sets primary SAM account name and teh display name on the
computer object in the DS.
Arguments:
DomainController -- DC name where to modify the computer object.
CurrentSamAccountName -- The current value of SAM account name.
NewSamAccountName -- The new value of SAM account name to be set.
DomainAccount -- Domain account to use for accessing the machine
account object in the DS. May be NULL in which case the
credentials of the user executing this routine are used.
DomainAccountPassword -- Password matching the domain account.
May be NULL in which case the credentials of the user executing
this routine are used.
Note:
This routine uses NetUserSetInfo, downlevel SAM based API.
NetUserSetInfo has an advantage such that it updates the DN of the
computer object to correspond to the new SAM account name. Also,
for a DC's computer object, it follows the serverReferenceBL attribute
link and updates the DN of the server object in the Config container.
The server object in the Config container, in its turn, has a reference
to the computer object (serverReference attrib) -- that reference also
gets updated as the result of NetUserSetInfo call. Updating the two
objects through ldap (instead of NetuserSetInfo) currently can't be done
as one transaction, so we use NetUserSetInfo to do all this for us. We
may reconsider the use of ldap once transactional ldap (i.e. several
ldap operations performed as one transaction) becomes available.
Returns:
NO_ERROR -- Success
Otherwise, error returned by NetUserSetInfo.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
NET_API_STATUS TmpNetStatus = NO_ERROR;
USER_INFO_0 NetUI0;
PUSER_INFO_10 usri10 = NULL;
BOOLEAN Connected = FALSE;
//
// Connect to the DC
//
NetStatus = NetpManageIPCConnect( DomainController,
DomainAccountName,
DomainAccountPassword,
NETSETUPP_CONNECT_IPC );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpSetPrimarySamAccountName: NetpManageIPCConnect failed to connect to %ws: 0x%lx\n",
DomainController,
NetStatus ));
goto Cleanup;
}
Connected = TRUE;
//
// Set the SAM account name
//
NetUI0.usri0_name = NewSamAccountName;
NetStatus = NetUserSetInfo( DomainController,
CurrentSamAccountName,
0,
(PBYTE)&NetUI0,
NULL );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpSetPrimarySamAccountName: NetUserSetInfo failed on %ws: 0x%lx\n",
DomainController,
NetStatus ));
goto Cleanup;
}
//
// Update the display name as well.
// Ignore error as this is not critical.
//
// First get the current display name
//
TmpNetStatus = NetUserGetInfo( DomainController,
NewSamAccountName,
10,
(PBYTE *)&usri10 );
if ( TmpNetStatus != NERR_Success ) {
NetpLog(( "NetpSetPrimarySamAccountName: failed to get display name on %ws (ignored) 0x%lx\n",
DomainController,
TmpNetStatus ));
//
// If the display name exists and is
// different from the new one, update it
//
} else if ( usri10->usri10_full_name != NULL &&
_wcsicmp(usri10->usri10_full_name, NewSamAccountName) != 0 ) {
USER_INFO_1011 usri1011;
usri1011.usri1011_full_name = NewSamAccountName; // new name
TmpNetStatus = NetUserSetInfo( DomainController,
NewSamAccountName,
1011,
(PBYTE)&usri1011,
NULL );
if ( TmpNetStatus != NERR_Success ) {
NetpLog(( "NetpSetPrimarySamAccountName: failed to update display name on %ws (ignored) 0x%lx\n",
DomainController,
TmpNetStatus ));
}
}
Cleanup:
if ( usri10 != NULL ) {
NetApiBufferFree( usri10 );
}
if ( Connected ) {
TmpNetStatus = NetpManageIPCConnect( DomainController,
DomainAccountName,
DomainAccountPassword,
NETSETUPP_DISCONNECT_IPC );
if ( TmpNetStatus != NO_ERROR ) {
NetpLog(( "NetpSetPrimarySamAccountName: NetpManageIPCConnect failed to disconnect from %ws: 0x%lx\n",
DomainController,
TmpNetStatus ));
}
}
return NetStatus;
}
#define NET_ADD_ALTERNATE_COMPUTER_NAME 1
#define NET_DEL_ALTERNATE_COMPUTER_NAME 2
#define NET_SET_PRIMARY_COMPUTER_NAME 3
NET_API_STATUS
NET_API_FUNCTION
NetpManageAltComputerName(
IN handle_t RpcBindingHandle,
IN LPWSTR AlternateName,
IN ULONG Action,
IN LPWSTR DomainAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN ULONG Reserved
)
/*++
Routine Description:
Manages the alternate names for the specified server.
Arguments:
AlternateName -- The name to add.
Action -- Specifies action to take on the name:
NET_ADD_ALTERNATE_COMPUTER_NAME - Add the alternate name.
NET_DEL_ALTERNATE_COMPUTER_NAME - Delete the alternate name.
NET_SET_PRIMARY_COMPUTER_NAME - Set the alternate name as
the primary computer name.
DomainAccount -- Domain account to use for accessing the
machine account object for the specified server in the AD.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
DomainAccountPassword -- Password matching the domain account.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
Reserved -- Reserved for future use. If some flags are specified
that are not supported, they will be ignored if
NET_IGNORE_UNSUPPORTED_FLAGS is set, otherwise this routine
will fail with ERROR_INVALID_FLAGS.
Note:
The process that calls this routine must have administrator
privileges on the local computer to perform the local computer
name modifications. The access check is performed by the local
information APIs.
Returns:
NO_ERROR -- Success
ERROR_NOT_SUPPORTED -- The specified server does not support this
functionality.
ERROR_INVALID_FLAGS - The Flags parameter is incorrect.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
NTSTATUS Status = STATUS_SUCCESS;
ULONG LdapStatus = LDAP_SUCCESS;
NT_PRODUCT_TYPE NtProductType;
LSA_HANDLE LocalPolicyHandle = NULL;
PPOLICY_DNS_DOMAIN_INFO LocalPolicyDns = NULL;
ULONG PrimaryNameSize = DNS_MAX_NAME_BUFFER_LENGTH * sizeof(WCHAR);
WCHAR NewNetbiosMachineName[MAX_COMPUTERNAME_LENGTH + 1];
LPWSTR ComputerName = NULL;
LPWSTR MachineAccountName = NULL;
LPWSTR NewMachineAccountName = NULL;
LPWSTR AccountUserName = NULL;
LPWSTR AccountDomainName = NULL;
LPWSTR DomainAccountPassword = NULL;
LPWSTR MachineAccountNameToCrack = NULL; // not allocated
LPWSTR NameToCrack = NULL;
LPWSTR PrimaryName = NULL;
BOOLEAN ClientImpersonated = FALSE;
BOOLEAN NameModifiedLocally = FALSE;
BOOLEAN PrimarySamAccountNameSet = FALSE;
BOOLEAN ToldNetlogonToAvoidDnsHostNameUpdate = FALSE;
BOOLEAN StopedNetlogon = FALSE;
SERVICE_STATUS NetlogonServiceStatus;
LPQUERY_SERVICE_CONFIG NetlogonServiceConfig = NULL;
RPC_AUTH_IDENTITY_HANDLE AuthId = 0;
HANDLE hDs = NULL;
PLDAP LdapHandle = NULL;
LONG LdapOption;
OBJECT_ATTRIBUTES OA;
PDS_NAME_RESULTW CrackedName = NULL;
PDOMAIN_CONTROLLER_INFOW DcInfo = NULL;
PWSTR AlternateNameValues[2];
PWSTR DnsHostNameValues[2];
PWSTR PrimaryNameValues[2];
LDAPModW DnsHostNameMod;
LDAPModW PrimaryNameMod;
LDAPModW AlternateDnsHostNameMod;
LDAPModW *ModList[4] = {NULL};
ULONG ModCount = 0;
SEC_WINNT_AUTH_IDENTITY AuthIdent = {0};
//
// Ldap modify server control
//
// An LDAP modify request will normally fail if it attempts
// to add an attribute that already exists, or if it attempts
// to delete an attribute that does not exist. With this control,
// as long as the attribute to be added has the same value as
// the existing attribute, then the modify will succeed. With
// this control, deletion of an attribute that doesn't exist
// will also succeed.
//
LDAPControlW ModifyControl =
{
LDAP_SERVER_PERMISSIVE_MODIFY_OID_W,
{
0, NULL
},
FALSE
};
PLDAPControlW ModifyControlArray[2] =
{
&ModifyControl,
NULL
};
//
// Initialize the log file
//
NetSetuppOpenLog();
NetpLog(( "NetpManageAltComputerName called:\n" ));
NetpLog(( " AlternateName = %ws\n", AlternateName ));
NetpLog(( " DomainAccount = %ws\n", DomainAccount ));
NetpLog(( " Action = 0x%lx\n", Action ));
NetpLog(( " Flags = 0x%lx\n", Reserved ));
//
// This API is supported on DCs and servers only
//
if ( !RtlGetNtProductType( &NtProductType ) ) {
NtProductType = NtProductWinNt;
}
if ( NtProductType != NtProductServer &&
NtProductType != NtProductLanManNt ) {
NetpLog(( "NetpManageAltComputerName: Not supported on wksta: %lu\n",
NtProductType ));
NetStatus = ERROR_NOT_SUPPORTED;
goto Cleanup;
}
//
// Validate the Flags
//
// If some flags are passed which we don't understand
// and we are not told to ignore them, error out.
//
if ( Reserved != 0 &&
(Reserved & NET_IGNORE_UNSUPPORTED_FLAGS) == 0 ) {
NetpLog(( "NetpManageAltComputerName: Invalid Flags passed\n" ));
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
//
// Validate the alternate name passed
//
NetStatus = DnsValidateName_W( AlternateName, DnsNameHostnameFull );
if ( NetStatus != NO_ERROR && NetStatus != DNS_ERROR_NON_RFC_NAME ) {
NetpLog(( "NetpManageAltComputerName: DnsValidateName failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Decrypt the domain account password
//
NetStatus = JoinpDecryptPasswordWithKey(
RpcBindingHandle,
EncryptedPassword,
FALSE, // don't encode the password
&DomainAccountPassword );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: JoinpDecryptPasswordWithKey failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// If there is no domain account passed,
// ignore the domain account password (if any)
//
if ( DomainAccount == NULL &&
DomainAccountPassword != NULL ) {
NetpMemoryFree( DomainAccountPassword );
DomainAccountPassword = NULL;
}
//
// Separate the domain account into
// the user and domain parts for later use
//
if ( DomainAccount != NULL ) {
NetStatus = NetpSeparateUserAndDomain( DomainAccount,
&AccountUserName,
&AccountDomainName );
}
if ( NetStatus != NERR_Success ) {
NetpLog(( "NetpGetComputerObjectDn: Cannot NetpSeparateUserAndDomain 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Get the current Netbios machine name
//
NetStatus = NetpGetComputerName( &ComputerName );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: NetpGetComputerName failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Get SAM machine account name from the Netbios machine name
//
NetStatus = NetpGetMachineAccountName( ComputerName, &MachineAccountName );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: NetpGetMachineAccountName failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Get the current primary DNS host name
//
PrimaryName = LocalAlloc( LMEM_ZEROINIT, PrimaryNameSize );
if ( PrimaryName == NULL ) {
NetpLog(( "NetpManageAltComputerName: LocalAlloc for PrimaryName failed\n" ));
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
NetStatus = EnumerateLocalComputerNamesW(
PrimaryComputerName, // type of name
0, // reserved
PrimaryName,
&PrimaryNameSize );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: EnumerateLocalComputerNamesW failed with Size 0x%lx: 0x%lx\n",
PrimaryNameSize,
NetStatus ));
goto Cleanup;
}
//
// If we are to rename the machine,
// get the new machine account name from
// the DNS name passed
//
if ( Action == NET_SET_PRIMARY_COMPUTER_NAME ) {
ULONG Size = MAX_COMPUTERNAME_LENGTH + 1;
if ( !DnsHostnameToComputerNameW(AlternateName,
NewNetbiosMachineName,
&Size) ) {
NetStatus = GetLastError();
NetpLog(( "NetpManageAltComputerName: DnsHostnameToComputerNameW failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Get the new SAM machine account name from the new Netbios machine name
//
NetStatus = NetpGetMachineAccountName( NewNetbiosMachineName, &NewMachineAccountName );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: NetpGetMachineAccountName (2) failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
}
//
// Open the local LSA policy
//
InitializeObjectAttributes( &OA, NULL, 0, NULL, NULL );
Status = LsaOpenPolicy( NULL,
&OA,
MAXIMUM_ALLOWED,
&LocalPolicyHandle );
if ( !NT_SUCCESS(Status) ) {
NetpLog(( "NetpManageAltComputerName: LsaOpenPolicy failed: 0x%lx\n",
Status ));
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// Get the current domain information from LSA
//
Status = LsaQueryInformationPolicy( LocalPolicyHandle,
PolicyDnsDomainInformation,
(PVOID *) &LocalPolicyDns );
if ( !NT_SUCCESS(Status) ) {
NetpLog(( "NetpManageAltComputerName: LsaQueryInformationPolicy failed: 0x%lx\n",
Status ));
NetStatus = NetpNtStatusToApiStatus( Status );
goto Cleanup;
}
//
// Do the local opperation for the specified alternate name
//
// Impersonate the caller. The local API will perform the
// access check on the caller on our behalf.
//
NetStatus = WsImpersonateClient();
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: WsImpersonateClient failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
ClientImpersonated = TRUE;
//
// Do local operations
//
if ( Action == NET_ADD_ALTERNATE_COMPUTER_NAME ) {
NetStatus = AddLocalAlternateComputerName( AlternateName,
0 ); // reserved
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: AddLocalAlternateComputerName failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
} else if ( Action == NET_DEL_ALTERNATE_COMPUTER_NAME ) {
NetStatus = RemoveLocalAlternateComputerName( AlternateName,
0 ); // reserved
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: RemoveLocalAlternateComputerName failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
} else if ( Action == NET_SET_PRIMARY_COMPUTER_NAME ) {
NetStatus = SetLocalPrimaryComputerName( AlternateName,
0 ); // reserved
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: SetLocalPrimaryComputerName failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
}
NameModifiedLocally = TRUE;
//
// We are done with local operations; we are going
// to do remote operations on the DC next.
//
// If the user credentials are supplied, revert
// the impersonation -- we will access the remote
// server with explicit credentials supplied.
// Otherwise, access the DC while running in the
// user context.
//
if ( DomainAccount != NULL ) {
WsRevertToSelf();
ClientImpersonated = FALSE;
}
//
// If this machine is not joined to an AD domain,
// there is nothing to do on the DC.
//
if ( LocalPolicyDns->Sid == NULL ||
LocalPolicyDns->DnsDomainName.Length == 0 ) {
NetStatus = NO_ERROR;
goto Cleanup;
}
//
// Discover a DC for the domain of this machine
// to modify the computer object in the DS.
//
NetStatus = DsGetDcNameWithAccountW(
NULL,
MachineAccountName,
UF_WORKSTATION_TRUST_ACCOUNT | UF_SERVER_TRUST_ACCOUNT,
NULL,
NULL,
NULL,
DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME,
&DcInfo );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: DsGetDcNameWithAccountW failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// If this machine is a DC, verify that we got it.
// We do this because we want to avoid inconsistent
// state where the local name stored in registry
// is different from name stored locally in the DS.
//
if ( NtProductType == NtProductLanManNt &&
!DnsNameCompare_W(PrimaryName, DcInfo->DomainControllerName+2) ) {
NetpLog(( "NetpManageAltComputerName: Got different DC '%ws' than local DC '%ws'\n",
DcInfo->DomainControllerName+2,
PrimaryName ));
NetStatus = ERROR_NO_SUCH_DOMAIN;
goto Cleanup;
}
//
// We've got a DC. Bind to the DS to get the DN
// for our machine account and do a LDAP connect
// to modify our machine account given the DN
//
NetStatus = DsMakePasswordCredentials( AccountUserName,
AccountDomainName,
DomainAccountPassword,
&AuthId );
if ( NetStatus != NERR_Success ) {
NetpLog(( "NetpManageAltComputerName: DsMakePasswordCredentials failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Bind to the DS on the DC.
//
NetStatus = DsBindWithCredW( DcInfo->DomainControllerName,
NULL,
AuthId,
&hDs );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: DsBindWithCredW failed to '%ws': 0x%lx\n",
DcInfo->DomainControllerName,
NetStatus ));
goto Cleanup ;
}
//
// Open an LDAP connection to the DC and set useful options
//
LdapHandle = ldap_initW( DcInfo->DomainControllerName+2,
LDAP_PORT );
if ( LdapHandle == NULL ) {
LdapStatus = LdapGetLastError();
NetpLog(( "NetpManageAltComputerName: ldap_init to %ws failed: %lu\n",
DcInfo->DomainControllerName+2,
LdapStatus ));
NetStatus = LdapMapErrorToWin32( LdapStatus );
goto Cleanup;
}
//
// Tell LDAP to avoid chasing referals
//
LdapOption = PtrToLong( LDAP_OPT_OFF );
LdapStatus = ldap_set_optionW( LdapHandle,
LDAP_OPT_REFERRALS,
&LdapOption );
if ( LdapStatus != LDAP_SUCCESS ) {
NetpLog(( "NetpManageAltComputerName: ldap_set_option LDAP_OPT_REFERRALS failed on %ws: %ld: %s\n",
DcInfo->DomainControllerName+2,
LdapStatus,
ldap_err2stringA(LdapStatus) ));
NetStatus = LdapMapErrorToWin32( LdapStatus );
goto Cleanup;
}
//
// Tell LDAP we are passing an explicit DC name
// to avoid the DC discovery
//
LdapOption = PtrToLong( LDAP_OPT_ON );
LdapStatus = ldap_set_optionW( LdapHandle,
LDAP_OPT_AREC_EXCLUSIVE,
&LdapOption );
if ( LdapStatus != LDAP_SUCCESS ) {
NetpLog(( "NetpManageAltComputerName: ldap_set_option LDAP_OPT_AREC_EXCLUSIVE failed on %ws: %ld: %s\n",
DcInfo->DomainControllerName+2,
LdapStatus,
ldap_err2stringA(LdapStatus) ));
NetStatus = LdapMapErrorToWin32( LdapStatus );
goto Cleanup;
}
//
// Bind to the LDAP server
//
if ( AccountUserName != NULL ) {
AuthIdent.User = AccountUserName;
AuthIdent.UserLength = wcslen( AccountUserName );
}
if ( AccountDomainName != NULL ) {
AuthIdent.Domain = AccountDomainName;
AuthIdent.DomainLength = wcslen( AccountDomainName );
}
if ( DomainAccountPassword != NULL ) {
AuthIdent.Password = DomainAccountPassword;
AuthIdent.PasswordLength = wcslen( DomainAccountPassword );
}
AuthIdent.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
LdapStatus = ldap_bind_sW( LdapHandle,
NULL,
(PWSTR) &AuthIdent,
LDAP_AUTH_NEGOTIATE );
if ( LdapStatus != LDAP_SUCCESS ) {
NetpLog(( "NetpManageAltComputerName: ldap_bind failed on %ws: %ld: %s\n",
DcInfo->DomainControllerName+2,
LdapStatus,
ldap_err2stringA(LdapStatus) ));
NetStatus = LdapMapErrorToWin32( LdapStatus );
goto Cleanup;
}
//
// Ok, now that we have all prerequsites satisfied,
// do the operations that may require rollback if
// they fail.
//
// Set the primary SAM account name by doing NetSetUser.
// Note that this will also rename the DN of the computer
// object (and its display name) and the DN of the server
// object linked to from the computer object if this is a DC.
//
if ( Action == NET_SET_PRIMARY_COMPUTER_NAME ) {
NetStatus = NetpSetPrimarySamAccountName(
DcInfo->DomainControllerName,
MachineAccountName,
NewMachineAccountName,
DomainAccount,
DomainAccountPassword );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: NetpSetPrimarySamAccountName failed on %ws: 0x%lx\n",
DcInfo->DomainControllerName,
NetStatus ));
goto Cleanup;
}
PrimarySamAccountNameSet = TRUE;
//
// We need to crack the new machine account
// name which we just set
//
MachineAccountNameToCrack = NewMachineAccountName;
//
// If we are not changing the primary name,
// the name to crack is the current machine name.
//
} else {
MachineAccountNameToCrack = MachineAccountName;
}
//
// Now get the DN for our machine account object
// in the DS. Do this after setting the SAM account
// name as it changes the DN.
//
// Form the NT4 account name 'domain\account' to crack
// it into the DN.
//
NameToCrack = LocalAlloc( LMEM_ZEROINIT,
LocalPolicyDns->Name.Length + // Netbios domain name
(1 + // backslash
wcslen(MachineAccountNameToCrack) + // SAM account name
1) * sizeof(WCHAR) ); // NULL terminator
if ( NameToCrack == NULL ) {
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
goto Cleanup;
}
RtlCopyMemory( NameToCrack,
LocalPolicyDns->Name.Buffer,
LocalPolicyDns->Name.Length );
wcscat( NameToCrack, L"\\" );
wcscat( NameToCrack, MachineAccountNameToCrack );
//
// Crack the account name into a DN
//
NetStatus = DsCrackNamesW( hDs,
0,
DS_NT4_ACCOUNT_NAME,
DS_FQDN_1779_NAME,
1,
&NameToCrack,
&CrackedName );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: DsCrackNames failed on '%ws' for %ws: 0x%lx\n",
DcInfo->DomainControllerName,
NameToCrack,
NetStatus ));
goto Cleanup;
}
//
// Check for consistency
//
if ( CrackedName->rItems[0].status != DS_NAME_NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: CrackNames failed for %ws: substatus 0x%lx\n",
NameToCrack,
CrackedName->rItems[0].status ));
NetStatus = NetpCrackNamesStatus2Win32Error( CrackedName->rItems[0].status );
goto Cleanup;
}
if ( CrackedName->cItems > 1 ) {
NetStatus = ERROR_DS_NAME_ERROR_NOT_UNIQUE;
NetpLog(( "NetpManageAltComputerName: Cracked Name %ws is not unique on %ws: %lu\n",
NameToCrack,
DcInfo->DomainControllerName,
CrackedName->cItems ));
goto Cleanup;
}
//
// Ok, we have our machine account DN. Proceed with modifying
// our machine account object in the DS.
//
// If we are seting new DnsHostName, we have to stop netlogon
// and tell it not to update this attribute before the
// reboot.
//
if ( Action == NET_SET_PRIMARY_COMPUTER_NAME ) {
//
// First get the current status of netlogon so that
// we can roll back properly on failure
//
NetStatus = NetpQueryService( SERVICE_NETLOGON,
&NetlogonServiceStatus,
&NetlogonServiceConfig );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: NetpQueryService failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Stop netlogon if it's running
//
if ( NetlogonServiceStatus.dwCurrentState != SERVICE_STOPPED ) {
NetStatus = NetpControlServices( NETSETUP_SVC_STOPPED,
NETSETUPP_SVC_NETLOGON );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: NetpControlServices failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
StopedNetlogon = TRUE;
}
//
// Tell netlogon not to update DnsHostName until reboot
// in case the user decides to start netlogon before reboot
// for some reason
//
NetpAvoidNetlogonSpnSet( TRUE );
ToldNetlogonToAvoidDnsHostNameUpdate = TRUE;
}
//
// Prepare attributes that need to be set in the DS
//
// If we are seting a primary name, we need to set
// DnsHostName attribute. Also, we need to add the
// current primary computer name to the additional
// DNS host name list.
//
if ( Action == NET_SET_PRIMARY_COMPUTER_NAME ) {
DnsHostNameValues[0] = AlternateName;
DnsHostNameValues[1] = NULL;
DnsHostNameMod.mod_type = L"DnsHostName";
DnsHostNameMod.mod_values = DnsHostNameValues;
DnsHostNameMod.mod_op = LDAP_MOD_REPLACE;
ModList[ModCount++] = &DnsHostNameMod;
//
// Add the current primary to additional list
//
PrimaryNameValues[0] = PrimaryName;
PrimaryNameValues[1] = NULL;
PrimaryNameMod.mod_type = L"msDS-AdditionalDnsHostName";
PrimaryNameMod.mod_values = PrimaryNameValues;
PrimaryNameMod.mod_op = LDAP_MOD_ADD;
ModList[ModCount++] = &PrimaryNameMod;
}
//
// Prepare the additional DNS host name modification.
//
// Note that we don't need to manipulate the additional
// SAM account name as it will be added/deleted by the DS
// itself as the result of the AdditionalDnsHostName update.
//
AlternateNameValues[0] = AlternateName;
AlternateNameValues[1] = NULL;
AlternateDnsHostNameMod.mod_type = L"msDS-AdditionalDnsHostName";
AlternateDnsHostNameMod.mod_values = AlternateNameValues;
//
// If we are additing an alternate name, the operation
// is add. Otherwise, we are deleting the alternate
// name or setting the alternate name as primary: in
// both cases the alternate name should be deleted
// from the additional name attribute list.
//
if ( Action == NET_ADD_ALTERNATE_COMPUTER_NAME ) {
AlternateDnsHostNameMod.mod_op = LDAP_MOD_ADD;
} else {
AlternateDnsHostNameMod.mod_op = LDAP_MOD_DELETE;
}
ModList[ModCount++] = &AlternateDnsHostNameMod;
//
// Write the modifications
//
LdapStatus = ldap_modify_ext_sW( LdapHandle,
CrackedName->rItems[0].pName, // DN of account
ModList,
ModifyControlArray, // server controls
NULL ); // no client controls
if ( LdapStatus != LDAP_SUCCESS ) {
NetpLog(( "NetpManageAltComputerName: ldap_modify_ext_s failed on %ws: %ld: %s\n",
DcInfo->DomainControllerName+2,
LdapStatus,
ldap_err2stringA(LdapStatus) ));
NetStatus = LdapMapErrorToWin32( LdapStatus );
goto Cleanup;
}
Cleanup:
//
// Revert impersonation
//
if ( ClientImpersonated ) {
WsRevertToSelf();
}
//
// On error, revert the changes. Do this after reverting
// impersonation to have as much (LocalSystem) access
// as one possibly can.
//
// Note that we don't need to revert ldap modifications
// because they were made as the last step.
//
if ( NetStatus != NO_ERROR && NameModifiedLocally ) {
NET_API_STATUS TmpNetStatus = NO_ERROR;
//
// If we added an alternate name, remove it
//
if ( Action == NET_ADD_ALTERNATE_COMPUTER_NAME ) {
TmpNetStatus = RemoveLocalAlternateComputerName( AlternateName,
0 ); // reserved
if ( TmpNetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: (rollback) RemoveLocalAlternateComputerName failed 0x%lx\n",
TmpNetStatus ));
}
//
// If we removed an alternate name, add it
//
} else if ( Action == NET_DEL_ALTERNATE_COMPUTER_NAME ) {
TmpNetStatus = AddLocalAlternateComputerName( AlternateName,
0 ); // reserved
if ( TmpNetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: (rollback) AddLocalAlternateComputerName failed 0x%lx\n",
TmpNetStatus ));
}
//
// If we set a new primary name, reset it to the previous value
//
} else if ( Action == NET_SET_PRIMARY_COMPUTER_NAME ) {
TmpNetStatus = SetLocalPrimaryComputerName( PrimaryName,
0 ); // reserved
if ( TmpNetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: (rollback) SetLocalPrimaryComputerName failed 0x%lx\n",
TmpNetStatus ));
}
}
}
if ( NetStatus != NO_ERROR && PrimarySamAccountNameSet ) {
NET_API_STATUS TmpNetStatus = NO_ERROR;
TmpNetStatus = NetpSetPrimarySamAccountName(
DcInfo->DomainControllerName,
NewMachineAccountName, // the changed name
MachineAccountName, // old name to restore
DomainAccount,
DomainAccountPassword );
if ( TmpNetStatus != NO_ERROR) {
NetpLog(( "NetpManageAltComputerName: NetpSetPrimarySamAccountName (rollback) failed on %ws: 0x%lx\n",
DcInfo->DomainControllerName,
TmpNetStatus ));
}
}
//
// On error, take back what we told netlogon
// w.r.t. the DnsHostName update
//
if ( NetStatus != NO_ERROR && ToldNetlogonToAvoidDnsHostNameUpdate ) {
NetpAvoidNetlogonSpnSet( FALSE );
}
//
// On error, restart netlogon if we stoped it
//
if ( NetStatus != NO_ERROR && StopedNetlogon ) {
NET_API_STATUS TmpNetStatus = NO_ERROR;
TmpNetStatus = NetpControlServices( NETSETUP_SVC_STARTED,
NETSETUPP_SVC_NETLOGON );
if ( TmpNetStatus != NO_ERROR ) {
NetpLog(( "NetpManageAltComputerName: (rollback) Failed starting netlogon: 0x%lx\n",
TmpNetStatus ));
}
}
//
// Close the log file
//
NetSetuppCloseLog();
//
// Finally free the memory
//
if ( DomainAccountPassword != NULL ) {
NetpMemoryFree( DomainAccountPassword );
}
if ( AccountUserName != NULL ) {
NetApiBufferFree( AccountUserName );
}
if ( AccountDomainName != NULL ) {
NetApiBufferFree( AccountDomainName );
}
if ( ComputerName != NULL ) {
NetApiBufferFree( ComputerName );
}
if ( MachineAccountName != NULL ) {
NetApiBufferFree( MachineAccountName );
}
if ( NewMachineAccountName != NULL ) {
NetApiBufferFree( NewMachineAccountName );
}
if ( PrimaryName != NULL ) {
LocalFree( PrimaryName );
}
if ( NameToCrack != NULL ) {
LocalFree( NameToCrack );
}
if ( NetlogonServiceConfig != NULL ) {
LocalFree( NetlogonServiceConfig );
}
if ( LocalPolicyDns != NULL ) {
LsaFreeMemory( LocalPolicyDns );
}
if ( LocalPolicyHandle != NULL ) {
LsaClose( LocalPolicyHandle );
}
if ( DcInfo != NULL ) {
NetApiBufferFree( DcInfo );
}
if ( AuthId ) {
DsFreePasswordCredentials( AuthId );
}
if ( CrackedName ) {
DsFreeNameResultW( CrackedName );
}
if ( hDs ) {
DsUnBind( &hDs );
}
if ( LdapHandle != NULL ) {
ldap_unbind_s( LdapHandle );
}
return NetStatus;
}
NET_API_STATUS
NET_API_FUNCTION
NetrAddAlternateComputerName(
IN handle_t RpcBindingHandle,
IN LPWSTR ServerName OPTIONAL,
IN LPWSTR AlternateName,
IN LPWSTR DomainAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN ULONG Reserved
)
/*++
Routine Description:
Adds an alternate name for the specified server.
Arguments:
ServerName -- Name of server on which to execute this function.
AlternateName -- The name to add.
DomainAccount -- Domain account to use for accessing the
machine account object for the specified server in the AD.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
DomainAccountPassword -- Password matching the domain account.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
Reserved -- Reserved for future use. If some flags are specified
that are not supported, they will be ignored if
NET_IGNORE_UNSUPPORTED_FLAGS is set, otherwise this routine
will fail with ERROR_INVALID_FLAGS.
Note:
The process that calls this routine must have administrator
privileges on the server computer.
Returns:
NO_ERROR -- Success
ERROR_NOT_SUPPORTED -- The specified server does not support this
functionality.
ERROR_INVALID_FLAGS - The Flags parameter is incorrect.
--*/
{
//
// Call the common routine
//
return NetpManageAltComputerName(
RpcBindingHandle,
AlternateName,
NET_ADD_ALTERNATE_COMPUTER_NAME,
DomainAccount,
EncryptedPassword,
Reserved );
}
NET_API_STATUS
NET_API_FUNCTION
NetrRemoveAlternateComputerName(
IN handle_t RpcBindingHandle,
IN LPWSTR ServerName OPTIONAL,
IN LPWSTR AlternateName,
IN LPWSTR DomainAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN ULONG Reserved
)
/*++
Routine Description:
Deletes an alternate name for the specified server.
Arguments:
ServerName -- Name of server on which to execute this function.
AlternateName -- The name to delete.
DomainAccount -- Domain account to use for accessing the
machine account object for the specified server in the AD.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
DomainAccountPassword -- Password matching the domain account.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
Reserved -- Reserved for future use. If some flags are specified
that are not supported, they will be ignored if
NET_IGNORE_UNSUPPORTED_FLAGS is set, otherwise this routine
will fail with ERROR_INVALID_FLAGS.
Note:
The process that calls this routine must have administrator
privileges on the server computer.
Returns:
NO_ERROR -- Success
ERROR_NOT_SUPPORTED -- The specified server does not support this
functionality.
ERROR_INVALID_FLAGS - The Flags parameter is incorrect.
--*/
{
//
// Call the common routine
//
return NetpManageAltComputerName(
RpcBindingHandle,
AlternateName,
NET_DEL_ALTERNATE_COMPUTER_NAME,
DomainAccount,
EncryptedPassword,
Reserved );
}
NET_API_STATUS
NET_API_FUNCTION
NetrSetPrimaryComputerName(
IN handle_t RpcBindingHandle,
IN LPWSTR ServerName OPTIONAL,
IN LPWSTR PrimaryName,
IN LPWSTR DomainAccount OPTIONAL,
IN PJOINPR_ENCRYPTED_USER_PASSWORD EncryptedPassword OPTIONAL,
IN ULONG Reserved
)
/*++
Routine Description:
Sets the primary computer name for the specified server.
Arguments:
ServerName -- Name of server on which to execute this function.
PrimaryName -- The primary computer name to set.
DomainAccount -- Domain account to use for accessing the
machine account object for the specified server in the AD.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
DomainAccountPassword -- Password matching the domain account.
Not used if the server is not joined to a domain. May be
NULL in which case the credentials of the user executing
this routine are used.
Reserved -- Reserved for future use. If some flags are specified
that are not supported, they will be ignored if
NET_IGNORE_UNSUPPORTED_FLAGS is set, otherwise this routine
will fail with ERROR_INVALID_FLAGS.
Note:
The process that calls this routine must have administrator
privileges on the server computer.
Returns:
NO_ERROR -- Success
ERROR_NOT_SUPPORTED -- The specified server does not support this
functionality.
ERROR_INVALID_FLAGS - The Flags parameter is incorrect.
--*/
{
//
// Call the common routine
//
return NetpManageAltComputerName(
RpcBindingHandle,
PrimaryName,
NET_SET_PRIMARY_COMPUTER_NAME,
DomainAccount,
EncryptedPassword,
Reserved );
}
NET_API_STATUS
NET_API_FUNCTION
NetrEnumerateComputerNames(
IN LPWSTR ServerName OPTIONAL,
IN NET_COMPUTER_NAME_TYPE NameType,
IN ULONG Reserved,
OUT PNET_COMPUTER_NAME_ARRAY *ComputerNames
)
/*++
Routine Description:
Enumerates computer names for the specified server.
Arguments:
ServerName -- Name of server on which to execute this function.
NameType -- The type of the name queried.
Reserved -- Reserved for future use. If some flags are specified
that are not supported, they will be ignored if
NET_IGNORE_UNSUPPORTED_FLAGS is set, otherwise this routine
will fail with ERROR_INVALID_FLAGS.
ComputerNames - Returns the computer names structure.
Returns:
NO_ERROR -- Success
ERROR_NOT_SUPPORTED -- The specified server does not support this
functionality.
ERROR_INVALID_FLAGS - The Flags parameter is incorrect.
--*/
{
NET_API_STATUS NetStatus = NO_ERROR;
NT_PRODUCT_TYPE NtProductType;
BOOL ClientImpersonated = FALSE;
ULONG Size = 0;
ULONG Index = 0;
ULONG EntryCount = 0;
LPWSTR LocalNames = NULL;
PNET_COMPUTER_NAME_ARRAY LocalArray = NULL;
LPTSTR_ARRAY TStrArray;
//
// Initialize the log file
//
NetSetuppOpenLog();
NetpLog(( "NetrEnumerateComputerNames called: NameType = 0x%lx, Flags = 0x%lx\n",
NameType, Reserved ));
//
// This API is supported on DCs and servers only
//
if ( !RtlGetNtProductType( &NtProductType ) ) {
NtProductType = NtProductWinNt;
}
if ( NtProductType != NtProductServer &&
NtProductType != NtProductLanManNt ) {
NetpLog(( "NetrEnumerateComputerNames: Not supported on wksta: %lu\n",
NtProductType ));
NetStatus = ERROR_NOT_SUPPORTED;
goto Cleanup;
}
//
// Validate the Flags
//
// If some flags are passed which we don't understand
// and we are not told to ignore them, error out.
//
if ( Reserved != 0 &&
(Reserved & NET_IGNORE_UNSUPPORTED_FLAGS) == 0 ) {
NetpLog(( "NetrEnumerateComputerNames: Invalid Flags passed\n" ));
NetStatus = ERROR_INVALID_FLAGS;
goto Cleanup;
}
//
// Validate the name type
//
if ( NameType >= NetComputerNameTypeMax ) {
NetpLog(( "NetrEnumerateComputerNames: Invalid name type passed %lu\n",
NameType ));
NetStatus = ERROR_INVALID_PARAMETER;
goto Cleanup;
}
//
// Impersonate the caller. The local API will perform the
// access check on the caller on our behalf.
//
NetStatus = WsImpersonateClient();
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetrEnumerateComputerNames: WsImpersonateClient failed: 0x%lx\n",
NetStatus ));
goto Cleanup;
}
ClientImpersonated = TRUE;
//
// Get the size of the local data
//
NetStatus = EnumerateLocalComputerNamesW(
NameType,
0, // reserved
LocalNames,
&Size ); // in characters, Null included
//
// Allocate memory for local names
//
if ( NetStatus != NO_ERROR ) {
if ( NetStatus == ERROR_MORE_DATA ) {
NetStatus = NetApiBufferAllocate( Size * sizeof(WCHAR), &LocalNames );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
} else {
NetpLog(( "NetrEnumerateComputerNames: EnumerateLocalComputerNamesW failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
}
//
// Get the names
//
NetStatus = EnumerateLocalComputerNamesW(
NameType,
0, // reserved
LocalNames,
&Size );
if ( NetStatus != NO_ERROR ) {
NetpLog(( "NetrEnumerateComputerNames: EnumerateLocalComputerNamesW (2) failed 0x%lx\n",
NetStatus ));
goto Cleanup;
}
//
// Determine the length of the returned information
//
Size = sizeof( NET_COMPUTER_NAME_ARRAY );
TStrArray = LocalNames;
while ( !NetpIsTStrArrayEmpty(TStrArray) ) {
Size += sizeof(UNICODE_STRING) + (wcslen(TStrArray) + 1) * sizeof(WCHAR);
EntryCount++;
TStrArray = NetpNextTStrArrayEntry( TStrArray );
}
//
// Allocate the return buffer.
//
NetStatus = NetApiBufferAllocate( Size, &LocalArray );
if ( NetStatus != NO_ERROR ) {
goto Cleanup;
}
LocalArray->EntryCount = EntryCount;
//
// If there are names to return, copy them
// to the return buffer
//
if ( EntryCount == 0 ) {
LocalArray->ComputerNames = NULL;
} else {
PUNICODE_STRING Strings;
LPBYTE Where;
Strings = (PUNICODE_STRING) (LocalArray + 1);
LocalArray->ComputerNames = Strings;
Where = (LPBYTE) &Strings[EntryCount];
//
// Loop copying the names into the return buffer.
//
Index = 0;
TStrArray = LocalNames;
while ( !NetpIsTStrArrayEmpty(TStrArray) ) {
Strings[Index].Buffer = (LPWSTR) Where;
Strings[Index].Length = wcslen(TStrArray) * sizeof(WCHAR);
Strings[Index].MaximumLength = Strings[Index].Length + sizeof(WCHAR);
RtlCopyMemory( Where, TStrArray, Strings[Index].MaximumLength );
Where += Strings[Index].MaximumLength;
Index++;
TStrArray = NetpNextTStrArrayEntry( TStrArray );
}
}
NetStatus = NO_ERROR;
Cleanup:
//
// Revert impersonation
//
if ( ClientImpersonated ) {
WsRevertToSelf();
}
//
// Return names on success or clean up on error
//
if ( NetStatus == NO_ERROR ) {
*ComputerNames = LocalArray;
//
// Be verbose
//
if ( LocalArray->EntryCount > 0 ) {
NetpLog(( "NetrEnumerateComputerNames: Returning names:" ));
for ( Index = 0; Index < LocalArray->EntryCount; Index++ ) {
NetpLog(( " %wZ", &(LocalArray->ComputerNames[Index]) ));
}
NetpLog(( "\n" ));
} else {
NetpLog(( "NetrEnumerateComputerNames: No names returned\n" ));
}
} else {
if ( LocalArray != NULL ) {
NetApiBufferFree( LocalArray );
}
NetpLog(( "NetrEnumerateComputerNames: Failed 0x%lx\n", NetStatus ));
}
if ( LocalNames != NULL ) {
NetApiBufferFree( LocalNames );
}
//
// Close the log file
//
NetSetuppCloseLog();
return NetStatus;
}