You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
8080 lines
237 KiB
8080 lines
237 KiB
/*++
|
|
|
|
Copyright (c) 1998 - 1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
ldapjoin.c
|
|
|
|
Abstract:
|
|
|
|
NetJoin support functions for accessing the DS via LDAP, validating names, and handling LSA
|
|
functionality
|
|
|
|
Author:
|
|
|
|
Mac McLain (MacM) 27-Jan-1998 Name validation code based on ui\common\lmobj\lmobj code
|
|
by ThomasPa
|
|
|
|
Environment:
|
|
|
|
User mode only.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
// Netlib uses DsGetDcName AND is linked into netapi32 where DsGetDcName is
|
|
// implemented. So define that we aren't importing the API.
|
|
#define _DSGETDCAPI_
|
|
|
|
#include <netsetp.h>
|
|
#include <lmaccess.h>
|
|
#include <wincrypt.h>
|
|
|
|
#define WKSTA_NETLOGON
|
|
#define NETSETUP_JOIN
|
|
|
|
#include <confname.h>
|
|
#include <winldap.h>
|
|
#include <nb30.h>
|
|
#include <msgrutil.h>
|
|
#include <lmaccess.h>
|
|
#include <lmuse.h>
|
|
#include <lmwksta.h>
|
|
#include <stdio.h>
|
|
#include <ntddbrow.h>
|
|
#include <netlibnt.h>
|
|
#include <ntddnfs.h>
|
|
#include <remboot.h>
|
|
#include <dns.h>
|
|
#include <ntsam.h>
|
|
#include <rpc.h>
|
|
#include <ntdsapi.h>
|
|
#include <netlogon.h>
|
|
#include <logonp.h>
|
|
#include <wchar.h>
|
|
#include <icanon.h> // NetpNameCanonicalize
|
|
#include <tstring.h> // STRLEN
|
|
#include <autoenr.h> // Autoenrol routine
|
|
|
|
#include "joinp.h"
|
|
|
|
|
|
#define NETSETUPP_WINLOGON_PATH L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\"
|
|
#define NETSETUPP_WINLOGON_CAD L"DisableCAD"
|
|
|
|
#define NETSETUPP_ALL_FILTER L"(ObjectClass=*)"
|
|
#define NETSETUPP_OU_FILTER L"(ObjectClass=OrganizationalUnit)"
|
|
#define NETSETUPP_RETURNED_ATTR L"AllowedChildClassesEffective"
|
|
#define NETSETUPP_DN_ATTR L"DistinguishedName"
|
|
#define NETSETUPP_WELL_KNOWN L"WellKnownObjects"
|
|
#define NETSETUPP_COMPUTER_OBJECT L"Computer"
|
|
#define NETSETUPP_OBJ_PREFIX L"CN="
|
|
#define NETSETUPP_ACCNT_TYPE_ENABLED L"4096"
|
|
#define NETSETUPP_ACCNT_TYPE_DISABLED L"4098"
|
|
|
|
//
|
|
// DNS registration removal function prototype
|
|
//
|
|
|
|
typedef DWORD (APIENTRY *DNS_REGISTRATION_REMOVAL_FN) ( VOID );
|
|
typedef DWORD (APIENTRY *DNS_REGISTRATION_ADDITION_FN) ( LPWSTR );
|
|
|
|
//
|
|
// Locally defined macros
|
|
//
|
|
#define clearncb(x) memset((char *)x,'\0',sizeof(NCB))
|
|
|
|
|
|
NTSTATUS
|
|
NetpGetLsaHandle(
|
|
IN LPWSTR lpServer, OPTIONAL
|
|
IN PLSA_HANDLE pPolicyHandleIn, OPTIONAL
|
|
OUT PLSA_HANDLE pPolicyHandleOut
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Either returns the given LSA handle if it's valid, or opens a new one
|
|
|
|
Arguments:
|
|
|
|
lpServer -- server name : NULL == local policy
|
|
pPolicyHandleIn -- Potentially open policy handle
|
|
pPolicyHandleOut -- Open policy handle returned here
|
|
|
|
Returns:
|
|
|
|
STATUS_SUCCESS -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
OBJECT_ATTRIBUTES OA;
|
|
UNICODE_STRING Server, *pServer = NULL;
|
|
|
|
if ( pPolicyHandleIn == NULL || *pPolicyHandleIn == NULL )
|
|
{
|
|
if ( lpServer != NULL )
|
|
{
|
|
RtlInitUnicodeString( &Server, lpServer );
|
|
pServer = &Server;
|
|
}
|
|
|
|
//
|
|
// Open the local policy
|
|
//
|
|
InitializeObjectAttributes( &OA, NULL, 0, NULL, NULL );
|
|
|
|
Status = LsaOpenPolicy( pServer, &OA, MAXIMUM_ALLOWED, pPolicyHandleOut );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpGetLsaHandle: LsaOpenPolicy on %ws failed: 0x%lx\n",
|
|
GetStrPtr(lpServer), Status ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pPolicyHandleOut = *pPolicyHandleIn;
|
|
}
|
|
|
|
return( Status );
|
|
}
|
|
|
|
|
|
VOID
|
|
NetpSetLsaHandle(
|
|
IN LSA_HANDLE OpenHandle,
|
|
OUT PLSA_HANDLE pReturnedHandle
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Either closes the opened handle or returns it
|
|
|
|
Arguments:
|
|
|
|
OpenHandle -- Handle returned from NetpGetLsaHandle
|
|
pReturnedHandle -- handle is passed back to the caller if requested
|
|
|
|
Returns:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
if ( pReturnedHandle == NULL )
|
|
{
|
|
if ( OpenHandle != NULL )
|
|
{
|
|
LsaClose( OpenHandle );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*pReturnedHandle = OpenHandle;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetLsaPrimaryDomain(
|
|
IN LPWSTR lpDomain,
|
|
IN PSID pDomainSid, OPTIONAL
|
|
IN PPOLICY_DNS_DOMAIN_INFO pPolicyDns, OPTIONAL
|
|
OUT PLSA_HANDLE pPolicyHandle OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Sets the primary domain in the local LSA policy
|
|
|
|
Arguments:
|
|
|
|
lpDomain -- Name of the domain to join
|
|
pDomainSid -- Primary domain sid to be set
|
|
pPolicyDns -- DNS domain info
|
|
pPolicyHandle -- handle returned if non-null
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_HANDLE LocalPolicy = NULL;
|
|
POLICY_PRIMARY_DOMAIN_INFO PolicyPDI;
|
|
|
|
|
|
Status = NetpGetLsaHandle( NULL, pPolicyHandle, &LocalPolicy );
|
|
|
|
//
|
|
// Now, build the primary domain info, and set it
|
|
//
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
RtlInitUnicodeString( &(PolicyPDI.Name), lpDomain );
|
|
PolicyPDI.Sid = pDomainSid;
|
|
|
|
Status = LsaSetInformationPolicy( LocalPolicy,
|
|
PolicyPrimaryDomainInformation,
|
|
( PVOID )&PolicyPDI );
|
|
|
|
if ( NT_SUCCESS( Status ) && pPolicyDns )
|
|
{
|
|
Status = LsaSetInformationPolicy( LocalPolicy,
|
|
PolicyDnsDomainInformation,
|
|
( PVOID )pPolicyDns );
|
|
}
|
|
}
|
|
|
|
NetpSetLsaHandle( LocalPolicy, pPolicyHandle );
|
|
|
|
NetpLog(( "NetpSetLsaPrimaryDomain: for '%ws' status: 0x%lx\n", GetStrPtr(lpDomain), Status ));
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetLsaPrimaryDomain(
|
|
IN LPWSTR lpServer, OPTIONAL
|
|
OUT PPOLICY_PRIMARY_DOMAIN_INFO *ppPolicyPDI,
|
|
OUT PPOLICY_DNS_DOMAIN_INFO *ppPolicyDns,
|
|
OUT PLSA_HANDLE pPolicyHandle OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the primary domain info in the local LSA policy
|
|
|
|
Arguments:
|
|
|
|
PolicyHandle -- Handle to the open policy. If NULL, a new handle is
|
|
opened.
|
|
lpServer -- Optional server name on which to read the policy
|
|
ppPolicyPDI -- Primary domain policy returned here
|
|
ppPolicyDNS -- Dns domain information is returned here if it exists
|
|
pPolicyHandle -- Optional. Policy handle returned here if not null
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_HANDLE LocalPolicy = NULL;
|
|
UNICODE_STRING Server, *pServer = NULL;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*ppPolicyPDI = NULL;
|
|
*ppPolicyDns = NULL;
|
|
|
|
if ( lpServer != NULL )
|
|
{
|
|
RtlInitUnicodeString( &Server, lpServer );
|
|
pServer = &Server;
|
|
}
|
|
|
|
Status = NetpGetLsaHandle( lpServer, pPolicyHandle, &LocalPolicy );
|
|
|
|
//
|
|
// Now, get the primary domain info
|
|
//
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Status = LsaQueryInformationPolicy( LocalPolicy,
|
|
PolicyDnsDomainInformation,
|
|
( PVOID * )ppPolicyDns );
|
|
|
|
if ( Status == RPC_NT_PROCNUM_OUT_OF_RANGE )
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
*ppPolicyDns = NULL;
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Status = LsaQueryInformationPolicy( LocalPolicy,
|
|
PolicyPrimaryDomainInformation,
|
|
(PVOID *)ppPolicyPDI);
|
|
|
|
if ( !NT_SUCCESS( Status ) && (*ppPolicyDns) != NULL )
|
|
{
|
|
LsaFreeMemory( *ppPolicyDns );
|
|
*ppPolicyDns = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
NetpSetLsaHandle( LocalPolicy, pPolicyHandle );
|
|
|
|
NetpLog(( "NetpGetLsaPrimaryDomain: status: 0x%lx\n", Status ));
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetLsaDcRole(
|
|
IN LPWSTR lpMachine,
|
|
OUT BOOL *pfIsDC
|
|
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Gets the role of the DC in the domain
|
|
|
|
Arguments:
|
|
|
|
lpMachine -- Machine to connect to
|
|
pfIsDC -- If TRUE, this is a DC.
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PBYTE pBuff;
|
|
LSA_HANDLE hPolicy = NULL;
|
|
|
|
Status = NetpGetLsaHandle( lpMachine, NULL, &hPolicy );
|
|
|
|
|
|
|
|
//
|
|
// Now, get the server role info
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
Status = LsaQueryInformationPolicy( hPolicy,
|
|
PolicyLsaServerRoleInformation,
|
|
&pBuff);
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
|
|
if ( *(PPOLICY_LSA_SERVER_ROLE)pBuff == PolicyServerRoleBackup ||
|
|
*(PPOLICY_LSA_SERVER_ROLE)pBuff == PolicyServerRolePrimary ) {
|
|
|
|
*pfIsDC = TRUE;
|
|
|
|
} else {
|
|
|
|
*pfIsDC = FALSE;
|
|
}
|
|
|
|
LsaFreeMemory( pBuff );
|
|
}
|
|
LsaClose( hPolicy );
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "NetpGetLsaDcRole failed with 0x%lx\n", Status ));
|
|
}
|
|
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
NTSTATUS
|
|
NetpLsaOpenSecret(
|
|
IN LSA_HANDLE hLsa,
|
|
IN PUNICODE_STRING pusSecretName,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
OUT PLSA_HANDLE phSecret
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Open the specified LSA secret as self.
|
|
|
|
LsaQuerySecret fails for a network client whent the client is not
|
|
trusted (see lsa\server\dbsecret.c). This causes remote join
|
|
operation to fail. To get around this, this function temporarily
|
|
un-impersonates, opens the secrets and impersonates again.
|
|
Thus the open secret occurrs in LocalSystem context.
|
|
|
|
$ REVIEW kumarp 15-July-1999
|
|
This is obviously not a good design. This should be changed post NT5.
|
|
|
|
Arguments:
|
|
|
|
same as those for LsaOpenSecret
|
|
|
|
Returns:
|
|
|
|
NTSTATUS, see help for LsaOpenSecret
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_UNSUCCESSFUL;
|
|
HANDLE hToken=NULL;
|
|
|
|
__try
|
|
{
|
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE,
|
|
TRUE, &hToken))
|
|
{
|
|
if (SetThreadToken(NULL, NULL))
|
|
{
|
|
Status = LsaOpenSecret(hLsa, pusSecretName,
|
|
DesiredAccess, phSecret);
|
|
}
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (hToken)
|
|
{
|
|
if ( !SetThreadToken(NULL, hToken) ) {
|
|
NetpLog(( "NetpLsaOpenSecret: Couldn't reset the user token 0x%lx\n",
|
|
GetLastError() ));
|
|
Status = NetpApiStatusToNtStatus( GetLastError() );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( hToken ) {
|
|
CloseHandle( hToken );
|
|
}
|
|
|
|
NetpLog(( "NetpLsaOpenSecret: status: 0x%lx\n", Status ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpManageMachineSecret(
|
|
IN LPWSTR lpMachine,
|
|
IN LPWSTR lpPassword,
|
|
IN DWORD Action,
|
|
IN BOOL UseDefaultForOldPwd,
|
|
OUT PLSA_HANDLE pPolicyHandle OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create/delete the machine secret
|
|
|
|
|
|
Arguments:
|
|
|
|
lpMachine -- Machine to add/delete the secret for
|
|
lpPassword -- Machine password to use.
|
|
Action -- Action to take
|
|
UseDefaultForOldPwd - if TRUE, the default password should be set
|
|
for the old password value. Used only if
|
|
secret is created.
|
|
pPolicyHandle -- If present, the opened policy handle is returned here
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_HANDLE LocalPolicy = NULL, SecretHandle = NULL;
|
|
UNICODE_STRING Key, Data, *CurrentValue = NULL;
|
|
BOOLEAN SecretCreated = FALSE;
|
|
WCHAR MachinePasswordBuffer[PWLEN + 1];
|
|
UNICODE_STRING MachinePassword;
|
|
BOOLEAN FreeCurrentValue = FALSE;
|
|
|
|
if( Action == NETSETUPP_CREATE )
|
|
{
|
|
ASSERT( lpPassword );
|
|
}
|
|
|
|
Status = NetpGetLsaHandle( NULL, pPolicyHandle, &LocalPolicy );
|
|
|
|
//
|
|
// open/create the secret
|
|
//
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
RtlInitUnicodeString( &Key, L"$MACHINE.ACC" );
|
|
RtlInitUnicodeString( &Data, lpPassword );
|
|
|
|
Status = NetpLsaOpenSecret( LocalPolicy, &Key,
|
|
Action == NETSETUPP_CREATE ?
|
|
SECRET_SET_VALUE | SECRET_QUERY_VALUE : DELETE,
|
|
&SecretHandle );
|
|
|
|
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND )
|
|
{
|
|
if ( Action == NETSETUPP_DELETE )
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
Status = LsaCreateSecret( LocalPolicy, &Key,
|
|
SECRET_SET_VALUE, &SecretHandle );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
SecretCreated = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpManageMachineSecret: Open/Create secret failed: 0x%lx\n", Status ));
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( Action == NETSETUPP_CREATE )
|
|
{
|
|
//
|
|
// First, read the current value, so we can save it as the old value
|
|
//
|
|
|
|
if ( !UseDefaultForOldPwd ) {
|
|
if ( SecretCreated )
|
|
{
|
|
CurrentValue = &Data;
|
|
}
|
|
else
|
|
{
|
|
Status = LsaQuerySecret( SecretHandle, &CurrentValue,
|
|
NULL, NULL, NULL );
|
|
FreeCurrentValue = TRUE;
|
|
}
|
|
|
|
//
|
|
// If we are to use the default value for old password,
|
|
// generate the default value
|
|
//
|
|
|
|
} else {
|
|
NetpGenerateDefaultPassword(lpMachine, MachinePasswordBuffer);
|
|
RtlInitUnicodeString( &MachinePassword, MachinePasswordBuffer );
|
|
CurrentValue = &MachinePassword;
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
//
|
|
// Now, store both the new password and the old
|
|
//
|
|
Status = LsaSetSecret( SecretHandle, &Data, CurrentValue );
|
|
|
|
if ( FreeCurrentValue )
|
|
{
|
|
LsaFreeMemory( CurrentValue );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No secret handle means we failed earlier in
|
|
// some intermediate state. That's ok, just press on.
|
|
//
|
|
if ( SecretHandle != NULL )
|
|
{
|
|
Status = LsaDelete( SecretHandle );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
SecretHandle = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( SecretHandle )
|
|
{
|
|
LsaClose( SecretHandle );
|
|
}
|
|
}
|
|
|
|
NetpSetLsaHandle( LocalPolicy, pPolicyHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpManageMachineSecret: '%s' operation failed: 0x%lx\n",
|
|
Action == NETSETUPP_CREATE ? "CREATE" : "DELETE", Status ));
|
|
}
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpReadCurrentSecret(
|
|
OUT LPWSTR *lpCurrentSecret,
|
|
OUT PLSA_HANDLE pPolicyHandle OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reads the value of the current secret
|
|
|
|
|
|
Arguments:
|
|
|
|
lpCurrentSecret -- Where the current secret is returned
|
|
pPolicyHandle -- If present, the opened policy handle is returned here
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_HANDLE LocalPolicy = NULL, SecretHandle;
|
|
UNICODE_STRING Secret, *Data = NULL;
|
|
|
|
Status = NetpGetLsaHandle( NULL, pPolicyHandle, &LocalPolicy );
|
|
|
|
//
|
|
// Now, read the secret
|
|
//
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
RtlInitUnicodeString( &Secret, L"$MACHINE.ACC" );
|
|
|
|
// Status = LsaRetrievePrivateData( LocalPolicy, &Key, &Data );
|
|
|
|
Status = NetpLsaOpenSecret( LocalPolicy,
|
|
&Secret,
|
|
SECRET_QUERY_VALUE,
|
|
&SecretHandle );
|
|
|
|
if ( NT_SUCCESS(Status) ) {
|
|
Status = LsaQuerySecret( SecretHandle,
|
|
&Data,
|
|
NULL,
|
|
NULL,
|
|
NULL );
|
|
|
|
LsaClose( SecretHandle );
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) ) {
|
|
|
|
if( NetApiBufferAllocate( Data->Length + sizeof( WCHAR ),
|
|
( PBYTE * )lpCurrentSecret ) != NERR_Success ) {
|
|
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( ( PVOID )*lpCurrentSecret,
|
|
Data->Buffer,
|
|
Data->Length );
|
|
|
|
( *lpCurrentSecret )[ Data->Length / sizeof( WCHAR ) ] = UNICODE_NULL;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
NetpSetLsaHandle( LocalPolicy, pPolicyHandle );
|
|
|
|
if ( Data != NULL ) {
|
|
LsaFreeMemory( Data );
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "NetpReadCurrentSecret: failed: 0x%lx\n", Status ));
|
|
}
|
|
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetNetlogonDomainCache(
|
|
IN LPWSTR lpDc
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initializes NetLogons trusted domain cache, using the trusted
|
|
domain list on the DC.
|
|
|
|
|
|
Arguments:
|
|
|
|
lpDc -- Name of a DC in the domain
|
|
The caller should already have an valid connection to IPC$
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
DWORD dwErr = ERROR_SUCCESS;
|
|
|
|
PDS_DOMAIN_TRUSTSW TrustedDomains=NULL;
|
|
ULONG TrustedDomainCount=0;
|
|
|
|
|
|
//
|
|
// Get the trusted domain list from the DC.
|
|
//
|
|
dwErr = DsEnumerateDomainTrustsW( lpDc, DS_DOMAIN_VALID_FLAGS,
|
|
&TrustedDomains, &TrustedDomainCount );
|
|
|
|
//
|
|
// If the server does not support returning all trust types
|
|
// (i.e. the server is an NT4 machine) ask for only those
|
|
// which it can return.
|
|
//
|
|
if ( dwErr == ERROR_NOT_SUPPORTED ) {
|
|
|
|
NetpLog(( "NetpSetNetlogonDomainCache: DsEnumerateDomainTrustsW for all trusts failed with ERROR_NOT_SUPPORTED -- retry\n"));
|
|
dwErr = DsEnumerateDomainTrustsW(
|
|
lpDc,
|
|
DS_DOMAIN_PRIMARY | DS_DOMAIN_DIRECT_OUTBOUND,
|
|
&TrustedDomains,
|
|
&TrustedDomainCount );
|
|
|
|
if ( dwErr == ERROR_NOT_SUPPORTED ) {
|
|
//
|
|
// looks like the DC is running NT3.51. In this case, we do not want
|
|
// to fail the join operation because we could not write
|
|
// the netlogon cache. reset the error code.
|
|
//
|
|
// see bug "359684 Win2k workstation unable to join NT3.51Domain"
|
|
//
|
|
NetpLog(( "NetpSetNetlogonDomainCache: DsEnumerateDomainTrustsW for some trusts failed with ERROR_NOT_SUPPORTED -- ignore\n"));
|
|
dwErr = ERROR_SUCCESS;
|
|
} else if ( dwErr != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpSetNetlogonDomainCache: DsEnumerateDomainTrustsW for some trusts failed with 0x%lx\n", dwErr ));
|
|
}
|
|
|
|
} else if ( dwErr != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpSetNetlogonDomainCache: DsEnumerateDomainTrustsW failed 0x%lx\n", dwErr ));
|
|
}
|
|
|
|
|
|
if ( dwErr == ERROR_SUCCESS ) {
|
|
|
|
//
|
|
// Write the trusted domain list to a file where netlogon will find it.
|
|
//
|
|
|
|
if ( TrustedDomainCount > 0 ) {
|
|
dwErr = NlWriteFileForestTrustList (
|
|
NL_FOREST_BINARY_LOG_FILE_JOIN,
|
|
TrustedDomains,
|
|
TrustedDomainCount );
|
|
} else {
|
|
NetpLog(( "NetpSetNetlogonDomainCache: No trusts to write\n" ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Disable the no Ctrl-Alt-Del from the winlogon side of things
|
|
//
|
|
|
|
if ( dwErr == ERROR_SUCCESS ) {
|
|
HKEY hWinlogon;
|
|
|
|
dwErr = RegOpenKey( HKEY_LOCAL_MACHINE,
|
|
NETSETUPP_WINLOGON_PATH, &hWinlogon );
|
|
|
|
if ( dwErr == ERROR_SUCCESS ) {
|
|
DWORD Value;
|
|
|
|
Value = 0;
|
|
dwErr = RegSetValueEx( hWinlogon, NETSETUPP_WINLOGON_CAD, 0,
|
|
REG_DWORD, (PBYTE)&Value, sizeof( ULONG ) );
|
|
|
|
RegCloseKey( hWinlogon );
|
|
}
|
|
|
|
//
|
|
// Failing to set this never causes failure
|
|
//
|
|
if ( dwErr != ERROR_SUCCESS ) {
|
|
|
|
NetpLog(( "Setting Winlogon DisableCAD failed with %lu\n", dwErr ));
|
|
dwErr = ERROR_SUCCESS;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Free locally used resources
|
|
//
|
|
|
|
if ( TrustedDomains != NULL ) {
|
|
NetApiBufferFree( TrustedDomains );
|
|
}
|
|
|
|
return dwErr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
Is this a terminal-server-application-server?
|
|
|
|
Arguments:
|
|
|
|
Args - none
|
|
|
|
Return Value:
|
|
|
|
TRUE or FALSE
|
|
|
|
--*/
|
|
BOOL IsAppServer(void)
|
|
{
|
|
OSVERSIONINFOEX osVersionInfo;
|
|
DWORDLONG dwlConditionMask = 0;
|
|
BOOL fIsWTS;
|
|
|
|
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
fIsWTS = GetVersionEx((OSVERSIONINFO *)&osVersionInfo) &&
|
|
(osVersionInfo.wSuiteMask & VER_SUITE_TERMINAL) &&
|
|
!(osVersionInfo.wSuiteMask & VER_SUITE_SINGLEUSERTS);
|
|
|
|
return fIsWTS;
|
|
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpManageLocalGroups(
|
|
IN PSID pDomainSid,
|
|
IN BOOL fDelete
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Performs SAM account handling to either add or remove the DomainAdmins,
|
|
etc groups from the local groups.
|
|
|
|
|
|
Arguments:
|
|
|
|
pDomainSid -- SID of the domain being joined/left
|
|
fDelete -- Whether to add or remove the admin alias
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
//
|
|
// Keep these in synch with the rids and Sids below
|
|
//
|
|
ULONG LocalRids[] =
|
|
{
|
|
DOMAIN_ALIAS_RID_ADMINS,
|
|
DOMAIN_ALIAS_RID_USERS
|
|
};
|
|
|
|
PWSTR ppwszLocalGroups[ sizeof( LocalRids ) / sizeof( ULONG )] =
|
|
{
|
|
NULL,
|
|
NULL
|
|
};
|
|
|
|
ULONG Rids[] =
|
|
{
|
|
DOMAIN_GROUP_RID_ADMINS,
|
|
DOMAIN_GROUP_RID_USERS
|
|
};
|
|
|
|
BOOLEAN GroupMustExist[ sizeof( LocalRids ) / sizeof( ULONG )] =
|
|
{
|
|
TRUE,
|
|
TRUE
|
|
};
|
|
|
|
static SID_IDENTIFIER_AUTHORITY BultinAuth = SECURITY_NT_AUTHORITY;
|
|
DWORD Sids[sizeof( SID )/sizeof( DWORD ) + SID_MAX_SUB_AUTHORITIES][sizeof(Rids) / sizeof(ULONG)];
|
|
DWORD cDSidSize, *pLastSub, i, j;
|
|
PUCHAR pSubAuthCnt;
|
|
PWSTR LocalGroupName = NULL;
|
|
PWCHAR DomainName = NULL;
|
|
ULONG Size, DomainSize;
|
|
SID_NAME_USE SNE;
|
|
ULONG numOfGroups;
|
|
|
|
|
|
cDSidSize = RtlLengthSid( pDomainSid );
|
|
|
|
// number of groups to process
|
|
numOfGroups = sizeof(Rids) / sizeof(ULONG);
|
|
|
|
|
|
for ( i = 0 ; i < numOfGroups && NetStatus == NERR_Success; i++)
|
|
{
|
|
Size = 0;
|
|
DomainSize = 0;
|
|
|
|
if ( DomainName != NULL ) {
|
|
NetApiBufferFree( DomainName );
|
|
DomainName = NULL;
|
|
}
|
|
|
|
//
|
|
// Get the name of the local group first...
|
|
//
|
|
RtlInitializeSid( ( PSID )Sids[ i ], &BultinAuth, 2 );
|
|
|
|
*(RtlSubAuthoritySid(( PSID )Sids[ i ], 0)) = SECURITY_BUILTIN_DOMAIN_RID;
|
|
*(RtlSubAuthoritySid(( PSID )Sids[ i ], 1)) = LocalRids[ i ];
|
|
|
|
LookupAccountSidW( NULL, ( PSID )Sids[ i ], NULL, &Size,
|
|
DomainName, &DomainSize, &SNE );
|
|
|
|
if ( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
|
|
{
|
|
NetStatus = NetApiBufferAllocate( Size * sizeof(WCHAR),
|
|
&LocalGroupName );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
NetStatus = NetApiBufferAllocate( DomainSize * sizeof(WCHAR),
|
|
&DomainName );
|
|
}
|
|
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
if ( !LookupAccountSid( NULL, ( PSID )Sids[ i ], LocalGroupName,
|
|
&Size, DomainName, &DomainSize, &SNE ) )
|
|
{
|
|
NetStatus = GetLastError();
|
|
|
|
if ( NetStatus == ERROR_NONE_MAPPED && GroupMustExist[ i ] == FALSE )
|
|
{
|
|
NetStatus = NERR_Success;
|
|
continue;
|
|
|
|
}
|
|
else
|
|
{
|
|
#ifdef NETSETUP_VERBOSE_LOGGING
|
|
UNICODE_STRING DisplaySid;
|
|
NTSTATUS Status2;
|
|
RtlZeroMemory( &DisplaySid, sizeof( UNICODE_STRING ) );
|
|
|
|
Status2 = RtlConvertSidToUnicodeString( &DisplaySid,
|
|
( PSID )Sids[ i ], TRUE );
|
|
|
|
if ( NT_SUCCESS( Status2 ) )
|
|
{
|
|
NetpLog(( "LookupAccounSid on %wZ failed with %lu\n",
|
|
&DisplaySid,
|
|
NetStatus ));
|
|
RtlFreeUnicodeString(&DisplaySid);
|
|
|
|
}
|
|
else
|
|
{
|
|
NetpLog(( "LookupAccounSid on <undisplayable sid> "
|
|
"failed with %lu\n",
|
|
NetStatus ));
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ppwszLocalGroups[ i ] = LocalGroupName;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
RtlCopyMemory( (PBYTE)Sids[i], pDomainSid, cDSidSize );
|
|
|
|
//
|
|
// Now, add the new domain relative rid
|
|
//
|
|
pSubAuthCnt = GetSidSubAuthorityCount( (PSID)Sids[i] );
|
|
|
|
(*pSubAuthCnt)++;
|
|
|
|
pLastSub = GetSidSubAuthority( (PSID)Sids[i], (*pSubAuthCnt) - 1 );
|
|
|
|
*pLastSub = Rids[i];
|
|
|
|
|
|
if ( fDelete == NETSETUPP_CREATE)
|
|
{
|
|
NetStatus = NetLocalGroupAddMember( NULL,
|
|
ppwszLocalGroups[i],
|
|
(PSID)Sids[i] );
|
|
|
|
if ( NetStatus == ERROR_MEMBER_IN_ALIAS )
|
|
{
|
|
NetStatus = NERR_Success;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
NetStatus = NetLocalGroupDelMember( NULL,
|
|
ppwszLocalGroups[i],
|
|
(PSID)Sids[i] );
|
|
|
|
if ( NetStatus == ERROR_MEMBER_NOT_IN_ALIAS )
|
|
{
|
|
NetStatus = NERR_Success;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If something failed, try to restore what was deleted
|
|
//
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
for ( j = 0; j < i; j++ ) {
|
|
|
|
if ( fDelete == NETSETUPP_DELETE)
|
|
{
|
|
NetLocalGroupAddMember( NULL,
|
|
ppwszLocalGroups[j],
|
|
(PSID)Sids[j] );
|
|
}
|
|
else
|
|
{
|
|
NetLocalGroupDelMember( NULL,
|
|
ppwszLocalGroups[j],
|
|
(PSID)Sids[j] );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( DomainName != NULL ) {
|
|
NetApiBufferFree( DomainName );
|
|
}
|
|
|
|
for ( i = 0; i < numOfGroups ; i++ )
|
|
{
|
|
if ( ppwszLocalGroups[ i ] )
|
|
{
|
|
NetApiBufferFree( ppwszLocalGroups[ i ] );
|
|
}
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
NetpLog(( "NetpManageLocalGroups failed with %lu\n", NetStatus ));
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpHandleJoinedStateInfo(
|
|
IN PNETSETUP_SAVED_JOIN_STATE SavedState,
|
|
IN BOOLEAN Save,
|
|
OUT PLSA_HANDLE ReturnedPolicyHandle OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Saves or restores the join state info.
|
|
|
|
Arguments:
|
|
|
|
SavedState -- join state info
|
|
This includes:
|
|
- machine account secret value
|
|
- primary domain info
|
|
- dns domain info
|
|
|
|
Save -- TRUE == save state, FALSE == restore state
|
|
|
|
ReturnedPolicyHandle -- local LSA handle returned in this
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_HANDLE LocalPolicy = NULL, SecretHandle;
|
|
UNICODE_STRING Secret;
|
|
|
|
if ( Save )
|
|
{
|
|
RtlZeroMemory( SavedState, sizeof( NETSETUP_SAVED_JOIN_STATE ) );
|
|
}
|
|
|
|
//
|
|
// get handle to local LSA policy
|
|
//
|
|
Status = NetpGetLsaHandle( NULL, ReturnedPolicyHandle, &LocalPolicy );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
//
|
|
// First, read the machine account secret
|
|
//
|
|
RtlInitUnicodeString( &Secret, L"$MACHINE.ACC" );
|
|
|
|
Status = NetpLsaOpenSecret( LocalPolicy,
|
|
&Secret,
|
|
SECRET_QUERY_VALUE | SECRET_SET_VALUE,
|
|
&SecretHandle );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( Save )
|
|
{
|
|
SavedState->MachineSecret = TRUE;
|
|
Status = LsaQuerySecret( SecretHandle,
|
|
&( SavedState->CurrentValue ),
|
|
NULL,
|
|
&( SavedState->PreviousValue ),
|
|
NULL );
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( SavedState ->MachineSecret )
|
|
{
|
|
Status = LsaSetSecret( SecretHandle,
|
|
SavedState->CurrentValue,
|
|
SavedState->PreviousValue );
|
|
}
|
|
}
|
|
LsaClose( SecretHandle );
|
|
}
|
|
|
|
//
|
|
// If machine secret is not present, it is not an error.
|
|
//
|
|
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND )
|
|
{
|
|
if ( Save )
|
|
{
|
|
SavedState->MachineSecret = FALSE;
|
|
}
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Now, save/restore the policy information
|
|
//
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( Save )
|
|
{
|
|
Status = NetpGetLsaPrimaryDomain( NULL,
|
|
&( SavedState->PrimaryDomainInfo ),
|
|
&( SavedState->DnsDomainInfo ),
|
|
&LocalPolicy );
|
|
}
|
|
else
|
|
{
|
|
Status = LsaSetInformationPolicy( LocalPolicy,
|
|
PolicyPrimaryDomainInformation,
|
|
SavedState->PrimaryDomainInfo );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
Status = LsaSetInformationPolicy( LocalPolicy,
|
|
PolicyDnsDomainInformation,
|
|
SavedState->DnsDomainInfo );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
NetpSetLsaHandle( LocalPolicy, ReturnedPolicyHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpHandleJoinedStateInfo: '%s' operation failed: 0x%lx\n",
|
|
Save ? "Save" : "Restore", Status ));
|
|
}
|
|
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
MsgFmtNcbName(
|
|
OUT PCHAR DestBuf,
|
|
IN LPTSTR Name,
|
|
IN DWORD Type)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
FmtNcbName - format a name NCB-style
|
|
|
|
Given a name, a name type, and a destination address, this
|
|
function copies the name and the type to the destination in
|
|
the format used in the name fields of a Network Control
|
|
Block.
|
|
|
|
|
|
SIDE EFFECTS
|
|
|
|
Modifies 16 bytes starting at the destination address.
|
|
|
|
Arguments:
|
|
|
|
DestBuf - Pointer to the destination buffer.
|
|
|
|
Name - Unicode NUL-terminated name string
|
|
|
|
Type - Name type number (0, 3, 5, or 32) (3=NON_FWD, 5=FWD)
|
|
|
|
|
|
|
|
Return Value:
|
|
|
|
NERR_Success - The operation was successful
|
|
|
|
Translated Return Code from the Rtl Translate routine.
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD i; // Counter
|
|
NTSTATUS ntStatus;
|
|
NET_API_STATUS status;
|
|
OEM_STRING ansiString;
|
|
UNICODE_STRING unicodeString;
|
|
PCHAR pAnsiString;
|
|
|
|
|
|
//
|
|
// Force the name to be upper case.
|
|
//
|
|
status = NetpNameCanonicalize(
|
|
NULL,
|
|
Name,
|
|
Name,
|
|
STRSIZE(Name),
|
|
NAMETYPE_MESSAGEDEST,
|
|
0);
|
|
if (status != NERR_Success) {
|
|
return(status);
|
|
}
|
|
|
|
//
|
|
// Convert the unicode name string into an ansi string - using the
|
|
// current locale.
|
|
//
|
|
#ifdef UNICODE
|
|
unicodeString.Length = (USHORT)(STRLEN(Name)*sizeof(WCHAR));
|
|
unicodeString.MaximumLength = (USHORT)((STRLEN(Name)+1) * sizeof(WCHAR));
|
|
unicodeString.Buffer = Name;
|
|
|
|
ntStatus = RtlUnicodeStringToOemString(
|
|
&ansiString,
|
|
&unicodeString,
|
|
TRUE); // Allocate the ansiString Buffer.
|
|
|
|
if (!NT_SUCCESS(ntStatus))
|
|
{
|
|
NetpLog(( "FmtNcbName: RtlUnicodeStringToOemString failed 0x%lx\n",
|
|
ntStatus ));
|
|
|
|
return NetpNtStatusToApiStatus(ntStatus);
|
|
}
|
|
|
|
pAnsiString = ansiString.Buffer;
|
|
*(pAnsiString+ansiString.Length) = '\0';
|
|
#else
|
|
UNUSED(ntStatus);
|
|
UNUSED(unicodeString);
|
|
UNUSED(ansiString);
|
|
pAnsiString = Name;
|
|
#endif // UNICODE
|
|
|
|
//
|
|
// copy each character until a NUL is reached, or until NCBNAMSZ-1
|
|
// characters have been copied.
|
|
//
|
|
for (i=0; i < NCBNAMSZ - 1; ++i) {
|
|
if (*pAnsiString == '\0') {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Copy the Name
|
|
//
|
|
|
|
*DestBuf++ = *pAnsiString++;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Free the buffer that RtlUnicodeStringToOemString created for us.
|
|
// NOTE: only the ansiString.Buffer portion is free'd.
|
|
//
|
|
|
|
#ifdef UNICODE
|
|
RtlFreeOemString( &ansiString);
|
|
#endif // UNICODE
|
|
|
|
//
|
|
// Pad the name field with spaces
|
|
//
|
|
for(; i < NCBNAMSZ - 1; ++i) {
|
|
*DestBuf++ = ' ';
|
|
}
|
|
|
|
//
|
|
// Set the name type.
|
|
//
|
|
NetpAssert( Type!=5 ); // 5 is not valid for NT.
|
|
|
|
*DestBuf = (CHAR) Type; // Set name type
|
|
|
|
return(NERR_Success);
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpCheckNetBiosNameNotInUse(
|
|
IN LPWSTR pszName,
|
|
IN BOOLEAN MachineName,
|
|
IN BOOLEAN Unique
|
|
)
|
|
{
|
|
NCB ncb;
|
|
LANA_ENUM lanaBuffer;
|
|
unsigned char i;
|
|
unsigned char nbStatus;
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
WCHAR szMachineNameBuf[MAX_COMPUTERNAME_LENGTH + 1];
|
|
LPWSTR szMachineName=szMachineNameBuf;
|
|
|
|
|
|
//
|
|
// Find the number of networks by sending an enum request via Netbios.
|
|
//
|
|
|
|
clearncb(&ncb);
|
|
ncb.ncb_command = NCBENUM; // Enumerate LANA nums (wait)
|
|
ncb.ncb_buffer = (PUCHAR)&lanaBuffer;
|
|
ncb.ncb_length = sizeof(LANA_ENUM);
|
|
|
|
nbStatus = Netbios (&ncb);
|
|
if (nbStatus != NRC_GOODRET)
|
|
{
|
|
NetStatus = NetpNetBiosStatusToApiStatus( nbStatus );
|
|
goto Cleanup;
|
|
}
|
|
|
|
clearncb(&ncb);
|
|
|
|
NetStatus = MsgFmtNcbName( (char *)ncb.ncb_name, pszName,
|
|
MachineName ? 0 : 0x1c );
|
|
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Move the Adapter Numbers (lana) into the array that will contain them.
|
|
//
|
|
for ( i = 0; i < lanaBuffer.length && NetStatus == NERR_Success; i++ )
|
|
{
|
|
NetpNetBiosReset( lanaBuffer.lana[i] );
|
|
|
|
if ( Unique )
|
|
{
|
|
ncb.ncb_command = NCBADDNAME;
|
|
}
|
|
else
|
|
{
|
|
ncb.ncb_command = NCBADDGRNAME;
|
|
}
|
|
|
|
ncb.ncb_lana_num = lanaBuffer.lana[i];
|
|
nbStatus = Netbios( &ncb );
|
|
|
|
switch ( nbStatus )
|
|
{
|
|
case NRC_DUPNAME:
|
|
// NRC_DUPNAME ==
|
|
// "A duplicate name existed in the local name table"
|
|
//
|
|
// In this case, we need to check if the name being checked
|
|
// is the same as the local computer name. If so,
|
|
// the name is expected to be in the local table therefore
|
|
// we convert this errcode to a success code
|
|
//
|
|
NetStatus = NetpGetComputerNameAllocIfReqd(
|
|
&szMachineName, MAX_COMPUTERNAME_LENGTH+1);
|
|
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
if (!_wcsicmp(szMachineName, pszName))
|
|
{
|
|
NetStatus = NERR_Success;
|
|
}
|
|
else
|
|
{
|
|
NetStatus = ERROR_DUP_NAME;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NRC_INUSE:
|
|
NetStatus = ERROR_DUP_NAME;
|
|
break;
|
|
|
|
case NRC_GOODRET:
|
|
// Delete the name
|
|
ncb.ncb_command = NCBDELNAME;
|
|
ncb.ncb_lana_num = lanaBuffer.lana[i];
|
|
// Not much we can do if this fails.
|
|
Netbios( &ncb );
|
|
// fall through
|
|
|
|
default:
|
|
NetStatus = NetpNetBiosStatusToApiStatus( nbStatus );
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
NetpLog(( "NetpCheckNetBiosNameNotInUse: for '%ws' returned: 0x%lx\n",
|
|
pszName, NetStatus ));
|
|
}
|
|
|
|
if (szMachineName != szMachineNameBuf)
|
|
{
|
|
NetApiBufferFree(szMachineName);
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpIsValidDomainName(
|
|
IN LPWSTR lpName,
|
|
IN LPWSTR lpServer,
|
|
IN LPWSTR lpAccount,
|
|
IN LPWSTR lpPassword
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determines if a name is a DC name or not. Copied from
|
|
ui\net\common\src\lmboj\lmobj\lmodom.cxx
|
|
|
|
Arguments:
|
|
|
|
lpName -- Name to check
|
|
lpServer -- Name of a server within that domain
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
ERROR_DUP_NAME -- The domain name is in use
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWKSTA_INFO_100 pWKI100 = NULL;
|
|
BOOL fIsDC;
|
|
POLICY_LSA_SERVER_ROLE Role;
|
|
|
|
NetStatus = NetpManageIPCConnect( lpServer, lpAccount,
|
|
lpPassword,
|
|
NETSETUPP_CONNECT_IPC | NETSETUPP_NULL_SESSION_IPC );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// Now, get the info from the server
|
|
//
|
|
NetStatus = NetWkstaGetInfo( lpServer, 100, (LPBYTE *)&pWKI100 );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
if (_wcsicmp( lpName, pWKI100->wki100_langroup ) == 0 ) {
|
|
|
|
//
|
|
// Ok, it's a match... Determine the domain role.
|
|
//
|
|
NetStatus = NetpGetLsaDcRole( lpServer, &fIsDC );
|
|
|
|
if ( ( NetStatus == NERR_Success ) && ( fIsDC == FALSE ) )
|
|
{
|
|
NetStatus = NERR_DCNotFound;
|
|
}
|
|
}
|
|
}
|
|
|
|
NetpManageIPCConnect( lpServer, lpAccount,
|
|
lpPassword, NETSETUPP_DISCONNECT_IPC );
|
|
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog((
|
|
"NetpIsValidDomainName for %ws returned 0x%lx\n",
|
|
lpName, NetStatus ));
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpCheckDomainNameIsValid(
|
|
IN LPWSTR lpName,
|
|
IN LPWSTR lpAccount,
|
|
IN LPWSTR lpPassword,
|
|
IN BOOL fShouldExist
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Checks to see if the given name is in use by a domain
|
|
|
|
Arguments:
|
|
|
|
lpName -- Name to check
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- The domain is found and valid
|
|
ERROR_NO_SUCH_DOMAIN -- Domain name not found
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
PBYTE pbDC;
|
|
DWORD cDCs, i, j;
|
|
PUNICODE_STRING pDCList;
|
|
LPWSTR pwszDomain;
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
PDOMAIN_CONTROLLER_INFO pDCInfo = NULL;
|
|
#else
|
|
PBYTE pDCInfo = NULL;
|
|
#endif
|
|
|
|
UNREFERENCED_PARAMETER( lpAccount );
|
|
UNREFERENCED_PARAMETER( lpPassword );
|
|
|
|
//
|
|
// Start with NetGetAnyDCName
|
|
//
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
NetStatus = DsGetDcName( NULL, lpName, NULL, NULL,
|
|
DS_FORCE_REDISCOVERY, &pDCInfo );
|
|
|
|
#else
|
|
|
|
NetStatus = NetGetAnyDCName( NULL,
|
|
( LPCWSTR )lpName,
|
|
&pDCInfo );
|
|
|
|
#endif
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
if ( NetStatus == ERROR_NO_SUCH_USER ) {
|
|
|
|
NetStatus = NERR_Success;
|
|
}
|
|
|
|
} else {
|
|
|
|
NetApiBufferFree( pDCInfo );
|
|
}
|
|
|
|
|
|
//
|
|
// Map our error codes so we only return success if we validated the
|
|
// domain name
|
|
//
|
|
if ( fShouldExist ) {
|
|
|
|
if ( NetStatus == NERR_Success || NetStatus == ERROR_NO_LOGON_SERVERS ) {
|
|
|
|
NetStatus = NERR_Success;
|
|
|
|
} else {
|
|
|
|
NetStatus = ERROR_NO_SUCH_DOMAIN;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if ( NetStatus == NERR_Success || NetStatus == ERROR_NO_LOGON_SERVERS ) {
|
|
|
|
NetStatus = ERROR_DUP_NAME;
|
|
|
|
} else if ( NetStatus == NERR_DCNotFound || NetStatus == ERROR_NO_SUCH_DOMAIN ) {
|
|
|
|
NetStatus = NERR_Success;
|
|
}
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "NetpCheckDomainNameIsValid for %ws returned 0x%lx\n",
|
|
lpName, NetStatus ));
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpManageIPCConnect(
|
|
IN LPWSTR lpServer,
|
|
IN LPWSTR lpAccount,
|
|
IN LPWSTR lpPassword,
|
|
IN ULONG fOptions
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Manages the connections to the servers IPC share
|
|
|
|
Arguments:
|
|
|
|
lpServer -- Server to connect to
|
|
lpAccount -- Account to use
|
|
lpPassword -- Password to use. The password has been NOT been encoded
|
|
fOptions -- Flags to determine operation/connect/disconnect
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- The domain is found and valid
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
#if(_WIN32_WINNT >= 0x0500)
|
|
WCHAR wszPath[2 + DNS_MAX_NAME_LENGTH + 1 + NNLEN + 1];
|
|
#else
|
|
WCHAR wszPath[2 + 256 + 1 + NNLEN + 1];
|
|
#endif
|
|
PWSTR pwszPath = wszPath;
|
|
USE_INFO_2 NetUI2;
|
|
PWSTR pwszUser, pwszDomain, pwszReset;
|
|
DWORD BadParm = 0;
|
|
DWORD ForceLevel = USE_NOFORCE;
|
|
|
|
//
|
|
// Guard against buffer overrun: the server name
|
|
// length has to be no more than max DNS name
|
|
// length plus 2 ( for "\\").
|
|
//
|
|
|
|
if ( wcslen(lpServer) > DNS_MAX_NAME_LENGTH + 2 ) {
|
|
NetpLog(( "NetpManageIPCConnect: server name %ws too long - error out\n", lpServer ));
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Build the path...
|
|
//
|
|
if (*lpServer != L'\\') {
|
|
|
|
wcscpy(wszPath, L"\\\\");
|
|
pwszPath += 2;
|
|
|
|
}
|
|
|
|
if ( FLAG_ON( fOptions, NETSETUPP_USE_LOTS_FORCE ) )
|
|
{
|
|
ASSERT( FLAG_ON(fOptions, NETSETUPP_DISCONNECT_IPC ) );
|
|
ForceLevel = USE_LOTS_OF_FORCE;
|
|
}
|
|
|
|
swprintf( pwszPath, L"%ws\\IPC$", lpServer );
|
|
pwszPath = wszPath;
|
|
|
|
if ( FLAG_ON( fOptions, NETSETUPP_DISCONNECT_IPC ) )
|
|
{
|
|
NetStatus = NetUseDel( NULL, pwszPath, ForceLevel );
|
|
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
NetpKdPrint(( PREFIX_NETJOIN "NetUseDel on %ws failed with %d\n", pwszPath, NetStatus ));
|
|
NetpLog(( "NetUseDel on %ws failed with %d\n", pwszPath, NetStatus ));
|
|
|
|
if ( (NetStatus != NERR_UseNotFound)
|
|
&& (ForceLevel != USE_LOTS_OF_FORCE) )
|
|
{
|
|
NetStatus = NetUseDel( NULL, pwszPath, USE_LOTS_OF_FORCE );
|
|
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
ASSERT( NetStatus == NERR_Success );
|
|
NetpKdPrint(( PREFIX_NETJOIN "NetUseDel with force on %ws failed with %d\n",
|
|
pwszPath, NetStatus ));
|
|
NetpLog(( "NetUseDel with force on %ws failed with %d\n",
|
|
pwszPath, NetStatus ));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
if ( lpAccount != NULL )
|
|
{
|
|
pwszReset = wcschr( lpAccount, L'\\' );
|
|
|
|
if (pwszReset != NULL)
|
|
{
|
|
pwszUser = pwszReset + 1;
|
|
pwszDomain = lpAccount;
|
|
*pwszReset = UNICODE_NULL;
|
|
}
|
|
else
|
|
{
|
|
pwszUser = lpAccount;
|
|
//
|
|
// First, assume it's a UPN, so we pass in an empty string
|
|
//
|
|
pwszDomain = L"";
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
pwszUser = NULL;
|
|
pwszDomain = NULL;
|
|
pwszReset = NULL;
|
|
}
|
|
|
|
RtlZeroMemory(&NetUI2, sizeof(USE_INFO_2) );
|
|
NetUI2.ui2_local = NULL;
|
|
NetUI2.ui2_remote = pwszPath;
|
|
NetUI2.ui2_asg_type = USE_IPC;
|
|
NetUI2.ui2_username = pwszUser;
|
|
NetUI2.ui2_domainname = pwszDomain;
|
|
NetUI2.ui2_password = lpPassword;
|
|
|
|
NetStatus = NetUseAdd( NULL, 2, (PBYTE)&NetUI2, &BadParm );
|
|
|
|
if ( NetStatus == ERROR_LOGON_FAILURE )
|
|
{
|
|
//
|
|
// If we passed in an empty domain name, try it again with a NULL one
|
|
//
|
|
if ( pwszReset == NULL && pwszUser != NULL )
|
|
{
|
|
NetUI2.ui2_domainname = NULL;
|
|
NetStatus = NetUseAdd( NULL, 2, (PBYTE)&NetUI2, &BadParm );
|
|
}
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
NetpKdPrint((PREFIX_NETJOIN "NetUseAdd to %ws returned %lu\n", pwszPath, NetStatus ));
|
|
NetpLog(( "NetUseAdd to %ws returned %lu\n", pwszPath, NetStatus ));
|
|
|
|
if ( NetStatus == ERROR_INVALID_PARAMETER && BadParm != 0 )
|
|
{
|
|
NetpLog(( "NetUseAdd bad parameter is %lu\n", BadParm ));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if ( pwszReset != NULL )
|
|
{
|
|
*pwszReset = L'\\';
|
|
}
|
|
|
|
if ( ( NetStatus == ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT ||
|
|
NetStatus == ERROR_NOLOGON_SERVER_TRUST_ACCOUNT ||
|
|
NetStatus == ERROR_SESSION_CREDENTIAL_CONFLICT ||
|
|
NetStatus == ERROR_ACCESS_DENIED ||
|
|
NetStatus == ERROR_LOGON_FAILURE ) &&
|
|
FLAG_ON( fOptions, NETSETUPP_NULL_SESSION_IPC ) )
|
|
{
|
|
NetpLog(( "Trying add to %ws using NULL Session\n", pwszPath ));
|
|
|
|
//
|
|
// Try it again with the null session
|
|
//
|
|
NetUI2.ui2_username = L"";
|
|
NetUI2.ui2_domainname = L"";
|
|
NetUI2.ui2_password = L"";
|
|
NetStatus = NetUseAdd( NULL, 2, (PBYTE)&NetUI2, NULL );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "NullSession NetUseAdd to %ws returned %lu\n",
|
|
pwszPath, NetStatus ));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NetpBrowserCheckDomain(
|
|
IN LPWSTR NewDomainName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Tell the browser to check a domain/workgroup name
|
|
|
|
Note: this routine is currently not in use.
|
|
|
|
Arguments:
|
|
|
|
NewDomainName - new name of the domain.
|
|
|
|
Return Value:
|
|
|
|
Status of the operation.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
|
|
UNICODE_STRING DeviceName;
|
|
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
HANDLE BrowserHandle = NULL;
|
|
LPBYTE Where;
|
|
DWORD BytesReturned;
|
|
|
|
UCHAR PacketBuffer[sizeof(LMDR_REQUEST_PACKET)+2*(DNLEN+1)*sizeof(WCHAR)];
|
|
PLMDR_REQUEST_PACKET RequestPacket = (PLMDR_REQUEST_PACKET)PacketBuffer;
|
|
|
|
|
|
//
|
|
// Open the browser driver.
|
|
//
|
|
|
|
|
|
//
|
|
// Open the browser device.
|
|
//
|
|
RtlInitUnicodeString(&DeviceName, DD_BROWSER_DEVICE_NAME_U);
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&DeviceName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = NtOpenFile(
|
|
&BrowserHandle,
|
|
SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = IoStatusBlock.Status;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build the request packet.
|
|
//
|
|
RequestPacket->Version = LMDR_REQUEST_PACKET_VERSION_DOM;
|
|
RtlInitUnicodeString( &RequestPacket->TransportName, NULL );
|
|
RequestPacket->Parameters.DomainRename.ValidateOnly = TRUE;
|
|
RtlInitUnicodeString( &RequestPacket->EmulatedDomainName, NULL );
|
|
|
|
|
|
//
|
|
// Copy the new domain name into the packet.
|
|
//
|
|
|
|
Where = (LPBYTE) RequestPacket->Parameters.DomainRename.DomainName;
|
|
RequestPacket->Parameters.DomainRename.DomainNameLength = wcslen( NewDomainName ) * sizeof(WCHAR);
|
|
wcscpy( (LPWSTR)Where, NewDomainName );
|
|
Where += RequestPacket->Parameters.DomainRename.DomainNameLength + sizeof(WCHAR);
|
|
|
|
|
|
|
|
//
|
|
// Send the request to the Datagram Receiver device driver.
|
|
//
|
|
|
|
if ( !DeviceIoControl(
|
|
BrowserHandle,
|
|
IOCTL_LMDR_RENAME_DOMAIN,
|
|
RequestPacket,
|
|
(DWORD)(Where - (LPBYTE)RequestPacket),
|
|
NULL,
|
|
0,
|
|
&BytesReturned,
|
|
NULL )) {
|
|
|
|
NetStatus = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
NetStatus = NO_ERROR;
|
|
Cleanup:
|
|
if ( BrowserHandle != NULL ) {
|
|
NtClose( BrowserHandle );
|
|
}
|
|
return NetStatus;
|
|
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpCreateAuthIdentForCreds(
|
|
IN PWSTR Account,
|
|
IN PWSTR Password,
|
|
OUT SEC_WINNT_AUTH_IDENTITY *AuthIdent
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Internal routine to create an AuthIdent structure for the given creditentials
|
|
|
|
Arguments:
|
|
|
|
Account - Account name
|
|
|
|
Password - Password for the account
|
|
|
|
AuthIdent - AuthIdentity struct to fill in
|
|
|
|
|
|
Returns:
|
|
|
|
ERROR_SUCCESS - Success
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - A memory allocation failed.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR UserCredentialString = NULL;
|
|
PWSTR szUser=NULL;
|
|
PWSTR szDomain=NULL;
|
|
|
|
RtlZeroMemory( AuthIdent, sizeof( SEC_WINNT_AUTH_IDENTITY ) );
|
|
|
|
//
|
|
// If there are no creds, just return
|
|
//
|
|
if ( Account == NULL )
|
|
{
|
|
return NERR_Success;
|
|
}
|
|
|
|
NetStatus = NetpSeparateUserAndDomain(Account, &szUser, &szDomain);
|
|
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
if ( szUser )
|
|
{
|
|
AuthIdent->User = szUser;
|
|
AuthIdent->UserLength = wcslen( szUser );
|
|
}
|
|
|
|
if ( szDomain )
|
|
{
|
|
AuthIdent->Domain = szDomain;
|
|
AuthIdent->DomainLength = wcslen( szDomain );
|
|
}
|
|
|
|
if ( Password )
|
|
{
|
|
AuthIdent->Password = Password;
|
|
AuthIdent->PasswordLength = wcslen( Password );
|
|
}
|
|
|
|
AuthIdent->Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
|
|
}
|
|
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetSeparatedSubstrings(
|
|
IN LPCWSTR szString,
|
|
IN WCHAR chSeparator,
|
|
OUT LPWSTR* pszS1,
|
|
OUT LPWSTR* pszS2
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus = ERROR_FILE_NOT_FOUND;
|
|
LPWSTR szT=NULL;
|
|
LPWSTR szS1=NULL;
|
|
LPWSTR szS2=NULL;
|
|
|
|
*pszS1 = NULL;
|
|
*pszS2 = NULL;
|
|
|
|
if (szString && wcschr( szString, chSeparator ))
|
|
{
|
|
NetStatus = NetpDuplicateString(szString, -1, &szS1);
|
|
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
szT = wcschr( szS1, chSeparator );
|
|
|
|
*szT = UNICODE_NULL;
|
|
szT++;
|
|
|
|
NetStatus = NetpDuplicateString(szT, -1, &szS2);
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
*pszS1 = szS1;
|
|
*pszS2 = szS2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NetStatus != NERR_Success)
|
|
{
|
|
NetApiBufferFree(szS1);
|
|
NetApiBufferFree(szS2);
|
|
}
|
|
|
|
return NetStatus;
|
|
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSeparateUserAndDomain(
|
|
IN LPCWSTR szUserAndDomain,
|
|
OUT LPWSTR* pszUser,
|
|
OUT LPWSTR* pszDomain
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
|
|
*pszUser = NULL;
|
|
*pszDomain = NULL;
|
|
|
|
//
|
|
// check for domain\user format
|
|
//
|
|
NetStatus = NetpGetSeparatedSubstrings(szUserAndDomain, L'\\',
|
|
pszDomain, pszUser);
|
|
|
|
if (NetStatus == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
//
|
|
// check for user@domain format
|
|
//
|
|
//NetStatus = NetpGetSeparatedSubstrings(szUserAndDomain, L'@',
|
|
// pszUser, pszDomain);
|
|
//if (NetStatus == ERROR_FILE_NOT_FOUND)
|
|
//{
|
|
//
|
|
// domain not specified, szUserAndDomain specifies a user
|
|
// (may be in the UPN format)
|
|
//
|
|
NetStatus = NetpDuplicateString(szUserAndDomain, -1, pszUser);
|
|
//}
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
|
|
VOID
|
|
NET_API_FUNCTION
|
|
NetpFreeAuthIdentForCreds(
|
|
IN PSEC_WINNT_AUTH_IDENTITY AuthIdent
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Free the authident structure allocated above
|
|
|
|
Arguments:
|
|
|
|
AuthIdent - AuthIdentity struct to free
|
|
|
|
|
|
Returns:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
if ( AuthIdent )
|
|
{
|
|
NetApiBufferFree( AuthIdent->User );
|
|
NetApiBufferFree( AuthIdent->Domain );
|
|
}
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpLdapUnbind(
|
|
IN PLDAP Ldap
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Unbinds a current ldap connection
|
|
|
|
Arguments:
|
|
|
|
Ldap -- Connection to be severed
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
|
|
if ( Ldap != NULL ) {
|
|
|
|
NetStatus = LdapMapErrorToWin32( ldap_unbind( Ldap ) );
|
|
|
|
}
|
|
|
|
NetpLog(( "ldap_unbind status: 0x%lx\n", NetStatus ));
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpLdapBind(
|
|
IN LPWSTR szUncDcName,
|
|
IN LPWSTR szUser,
|
|
IN LPWSTR szPassword,
|
|
OUT PLDAP *pLdap
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Binds to the named server using the given credentials
|
|
|
|
Arguments:
|
|
|
|
szUncDcName -- DC to connect to
|
|
szUser -- User name to bind with
|
|
szPassword -- Password to use for bind
|
|
pLdap -- Where the connection handle is returned
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
SEC_WINNT_AUTH_IDENTITY AuthId = {0}, *pAuthId = NULL;
|
|
LONG LdapOption;
|
|
ULONG LdapStatus = LDAP_SUCCESS;
|
|
|
|
//
|
|
// Initialization
|
|
//
|
|
|
|
*pLdap = NULL;
|
|
|
|
if ( szUser ) {
|
|
NetStatus = NetpCreateAuthIdentForCreds( szUser, szPassword, &AuthId );
|
|
pAuthId = &AuthId;
|
|
}
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// Open an LDAP connection to the DC and set useful options
|
|
//
|
|
|
|
*pLdap = ldap_initW( szUncDcName + 2, LDAP_PORT );
|
|
|
|
if ( *pLdap ) {
|
|
|
|
//
|
|
// Tell LDAP we are passing an explicit DC name
|
|
// to avoid the DC discovery
|
|
//
|
|
LdapOption = PtrToLong( LDAP_OPT_ON );
|
|
LdapStatus = ldap_set_optionW( *pLdap,
|
|
LDAP_OPT_AREC_EXCLUSIVE,
|
|
&LdapOption );
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NetpLog(( "NetpLdapBind: ldap_set_option LDAP_OPT_AREC_EXCLUSIVE failed on %ws: %ld: %s\n",
|
|
szUncDcName,
|
|
LdapStatus,
|
|
ldap_err2stringA(LdapStatus) ));
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
|
|
//
|
|
// Do the bind
|
|
//
|
|
} else {
|
|
LdapStatus = ldap_bind_sW( *pLdap,
|
|
NULL,
|
|
(PWSTR) pAuthId,
|
|
LDAP_AUTH_NEGOTIATE );
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NetpLog(( "NetpLdapBind: ldap_bind failed on %ws: %ld: %s\n",
|
|
szUncDcName,
|
|
LdapStatus,
|
|
ldap_err2stringA(LdapStatus) ));
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
}
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
NetpLdapUnbind( *pLdap );
|
|
*pLdap = NULL;
|
|
}
|
|
|
|
} else {
|
|
LdapStatus = LdapGetLastError();
|
|
NetpLog(( "NetpLdapBind: ldap_init to %ws failed: %lu\n",
|
|
szUncDcName,
|
|
LdapStatus ));
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
}
|
|
|
|
NetpFreeAuthIdentForCreds( pAuthId );
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetNCRoot(
|
|
IN PLDAP Ldap,
|
|
OUT LPWSTR *NCRoot,
|
|
OUT PBOOLEAN SupportsPageable
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines the DS root for the given domain and determines whether this
|
|
server supports pageable searches
|
|
|
|
Arguments:
|
|
|
|
Ldap -- Connection to the server
|
|
NCRoot -- Where the root is returned. Must be freed via NetApiBufferFree
|
|
SupportsPageable -- if TRUE, this server supports pageable searches
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR Attribs[3] = {
|
|
L"defaultNamingContext",
|
|
L"supportedControl",
|
|
NULL
|
|
};
|
|
PWSTR *Values = NULL;
|
|
LDAPMessage *Message=NULL, *Entry;
|
|
ULONG Items, i;
|
|
|
|
|
|
NetStatus = LdapMapErrorToWin32( ldap_search_s( Ldap, NULL, LDAP_SCOPE_BASE,
|
|
NETSETUPP_ALL_FILTER, Attribs, 0, &Message ) );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
Entry = ldap_first_entry( Ldap, Message );
|
|
|
|
if ( Entry ) {
|
|
|
|
//
|
|
// Now, we'll have to get the values
|
|
//
|
|
Values = ldap_get_values( Ldap, Entry, Attribs[ 0 ] );
|
|
|
|
if ( Values ) {
|
|
|
|
NetStatus = NetpDuplicateString(Values[ 0 ], -1, NCRoot);
|
|
ldap_value_free( Values );
|
|
|
|
} else {
|
|
|
|
NetStatus = LdapMapErrorToWin32( Ldap->ld_errno );
|
|
|
|
}
|
|
|
|
//
|
|
// Now, see if we have the right control bits to do pageable stuff
|
|
//
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
Values = ldap_get_values( Ldap, Entry, Attribs[ 1 ] );
|
|
|
|
if ( Values ) {
|
|
|
|
Items = ldap_count_values( Values );
|
|
|
|
for ( i = 0; i < Items ; i++ ) {
|
|
|
|
if ( _wcsicmp( Values[ i ], LDAP_PAGED_RESULT_OID_STRING_W ) == 0 ) {
|
|
|
|
*SupportsPageable = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ldap_value_free( Values );
|
|
|
|
} else {
|
|
|
|
NetStatus = LdapMapErrorToWin32( Ldap->ld_errno );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
NetStatus = LdapMapErrorToWin32( Ldap->ld_errno );
|
|
}
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Failed to find the root NC: %lu\n", NetStatus ));
|
|
}
|
|
|
|
//Cleanup:
|
|
if (Message)
|
|
{
|
|
ldap_msgfree( Message );
|
|
}
|
|
|
|
return( NetStatus );
|
|
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetDefaultJoinableOu(
|
|
IN LPWSTR Root,
|
|
IN PLDAP Ldap,
|
|
OUT PWSTR *DefaultOu
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches for all the OUs under the given domain root under which the bound
|
|
user has the rights to create a computer object
|
|
|
|
This routine does pageable searches
|
|
|
|
Arguments:
|
|
|
|
Root -- Root NC path
|
|
Ldap -- Connection to the server
|
|
DefaultOu - Where the default joinable ou is returned. NULL if no default joinable ou was
|
|
found
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR Attribs[] = {
|
|
NETSETUPP_WELL_KNOWN,
|
|
NULL
|
|
};
|
|
ULONG Count, Status, i, StringLength;
|
|
PWSTR *WKOs = NULL, *Classes = NULL;
|
|
LDAPMessage *Message = NULL, *Entry, *Message2 = NULL, *Entry2;
|
|
PWSTR ParseString, End, DN = NULL;
|
|
BOOLEAN MatchFound = FALSE;
|
|
|
|
*DefaultOu = NULL;
|
|
|
|
NetpLog(( "Default OU search\n" ));
|
|
|
|
//
|
|
// Ok, first, read the list of WellKnownObjects off of the root
|
|
//
|
|
Status = ldap_search_s( Ldap,
|
|
Root,
|
|
LDAP_SCOPE_BASE,
|
|
NETSETUPP_ALL_FILTER,
|
|
Attribs,
|
|
0,
|
|
&Message );
|
|
|
|
if ( Message ) {
|
|
|
|
Entry = ldap_first_entry( Ldap, Message );
|
|
|
|
while ( Status == LDAP_SUCCESS && Entry ) {
|
|
|
|
//
|
|
// Read the list of objects that the current user is allowed to
|
|
// create under this OU and make sure that we can create a computer
|
|
// object
|
|
//
|
|
WKOs = ldap_get_values( Ldap, Entry, Attribs[ 0 ] );
|
|
|
|
if ( WKOs ) {
|
|
|
|
i = 0;
|
|
while ( WKOs[ i ] ) {
|
|
|
|
if ( !toupper( WKOs[ i ][ 0 ] ) == L'B' ) {
|
|
|
|
NetpLog(( "Unexpected object string %ws\n",
|
|
WKOs[ i ] ));
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
ParseString = WKOs[ i ] + 2;
|
|
|
|
StringLength = wcstoul( ParseString, &End, 10 );
|
|
|
|
ParseString = End + 1; // Skip over the ':'
|
|
|
|
if ( _wcsnicmp( ParseString,
|
|
L"AA312825768811D1ADED00C04FD8D5CD",
|
|
StringLength ) == 0 ) {
|
|
|
|
MatchFound = TRUE;
|
|
ParseString += StringLength + 1;
|
|
|
|
//
|
|
// Now, see if it is accessible or not
|
|
//
|
|
Attribs[ 0 ] = NETSETUPP_RETURNED_ATTR;
|
|
|
|
Status = ldap_search_s( Ldap,
|
|
Root,
|
|
LDAP_SCOPE_BASE,
|
|
NETSETUPP_ALL_FILTER,
|
|
Attribs,
|
|
0,
|
|
&Message2 );
|
|
|
|
|
|
if ( Message2 ) {
|
|
|
|
Entry2 = ldap_first_entry( Ldap, Message2 );
|
|
|
|
while ( Status == LDAP_SUCCESS && Entry2 ) {
|
|
|
|
//
|
|
// Read the list of objects that the current user is allowed to
|
|
// create under this OU and make sure that we can create a computer
|
|
// object
|
|
//
|
|
Classes = ldap_get_values( Ldap, Entry2, Attribs[ 0 ] );
|
|
|
|
if ( Classes ) {
|
|
|
|
i = 0;
|
|
while ( Classes[ i ] ) {
|
|
|
|
if ( _wcsicmp( Classes[ i ],
|
|
NETSETUPP_COMPUTER_OBJECT ) == 0 ) {
|
|
|
|
DN = ldap_get_dn( Ldap, Entry2 );
|
|
|
|
if ( DN != NULL ) {
|
|
NetStatus = NetpDuplicateString(DN, -1,
|
|
DefaultOu);
|
|
ldap_memfree( DN );
|
|
DN = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
ldap_value_free( Classes );
|
|
}
|
|
|
|
//
|
|
// If we found the entry or failed to allocate memory,
|
|
// we are done
|
|
//
|
|
if ( *DefaultOu != NULL || NetStatus != NERR_Success ) {
|
|
break;
|
|
}
|
|
|
|
Entry2 = ldap_next_entry( Ldap, Entry2 );
|
|
|
|
}
|
|
|
|
Status = Ldap->ld_errno;
|
|
ldap_msgfree( Message2 );
|
|
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success || MatchFound ) {
|
|
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
ldap_value_free( WKOs );
|
|
}
|
|
|
|
Entry = ldap_next_entry( Ldap, Entry );
|
|
|
|
}
|
|
|
|
Status = Ldap->ld_errno;
|
|
ldap_msgfree( Message );
|
|
|
|
}
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
NetStatus = LdapMapErrorToWin32( Status );
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetListOfJoinableOUsPaged(
|
|
IN LPWSTR Root,
|
|
IN PLDAP Ldap,
|
|
OUT PULONG OUCount,
|
|
OUT PWSTR **OUs
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches for all the OUs under the given domain root under which the bound
|
|
user has the rights to create a computer object
|
|
|
|
This routine does pageable searches
|
|
|
|
Arguments:
|
|
|
|
Root -- Root NC path
|
|
Ldap -- Connection to the server
|
|
OUCount -- Where the count of strings is returned
|
|
OUs -- Where the list of OUs is returned
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PLDAPSearch SearchHandle = NULL;
|
|
PWSTR Attribs[] = {
|
|
NETSETUPP_RETURNED_ATTR,
|
|
NULL
|
|
};
|
|
ULONG Count, i;
|
|
ULONG Status = LDAP_SUCCESS;
|
|
PWSTR *Classes = NULL;
|
|
LDAPMessage *Message = NULL, *Entry;
|
|
PWSTR DN;
|
|
PWSTR *DnList = NULL, *NewList = NULL;
|
|
ULONG CurrentIndex = 0, ListCount = 0;
|
|
PWSTR DefaultOu = NULL;
|
|
|
|
NetpLog(( "PAGED OU search\n" ));
|
|
|
|
//
|
|
// Initialize the pageable search
|
|
//
|
|
SearchHandle = ldap_search_init_pageW( Ldap,
|
|
Root,
|
|
LDAP_SCOPE_SUBTREE,
|
|
NETSETUPP_OU_FILTER,
|
|
Attribs,
|
|
FALSE,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
2000,
|
|
NULL );
|
|
|
|
if ( SearchHandle == NULL ) {
|
|
|
|
NetStatus = LdapMapErrorToWin32( LdapGetLastError( ) );
|
|
|
|
} else {
|
|
|
|
while ( NetStatus == NERR_Success ) {
|
|
|
|
Count = 0;
|
|
//
|
|
// Get the next page
|
|
//
|
|
Status = ldap_get_next_page_s( Ldap,
|
|
SearchHandle,
|
|
NULL,
|
|
100,
|
|
&Count,
|
|
&Message );
|
|
|
|
if ( Message ) {
|
|
|
|
//
|
|
// Process all of the entries
|
|
//
|
|
Entry = ldap_first_entry( Ldap, Message );
|
|
|
|
while ( Status == LDAP_SUCCESS && Entry ) {
|
|
|
|
//
|
|
// Read the list of classes that the current user is allowed to
|
|
// create under this OU and make sure that we can create a computer
|
|
// object
|
|
//
|
|
Classes = ldap_get_values( Ldap, Entry, Attribs[ 0 ] );
|
|
|
|
if ( Classes ) {
|
|
|
|
i = 0;
|
|
while ( Classes[ i ] ) {
|
|
|
|
if ( _wcsicmp( Classes[ i ], NETSETUPP_COMPUTER_OBJECT ) == 0 ) {
|
|
|
|
DN = ldap_get_dn( Ldap, Entry );
|
|
|
|
NetpKdPrint(( PREFIX_NETJOIN "DN = %ws\n", DN ));
|
|
|
|
//
|
|
// We'll allocate the return list in blocks of 10 to cut
|
|
// down on the number of allocations
|
|
//
|
|
|
|
if ( DN != NULL ) {
|
|
if ( CurrentIndex >= ListCount ) {
|
|
|
|
if ( NetApiBufferAllocate( ( ListCount + 10 ) * sizeof( PWSTR ),
|
|
( PVOID * )&NewList ) != NERR_Success ) {
|
|
|
|
Status = LDAP_NO_MEMORY;
|
|
|
|
} else {
|
|
|
|
RtlZeroMemory( NewList, ( ListCount + 10 ) * sizeof( PWSTR ) );
|
|
RtlCopyMemory( NewList,
|
|
DnList,
|
|
ListCount * sizeof( PWSTR ) );
|
|
ListCount += 10;
|
|
|
|
NetApiBufferFree( DnList );
|
|
DnList = NewList;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Copy the string
|
|
//
|
|
if ( Status == LDAP_SUCCESS ) {
|
|
|
|
if (NERR_Success ==
|
|
NetpDuplicateString(DN, -1,
|
|
&NewList[CurrentIndex]))
|
|
{
|
|
CurrentIndex++;
|
|
}
|
|
else
|
|
{
|
|
Status = LDAP_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
ldap_memfree( DN );
|
|
DN = NULL;
|
|
}
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
ldap_value_free( Classes );
|
|
}
|
|
|
|
Entry = ldap_next_entry( Ldap, Entry );
|
|
|
|
}
|
|
|
|
Status = Ldap->ld_errno;
|
|
ldap_msgfree( Message );
|
|
Message = NULL;
|
|
|
|
}
|
|
|
|
if ( Status == LDAP_NO_RESULTS_RETURNED ) {
|
|
|
|
Status = LDAP_SUCCESS;
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
ldap_search_abandon_page( Ldap,
|
|
SearchHandle );
|
|
|
|
NetStatus = LdapMapErrorToWin32( Status );
|
|
}
|
|
|
|
|
|
//
|
|
// Check the computers container
|
|
//
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
NetStatus = NetpGetDefaultJoinableOu( Root,
|
|
Ldap,
|
|
&DefaultOu );
|
|
|
|
if ( NetStatus == NERR_Success && DefaultOu ) {
|
|
|
|
//
|
|
// We'll allocate the return list in blocks of 10 to cut
|
|
// down on the number of allocations
|
|
//
|
|
if ( CurrentIndex >= ListCount ) {
|
|
|
|
if ( NetApiBufferAllocate( ( ListCount + 10 ) * sizeof( PWSTR ),
|
|
( PVOID * )&NewList ) != NERR_Success ) {
|
|
|
|
Status = LDAP_NO_MEMORY;
|
|
|
|
} else {
|
|
|
|
RtlZeroMemory( NewList, ( ListCount + 10 ) * sizeof( PWSTR ) );
|
|
RtlCopyMemory( NewList,
|
|
DnList,
|
|
ListCount * sizeof( PWSTR ) );
|
|
ListCount += 10;
|
|
NetApiBufferFree( DnList );
|
|
DnList = NewList;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Copy the string
|
|
//
|
|
if ( Status == LDAP_SUCCESS ) {
|
|
|
|
if (NERR_Success ==
|
|
NetpDuplicateString(DefaultOu, -1, &NewList[CurrentIndex]))
|
|
{
|
|
CurrentIndex++;
|
|
}
|
|
else
|
|
{
|
|
Status = LDAP_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
NetApiBufferFree( DefaultOu );
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// If there was an error, free everyting
|
|
//
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
for ( i = 0; i < ListCount; i++ ) {
|
|
|
|
NetApiBufferFree( DnList[ i ] );
|
|
}
|
|
|
|
NetApiBufferFree( DnList );
|
|
|
|
} else {
|
|
|
|
*OUs = DnList;
|
|
*OUCount = CurrentIndex;
|
|
}
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
NetpLog(( "Found %lu OUs\n", *OUs ));
|
|
|
|
} else {
|
|
|
|
NetpLog(( "Failed to obtain the list of joinable OUs: %lu\n",
|
|
NetStatus ));
|
|
|
|
}
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetListOfJoinableOUsNonPaged(
|
|
IN LPWSTR Root,
|
|
IN PLDAP Ldap,
|
|
OUT PULONG OUCount,
|
|
OUT PWSTR **OUs
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches for all the OUs under the given domain root under which the bound
|
|
user has the rights to create a computer object
|
|
|
|
This routine does not use pageable searchs and will return only the max_search count
|
|
of entries
|
|
|
|
Arguments:
|
|
|
|
Root -- Root NC path
|
|
Ldap -- Connection to the server
|
|
OUCount -- Where the count of strings is returned
|
|
OUs -- Where the list of OUs is returned
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR Attribs[] = {
|
|
NETSETUPP_RETURNED_ATTR,
|
|
NULL
|
|
};
|
|
ULONG Count, Status, i;
|
|
PWSTR *Classes = NULL;
|
|
LDAPMessage *Message = NULL, *Entry;
|
|
PWSTR DN;
|
|
PWSTR *DnList = NULL, *NewList = NULL;
|
|
ULONG CurrentIndex = 0, ListCount = 0;
|
|
PWSTR DefaultOu = NULL;
|
|
|
|
NetpLog(( "Normal OU search\n" ));
|
|
|
|
Status = ldap_search_s( Ldap,
|
|
Root,
|
|
LDAP_SCOPE_SUBTREE,
|
|
NETSETUPP_OU_FILTER,
|
|
Attribs,
|
|
0,
|
|
&Message );
|
|
|
|
if ( Message ) {
|
|
|
|
Entry = ldap_first_entry( Ldap, Message );
|
|
|
|
while ( Status == LDAP_SUCCESS && Entry ) {
|
|
|
|
//
|
|
// Read the list of classes that the current user is allowed to
|
|
// create under this OU and make sure that we can create a computer
|
|
// object
|
|
//
|
|
Classes = ldap_get_values( Ldap, Entry, Attribs[ 0 ] );
|
|
|
|
if ( Classes ) {
|
|
|
|
i = 0;
|
|
while ( Classes[ i ] ) {
|
|
|
|
if ( _wcsicmp( Classes[ i ], NETSETUPP_COMPUTER_OBJECT ) == 0 ) {
|
|
|
|
DN = ldap_get_dn( Ldap, Entry );
|
|
|
|
//
|
|
// We'll allocate the return list in blocks of 10 to cut
|
|
// down on the number of allocations
|
|
//
|
|
if ( CurrentIndex >= ListCount ) {
|
|
|
|
if ( NetApiBufferAllocate( ( ListCount + 10 ) * sizeof( PWSTR ),
|
|
( PVOID * )&NewList ) != NERR_Success ) {
|
|
|
|
Status = LDAP_NO_MEMORY;
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( NewList,
|
|
DnList,
|
|
ListCount * sizeof( PWSTR ) );
|
|
ListCount += 10;
|
|
DnList = NewList;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Copy the string
|
|
//
|
|
if ( Status == LDAP_SUCCESS ) {
|
|
|
|
if (NERR_Success ==
|
|
NetpDuplicateString(DN, -1, &NewList[CurrentIndex]))
|
|
{
|
|
CurrentIndex++;
|
|
}
|
|
else
|
|
{
|
|
Status = LDAP_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
ldap_memfree( DN );
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
ldap_value_free( Classes );
|
|
}
|
|
|
|
Entry = ldap_next_entry( Ldap, Entry );
|
|
|
|
}
|
|
|
|
Status = Ldap->ld_errno;
|
|
ldap_msgfree( Message );
|
|
|
|
}
|
|
|
|
NetStatus = LdapMapErrorToWin32( Status );
|
|
|
|
//
|
|
// Check the computers container
|
|
//
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
NetStatus = NetpGetDefaultJoinableOu( Root,
|
|
Ldap,
|
|
&DefaultOu );
|
|
|
|
if ( NetStatus == NERR_Success && DefaultOu ) {
|
|
|
|
//
|
|
// We'll allocate the return list in blocks of 10 to cut
|
|
// down on the number of allocations
|
|
//
|
|
if ( CurrentIndex >= ListCount ) {
|
|
|
|
if ( NetApiBufferAllocate( ( ListCount + 10 ) * sizeof( PWSTR ),
|
|
( PVOID * )&NewList ) != NERR_Success ) {
|
|
|
|
Status = LDAP_NO_MEMORY;
|
|
|
|
} else {
|
|
|
|
RtlCopyMemory( NewList,
|
|
DnList,
|
|
ListCount * sizeof( PWSTR ) );
|
|
ListCount += 10;
|
|
DnList = NewList;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Copy the string
|
|
//
|
|
if ( Status == LDAP_SUCCESS ) {
|
|
|
|
if (NERR_Success ==
|
|
NetpDuplicateString(DefaultOu, -1, &NewList[CurrentIndex]))
|
|
{
|
|
CurrentIndex++;
|
|
}
|
|
else
|
|
{
|
|
Status = LDAP_NO_MEMORY;
|
|
}
|
|
}
|
|
|
|
NetApiBufferFree( DefaultOu );
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there was an error, free everyting
|
|
//
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
for ( i = 0; i < ListCount; i++ ) {
|
|
|
|
NetApiBufferFree( DnList[ i ] );
|
|
}
|
|
|
|
NetApiBufferFree( DnList );
|
|
|
|
} else {
|
|
|
|
*OUs = DnList;
|
|
*OUCount = CurrentIndex;
|
|
}
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
NetpLog(( "Found %lu OUs\n", *OUs ));
|
|
|
|
} else {
|
|
|
|
NetpLog(( "Failed to obtain the list of joinable OUs: %lu\n",
|
|
NetStatus ));
|
|
|
|
}
|
|
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetListOfJoinableOUs(
|
|
IN LPWSTR Domain,
|
|
IN LPWSTR Account,
|
|
IN LPWSTR Password,
|
|
OUT PULONG Count,
|
|
OUT PWSTR **OUs
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine searches for all the OUs under the given domain root under which the bound
|
|
user has the rights to create a computer object
|
|
|
|
Arguments:
|
|
|
|
Domain -- Domain under which to find all of the OUs under which a computer object can be
|
|
created
|
|
Account -- Account to use for the LDAP bind
|
|
Password -- Password to used for the bind. The password is encoded. The first WCHAR of the
|
|
password is the seed.
|
|
OUCount -- Where the count of strings is returned
|
|
OUs -- Where the list of OUs is returned
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
NERR_DefaultJoinRequired -- The servers for this domain do not support the DS so the computer
|
|
account can only be created under the default container (for NT4, this is the SAM account
|
|
database)
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR DomainControllerName = NULL;
|
|
ULONG DcFlags = 0;
|
|
PLDAP Ldap = NULL;
|
|
PWSTR NCRoot;
|
|
BOOLEAN Pageable = FALSE;
|
|
UCHAR Seed;
|
|
UNICODE_STRING EncodedPassword;
|
|
|
|
if ( Password ) {
|
|
|
|
if ( wcslen( Password ) < 1 ) {
|
|
|
|
return( ERROR_INVALID_PARAMETER );
|
|
}
|
|
|
|
Seed = ( UCHAR )*Password;
|
|
RtlInitUnicodeString( &EncodedPassword, Password + 1 );
|
|
|
|
} else {
|
|
|
|
RtlZeroMemory( &EncodedPassword, sizeof( UNICODE_STRING ) );
|
|
Seed = 0;
|
|
}
|
|
|
|
NetSetuppOpenLog();
|
|
|
|
//
|
|
// First, find a DC in the destination domain
|
|
//
|
|
NetStatus = NetpDsGetDcName( NULL,
|
|
Domain,
|
|
NULL,
|
|
NETSETUPP_DSGETDC_FLAGS,
|
|
&DcFlags,
|
|
&DomainControllerName
|
|
,NULL
|
|
);
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// Try and bind to the server
|
|
//
|
|
RtlRunDecodeUnicodeString( Seed, &EncodedPassword );
|
|
NetStatus = NetpLdapBind( DomainControllerName,
|
|
Account,
|
|
EncodedPassword.Buffer,
|
|
&Ldap );
|
|
RtlRunEncodeUnicodeString( &Seed, &EncodedPassword );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
|
|
//
|
|
// Get the X500 domain name
|
|
//
|
|
NetStatus = NetpGetNCRoot( Ldap,
|
|
&NCRoot,
|
|
&Pageable );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// Get the list of OUs
|
|
//
|
|
if ( Pageable ) {
|
|
|
|
NetStatus = NetpGetListOfJoinableOUsPaged( NCRoot,
|
|
Ldap,
|
|
Count,
|
|
OUs );
|
|
} else {
|
|
|
|
NetStatus = NetpGetListOfJoinableOUsNonPaged( NCRoot,
|
|
Ldap,
|
|
Count,
|
|
OUs );
|
|
|
|
}
|
|
|
|
NetApiBufferFree( NCRoot );
|
|
}
|
|
|
|
NetpLdapUnbind( Ldap );
|
|
|
|
} else if ( NetStatus == ERROR_BAD_NET_RESP ) {
|
|
|
|
NetStatus = NERR_DefaultJoinRequired;
|
|
}
|
|
|
|
NetApiBufferFree( DomainControllerName );
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "NetpGetListOfJoinableOUs failed with %lu\n",
|
|
NetStatus ));
|
|
|
|
}
|
|
|
|
NetSetuppCloseLog( );
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NetpGetDnsHostName(
|
|
IN LPWSTR PassedHostName OPTIONAL,
|
|
IN PUNICODE_STRING DnsDomainName,
|
|
IN BOOL UseGpSuffix,
|
|
OUT LPWSTR *DnsHostName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine determines the value of DnsHostName attribute to be set on the
|
|
computer object in the DS. DnsHostName is <HostName.PrimaryDnsSuffix>.
|
|
Here HostName is a computer name which may be different from the Netbios name;
|
|
Netbios name is at most 15 characters of HostName. PrimaryDnsSuffix can be
|
|
set through Policy or through the UI or can be defaulted to the DNS name of the
|
|
domain being joined; policy setting takes preference.
|
|
|
|
This routine determines *new* values for HostName and PrimaryDnsSuffix which
|
|
will be applied after the reboot. Thus DnsHostName will have the correct value
|
|
after the machine reboots.
|
|
|
|
Arguments:
|
|
|
|
PassedHostName - The host name of this machine (can be longer than 15 chars).
|
|
If NULL, the host name is read from the registry.
|
|
|
|
DnsDomainName - DNS name of the domain being joined
|
|
|
|
UseGpSuffix - If TRUE, the primary DNS suffix that comes down through
|
|
policy will be used.
|
|
|
|
DnsHostname - Returns the value of DnsHostName. Must be freed by calling
|
|
NetApiBufferFree.
|
|
|
|
Returns:
|
|
|
|
NO_ERROR - Success
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY - There was not enough memory to read the data from
|
|
registry
|
|
|
|
ERROR_INVALID_COMPUTERNAME - It was not possible to determine DnsHostName from
|
|
the registry
|
|
--*/
|
|
{
|
|
LONG RegStatus;
|
|
HKEY Key = NULL;
|
|
DWORD Type;
|
|
NET_API_STATUS NetStatus;
|
|
PWSTR HostName = NULL;
|
|
PWSTR PrimaryDnsSuffix = NULL;
|
|
LPWSTR LocalDnsHostName;
|
|
DWORD Size = 0;
|
|
|
|
//
|
|
// First detemine HostName.
|
|
//
|
|
// If it's passed, use it
|
|
//
|
|
|
|
if ( PassedHostName != NULL ) {
|
|
HostName = PassedHostName;
|
|
|
|
//
|
|
// Otherwise, read it from teh registry
|
|
//
|
|
} else {
|
|
|
|
RegStatus = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
|
|
L"System\\CurrentControlSet\\Services\\Tcpip\\Parameters",
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&Key );
|
|
|
|
//
|
|
// Not having host name is critical -- error out in such case
|
|
//
|
|
|
|
if ( RegStatus != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpGetDnsHostName: Cannot open TCPIP parameters: 0x%lx\n", RegStatus ));
|
|
|
|
} else {
|
|
|
|
//
|
|
// First try to read the new value
|
|
//
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"NV Hostname",
|
|
0,
|
|
&Type,
|
|
NULL,
|
|
&Size );
|
|
|
|
if ( RegStatus == ERROR_SUCCESS && Size != 0 ) {
|
|
|
|
HostName = LocalAlloc( 0, Size );
|
|
if ( HostName == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"NV Hostname",
|
|
0,
|
|
&Type,
|
|
(PUCHAR) HostName,
|
|
&Size );
|
|
|
|
if ( RegStatus != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpGetDnsHostName: Cannot read NV Hostname: 0x%lx\n", RegStatus ));
|
|
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
|
goto Cleanup;
|
|
} else {
|
|
NetpLog(( "NetpGetDnsHostName: Read NV Hostname: %ws\n", HostName ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the new value does not exist for some reason,
|
|
// try to read the currently active one
|
|
//
|
|
if ( HostName == NULL ) {
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"Hostname",
|
|
0,
|
|
&Type,
|
|
NULL,
|
|
&Size );
|
|
|
|
if ( RegStatus == ERROR_SUCCESS && Size != 0 ) {
|
|
HostName = LocalAlloc( 0, Size );
|
|
if ( HostName == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"Hostname",
|
|
0,
|
|
&Type,
|
|
(PUCHAR) HostName,
|
|
&Size );
|
|
|
|
if ( RegStatus != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpGetDnsHostName: Cannot read Hostname: 0x%lx\n", RegStatus ));
|
|
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
|
goto Cleanup;
|
|
} else {
|
|
NetpLog(( "NetpGetDnsHostName: Read Hostname: %ws\n", HostName ));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we couldn't get HostName, something's really bad
|
|
//
|
|
|
|
if ( HostName == NULL ) {
|
|
NetpLog(( "NetpGetDnsHostName: Could not get Hostname\n" ));
|
|
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( Key != NULL ) {
|
|
RegCloseKey( Key );
|
|
Key = NULL;
|
|
}
|
|
|
|
//
|
|
// Second read primary DNS suffix of this machine.
|
|
//
|
|
// Try the suffix that comes down through policy first
|
|
//
|
|
|
|
if ( UseGpSuffix ) {
|
|
|
|
RegStatus = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
|
|
L"Software\\Policies\\Microsoft\\System\\DNSclient",
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&Key );
|
|
|
|
if ( RegStatus == 0 ) {
|
|
|
|
//
|
|
// Read only the new value; if it doesn't exist the
|
|
// current value will be deleted after the reboot
|
|
//
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"NV PrimaryDnsSuffix",
|
|
0,
|
|
&Type,
|
|
NULL,
|
|
&Size );
|
|
|
|
if ( RegStatus == ERROR_SUCCESS && Size != 0 ) {
|
|
|
|
PrimaryDnsSuffix = LocalAlloc( 0, Size );
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"NV PrimaryDnsSuffix",
|
|
0,
|
|
&Type,
|
|
(PUCHAR) PrimaryDnsSuffix,
|
|
&Size );
|
|
|
|
if ( RegStatus != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpGetDnsHostName: Cannot read NV PrimaryDnsSuffix: 0x%lx\n", RegStatus ));
|
|
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
|
goto Cleanup;
|
|
} else {
|
|
NetpLog(( "NetpGetDnsHostName: Read NV PrimaryDnsSuffix: %ws\n", PrimaryDnsSuffix ));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there is no policy setting for PrimaryDnsSuffix,
|
|
// get it from the TCPIP setting
|
|
//
|
|
|
|
if ( Key != NULL ) {
|
|
RegCloseKey( Key );
|
|
Key = NULL;
|
|
}
|
|
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
|
|
RegStatus = RegOpenKeyExW( HKEY_LOCAL_MACHINE,
|
|
L"System\\CurrentControlSet\\Services\\Tcpip\\Parameters",
|
|
0,
|
|
KEY_QUERY_VALUE,
|
|
&Key );
|
|
|
|
if ( RegStatus == ERROR_SUCCESS ) {
|
|
ULONG SyncValue;
|
|
|
|
Size = sizeof( ULONG );
|
|
RegStatus = RegQueryValueEx( Key,
|
|
L"SyncDomainWithMembership",
|
|
0,
|
|
&Type,
|
|
(PUCHAR)&SyncValue,
|
|
&Size );
|
|
|
|
//
|
|
// If we are not to sync DNS suffix with the name of the
|
|
// domain that we join, get the configured suffix
|
|
//
|
|
if ( RegStatus == ERROR_SUCCESS && SyncValue == 0 ) {
|
|
|
|
//
|
|
// Read the new value
|
|
//
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"NV Domain",
|
|
0,
|
|
&Type,
|
|
NULL,
|
|
&Size );
|
|
|
|
if ( RegStatus == ERROR_SUCCESS && Size != 0 ) {
|
|
|
|
PrimaryDnsSuffix = LocalAlloc( 0, Size );
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"NV Domain",
|
|
0,
|
|
&Type,
|
|
(PUCHAR) PrimaryDnsSuffix,
|
|
&Size );
|
|
|
|
if ( RegStatus != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpGetDnsHostName: Cannot read NV Domain: 0x%lx\n", RegStatus ));
|
|
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
|
goto Cleanup;
|
|
} else {
|
|
NetpLog(( "NetpGetDnsHostName: Read NV Domain: %ws\n", PrimaryDnsSuffix ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If the new value does not exist for some reason,
|
|
// read the currently active one
|
|
//
|
|
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"Domain",
|
|
0,
|
|
&Type,
|
|
NULL,
|
|
&Size );
|
|
|
|
if ( RegStatus == ERROR_SUCCESS && Size != 0 ) {
|
|
|
|
PrimaryDnsSuffix = LocalAlloc( 0, Size );
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RegStatus = RegQueryValueExW( Key,
|
|
L"Domain",
|
|
0,
|
|
&Type,
|
|
(PUCHAR) PrimaryDnsSuffix,
|
|
&Size );
|
|
|
|
if ( RegStatus != ERROR_SUCCESS ) {
|
|
NetpLog(( "NetpGetDnsHostName: Cannot read Domain: 0x%lx\n", RegStatus ));
|
|
NetStatus = ERROR_INVALID_COMPUTERNAME;
|
|
goto Cleanup;
|
|
} else {
|
|
NetpLog(( "NetpGetDnsHostName: Read Domain: %ws\n", PrimaryDnsSuffix ));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we still have no PrimaryDnsSuffix, use DNS name of the domain we join
|
|
//
|
|
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
NetpLog(( "NetpGetDnsHostName: PrimaryDnsSuffix defaulted to DNS domain name: %wZ\n", DnsDomainName ));
|
|
|
|
PrimaryDnsSuffix = LocalAlloc( 0, DnsDomainName->Length + sizeof(WCHAR) );
|
|
if ( PrimaryDnsSuffix == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
RtlCopyMemory( PrimaryDnsSuffix,
|
|
DnsDomainName->Buffer,
|
|
DnsDomainName->Length );
|
|
|
|
PrimaryDnsSuffix[ (DnsDomainName->Length)/sizeof(WCHAR) ] = UNICODE_NULL;
|
|
}
|
|
|
|
//
|
|
// Now we have Hostname and Primary DNS suffix.
|
|
// Connect them with . to form DnsHostName.
|
|
//
|
|
|
|
NetStatus = NetApiBufferAllocate(
|
|
(wcslen(HostName) + 1 + wcslen(PrimaryDnsSuffix) + 1) * sizeof(WCHAR),
|
|
&LocalDnsHostName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
wcscpy( LocalDnsHostName, HostName );
|
|
wcscat( LocalDnsHostName, L"." );
|
|
wcscat( LocalDnsHostName, PrimaryDnsSuffix );
|
|
|
|
//
|
|
// If we are here, it's a success
|
|
//
|
|
|
|
*DnsHostName = LocalDnsHostName;
|
|
NetStatus = NO_ERROR;
|
|
|
|
Cleanup:
|
|
|
|
if ( Key != NULL ) {
|
|
RegCloseKey( Key );
|
|
}
|
|
|
|
if ( HostName != NULL && HostName != PassedHostName ) {
|
|
LocalFree( HostName );
|
|
}
|
|
|
|
if ( PrimaryDnsSuffix != NULL ) {
|
|
LocalFree( PrimaryDnsSuffix );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
VOID
|
|
NetpRemoveDuplicateStrings(
|
|
IN PWCHAR *Source,
|
|
IN OUT PWCHAR *Target
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine accepts two pointer arrays and removes those entries
|
|
from the target array which point to strings that are identical to
|
|
one of the strings pointed to by the entries in the source array.
|
|
On return, the target array entries which precede the NULL terminator
|
|
will point to strings which are different from any of the strings
|
|
pointed to by the source array elements.
|
|
|
|
Arguments:
|
|
|
|
Source -- The NULL terminated array of pointes to source strings.
|
|
For example:
|
|
Source[0] = L"abc";
|
|
Source[1] = L"def";
|
|
Source[2] = NULL;
|
|
|
|
Target -- The NULL terminated array of pointes to target strings.
|
|
For example:
|
|
Target[0] = L"abc";
|
|
Target[1] = L"ghi";
|
|
Target[2] = L"def";
|
|
Target[3] = NULL;
|
|
|
|
On return, the Target array will be, for our example:
|
|
Target[0] = L"ghi";
|
|
Target[1] = NULL;
|
|
Target[2] = L"def";
|
|
Target[3] = NULL;
|
|
|
|
Note that, on return, the target array has size of 1 and
|
|
contains only one valid pointer.
|
|
|
|
Returns:
|
|
|
|
VOID
|
|
|
|
--*/
|
|
{
|
|
PWCHAR *TargetPtr, *TargetNextPtr, *SourcePtr;
|
|
BOOL KeepEntry;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
|
|
if ( Source == NULL || *Source == NULL ||
|
|
Target == NULL || *Target == NULL ) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Loop through the target and compare with the source
|
|
//
|
|
|
|
for ( TargetPtr = TargetNextPtr = Target;
|
|
*TargetNextPtr != NULL;
|
|
TargetNextPtr++ ) {
|
|
|
|
KeepEntry = TRUE;
|
|
for ( SourcePtr = Source; *SourcePtr != NULL; SourcePtr++ ) {
|
|
if ( _wcsicmp( *SourcePtr, *TargetNextPtr ) == 0 ) {
|
|
KeepEntry = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( KeepEntry ) {
|
|
*TargetPtr = *TargetNextPtr;
|
|
TargetPtr ++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Terminate the target array
|
|
//
|
|
|
|
*TargetPtr = NULL;
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
NetpCrackNamesStatus2Win32Error(
|
|
DWORD dwStatus
|
|
)
|
|
{
|
|
switch (dwStatus) {
|
|
case DS_NAME_ERROR_RESOLVING:
|
|
return ERROR_DS_NAME_ERROR_RESOLVING;
|
|
|
|
case DS_NAME_ERROR_NOT_FOUND:
|
|
return ERROR_DS_NAME_ERROR_NOT_FOUND;
|
|
|
|
case DS_NAME_ERROR_NOT_UNIQUE:
|
|
return ERROR_DS_NAME_ERROR_NOT_UNIQUE;
|
|
|
|
case DS_NAME_ERROR_NO_MAPPING:
|
|
return ERROR_DS_NAME_ERROR_NO_MAPPING;
|
|
|
|
case DS_NAME_ERROR_DOMAIN_ONLY:
|
|
return ERROR_DS_NAME_ERROR_DOMAIN_ONLY;
|
|
|
|
case DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING:
|
|
return ERROR_DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING;
|
|
}
|
|
|
|
return ERROR_FILE_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Machine account attributes in the DS
|
|
//
|
|
|
|
#define NETSETUPP_OBJECTCLASS L"objectClass"
|
|
#define NETSETUPP_SAMACCOUNTNAME L"SamAccountName"
|
|
#define NETSETUPP_DNSHOSTNAME L"DnsHostName"
|
|
#define NETSETUPP_SERVICEPRINCIPALNAME L"ServicePrincipalName"
|
|
#define NETSETUPP_USERACCOUNTCONTROL L"userAccountControl"
|
|
#define NETSETUPP_UNICODEPWD L"unicodePwd"
|
|
#define NETSETUPP_ORGANIZATIONALUNIT L"OrganizationalUnit"
|
|
#define NETSETUPP_HOST_SPN_PREFIX L"HOST/"
|
|
#define NETSETUPP_COMP_OBJ_ATTR_COUNT 6
|
|
#define NETSETUPP_MULTIVAL_ATTRIB 0x01
|
|
#define NETSETUPP_COMPUTER_CONTAINER_GUID_IN_B32_FORM L"B:32:" GUID_COMPUTRS_CONTAINER_W L":"
|
|
|
|
typedef struct _NETSETUPP_MACH_ACC_ATTRIBUTE {
|
|
PWSTR AttribType; // Type of the attribute
|
|
ULONG AttribFlags; // Attribute flags
|
|
PWSTR *AttribValues; // Values of the attribute
|
|
} NETSETUPP_MACH_ACC_ATTRIBUTE, *PNETSETUPP_MACH_ACC_ATTRIBUTE;
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetComputerObjectDn(
|
|
IN PDOMAIN_CONTROLLER_INFO DcInfo,
|
|
IN LPWSTR Account,
|
|
IN LPWSTR Password,
|
|
IN PLDAP Ldap,
|
|
IN LPWSTR ComputerName,
|
|
IN LPWSTR OU OPTIONAL,
|
|
OUT LPWSTR *ComputerObjectDn
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get the DN for the computer account in the specified OU.
|
|
The algorithm is as follows.
|
|
|
|
First try to get the DN of the pre-existing account (if any)
|
|
by cracking the account name into a DN. If that succeeds, verify
|
|
that the passed OU (if any) matches the cracked DN. If the OU
|
|
matches, return success, otherwise return error (ERROR_FILE_EXISTS).
|
|
If no OU is not passed, simply return the cracked DN.
|
|
|
|
If the account does not exist, verify that the passed OU
|
|
(if any) exists. If so, build the DN from the computer name and
|
|
the OU and return it. If no OU is passed, get the default computer
|
|
container name (by reading the WellKnownObjects attribute) and build
|
|
the DN using the computer name and the default computer container DN.
|
|
|
|
Arguments:
|
|
|
|
DcInfo - Domain controller on which to create the object
|
|
|
|
Account - Account to use for the LDAP bind
|
|
|
|
Password - Password to used for the bind
|
|
|
|
Ldap - Ldap binding to the DC
|
|
|
|
ComputerName - Name of the computer being joined
|
|
|
|
OU - OU under which to create the object.
|
|
The name must be a fully qualified name
|
|
e.g.: "ou=test,dc=ntdev,dc=microsoft,dc=com"
|
|
NULL indicates to use the default computer container
|
|
|
|
ComputerObjectDn - Returns the DN of the computer object.
|
|
The retuned buffer must be freed using NetApiBufferFree
|
|
|
|
Returns:
|
|
|
|
NO_ERROR -- Success
|
|
|
|
ERROR_DS_NAME_ERROR_NOT_UNIQUE -- One of names being cracked
|
|
(the Netbios domain name or the pre-existing account name
|
|
or the root DN) is not unique.
|
|
|
|
ERROR_FILE_EXISTS -- The OU passed does not match the cracked DN
|
|
of the pre-existing account.
|
|
|
|
ERROR_FILE_NOT_FOUND -- The specified OU does not exist or
|
|
Could not get/read the WellKnownObjects attribute or
|
|
Could not get the default computer container name from the
|
|
WellKnownObjects attribute.
|
|
|
|
ERROR_NOT_ENOUGH_MEMORY -- Could not allocated memory required.
|
|
|
|
One of the errors returned by DsCrackNames.
|
|
(see NetpCrackNamesStatus2Win32Error())
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NO_ERROR;
|
|
ULONG LdapStatus;
|
|
HANDLE hDs = NULL;
|
|
PWCHAR AccountUserName = NULL;
|
|
PWCHAR AccountDomainName = NULL;
|
|
LPWSTR NetbiosDomainNameWithBackslash = NULL;
|
|
PWCHAR ComputerContainerDn = NULL;
|
|
PWCHAR NameToCrack = NULL;
|
|
RPC_AUTH_IDENTITY_HANDLE AuthId = 0;
|
|
PDS_NAME_RESULTW CrackedName = NULL;
|
|
PWCHAR WellKnownObjectsAttr[2];
|
|
PWSTR *WellKnownObjectValues = NULL;
|
|
LDAPMessage *LdapMessage = NULL, *LdapEntry = NULL;
|
|
LPWSTR LocalComputerObjectDn = NULL;
|
|
ULONG Index;
|
|
|
|
//
|
|
// First check whether the account already exists for the computer
|
|
//
|
|
// If account is passed, prepare the corresponding credentials.
|
|
// Otherwise, use the default creds of the user running this routine.
|
|
//
|
|
|
|
if ( Account != NULL ) {
|
|
NetStatus = NetpSeparateUserAndDomain( Account, &AccountUserName, &AccountDomainName );
|
|
if ( NetStatus != NERR_Success ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: Cannot NetpSeparateUserAndDomain 0x%lx\n", NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = DsMakePasswordCredentials( AccountUserName,
|
|
AccountDomainName,
|
|
Password,
|
|
&AuthId);
|
|
if ( NetStatus != NERR_Success ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: Cannot DsMakePasswordCredentials 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(( "NetpGetComputerObjectDn: Unable to bind to DS on '%ws': 0x%lx\n",
|
|
DcInfo->DomainControllerName, NetStatus ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Attempt to crack the account name into a DN.
|
|
//
|
|
// We need to have the Netbios domain name to
|
|
// form an NT4 style account name since DsCrackNames
|
|
// doesn't accept DNS domain names for cracking accounts.
|
|
// So, if we have a DNS domain name, we need to crack it
|
|
// into a Netbios domain name first.
|
|
//
|
|
|
|
if ( (DcInfo->Flags & DS_DNS_DOMAIN_FLAG) == 0 ) {
|
|
|
|
NetbiosDomainNameWithBackslash = LocalAlloc( 0, (wcslen(DcInfo->DomainName) + 2) * sizeof(WCHAR) );
|
|
if ( NetbiosDomainNameWithBackslash == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
swprintf( NetbiosDomainNameWithBackslash, L"%ws\\", DcInfo->DomainName );
|
|
|
|
} else {
|
|
|
|
NameToCrack = LocalAlloc( 0, (wcslen(DcInfo->DomainName) + 1 + 1) * sizeof(WCHAR) );
|
|
|
|
if ( NameToCrack == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
swprintf( NameToCrack, L"%ws/", DcInfo->DomainName );
|
|
|
|
//
|
|
// Be verbose
|
|
//
|
|
NetpLog(( "NetpGetComputerObjectDn: Cracking DNS domain name %ws into Netbios on %ws\n",
|
|
NameToCrack,
|
|
DcInfo->DomainControllerName ));
|
|
|
|
if ( CrackedName != NULL ) {
|
|
DsFreeNameResultW( CrackedName );
|
|
CrackedName = NULL;
|
|
}
|
|
|
|
//
|
|
// Crack the DNS domain name into a Netbios domain name
|
|
//
|
|
NetStatus = DsCrackNamesW( hDs,
|
|
0,
|
|
DS_CANONICAL_NAME,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
1,
|
|
&NameToCrack,
|
|
&CrackedName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: CrackNames failed for %ws: 0x%lx\n",
|
|
NameToCrack,
|
|
NetStatus ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Check for consistency
|
|
//
|
|
if ( CrackedName->cItems != 1 ) {
|
|
NetStatus = ERROR_DS_NAME_ERROR_NOT_UNIQUE;
|
|
NetpLog(( "NetpGetComputerObjectDn: Cracked Name %ws is not unique: %lu\n",
|
|
NameToCrack,
|
|
CrackedName->cItems ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
if ( CrackedName->rItems[0].status != DS_NAME_NO_ERROR ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: CrackNames failed for %ws: substatus 0x%lx\n",
|
|
NameToCrack,
|
|
CrackedName->rItems[0].status ));
|
|
NetStatus = NetpCrackNamesStatus2Win32Error( CrackedName->rItems[0].status );
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Be verbose
|
|
//
|
|
NetpLog(( "NetpGetComputerObjectDn: Crack results: \tname = %ws\n",
|
|
CrackedName->rItems[0].pName ));
|
|
|
|
//
|
|
// We've got the Netbios domain name
|
|
// (the cracked name already includes the trailing backslash)
|
|
//
|
|
|
|
NetbiosDomainNameWithBackslash = LocalAlloc( 0, (wcslen(CrackedName->rItems[0].pName) + 1) * sizeof(WCHAR) );
|
|
if ( NetbiosDomainNameWithBackslash == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
wcscpy( NetbiosDomainNameWithBackslash, CrackedName->rItems[0].pName );
|
|
}
|
|
|
|
//
|
|
// Form the NT4 account name given the Netbios domain name
|
|
//
|
|
|
|
if ( NameToCrack != NULL ) {
|
|
LocalFree( NameToCrack );
|
|
NameToCrack = NULL;
|
|
}
|
|
|
|
NameToCrack = LocalAlloc( 0,
|
|
(wcslen(NetbiosDomainNameWithBackslash) + wcslen(ComputerName) + 1 + 1) * sizeof(WCHAR) );
|
|
if ( NameToCrack == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
|
|
swprintf( NameToCrack, L"%ws%ws$", NetbiosDomainNameWithBackslash, ComputerName );
|
|
|
|
//
|
|
// Crack the account name into a DN
|
|
//
|
|
|
|
if ( CrackedName != NULL ) {
|
|
DsFreeNameResultW( CrackedName );
|
|
CrackedName = NULL;
|
|
}
|
|
|
|
//
|
|
// Be verbose
|
|
//
|
|
|
|
NetpLog(( "NetpGetComputerObjectDn: Cracking account name %ws on %ws\n",
|
|
NameToCrack,
|
|
DcInfo->DomainControllerName ));
|
|
|
|
NetStatus = DsCrackNamesW( hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&NameToCrack,
|
|
&CrackedName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: CrackNames failed for %ws: 0x%lx\n",
|
|
NameToCrack,
|
|
NetStatus ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Check for consistency
|
|
//
|
|
|
|
if ( CrackedName->cItems > 1 ) {
|
|
NetStatus = ERROR_DS_NAME_ERROR_NOT_UNIQUE;
|
|
NetpLog(( "NetpGetComputerObjectDn: Cracked Name %ws is not unique: %lu\n",
|
|
NameToCrack,
|
|
CrackedName->cItems ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// If the account alredy exists, verify that the passed OU (if any)
|
|
// matches that of the account DN
|
|
//
|
|
|
|
if ( CrackedName->rItems[0].status == DS_NAME_NO_ERROR ) {
|
|
ULONG DnSize;
|
|
|
|
NetpLog(( "NetpGetComputerObjectDn: Crack results: \t(Account already exists) DN = %ws\n",
|
|
CrackedName->rItems[0].pName ));
|
|
|
|
DnSize = ( wcslen(CrackedName->rItems[0].pName) + 1 ) * sizeof(WCHAR);
|
|
|
|
//
|
|
// Allocate storage for the computer object DN
|
|
//
|
|
NetStatus = NetApiBufferAllocate( DnSize, &LocalComputerObjectDn );
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If the OU is passed, verify that it matches the cracked name
|
|
//
|
|
if ( OU != NULL ) {
|
|
ULONG DnSizeFromOu;
|
|
|
|
DnSizeFromOu = ( wcslen(NETSETUPP_OBJ_PREFIX) +
|
|
wcslen(ComputerName) + 1 + wcslen(OU) + 1 ) * sizeof(WCHAR);
|
|
|
|
if ( DnSizeFromOu != DnSize ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: Passed OU doesn't match in size cracked DN: %lu %lu\n",
|
|
DnSizeFromOu,
|
|
DnSize ));
|
|
NetStatus = ERROR_FILE_EXISTS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
swprintf( LocalComputerObjectDn, L"%ws%ws,%ws", NETSETUPP_OBJ_PREFIX, ComputerName, OU );
|
|
|
|
if ( _wcsicmp(LocalComputerObjectDn, CrackedName->rItems[0].pName) != 0 ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: Passed OU doesn't match cracked DN: %ws %ws\n",
|
|
LocalComputerObjectDn,
|
|
CrackedName->rItems[0].pName ));
|
|
|
|
NetStatus = ERROR_FILE_EXISTS;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Otherwise, just use the cracked name
|
|
//
|
|
} else {
|
|
wcscpy( LocalComputerObjectDn, CrackedName->rItems[0].pName );
|
|
}
|
|
|
|
//
|
|
// We've got the computer object DN from the existing account
|
|
//
|
|
NetStatus = NO_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Be verbose
|
|
//
|
|
|
|
NetpLog(( "NetpGetComputerObjectDn: Crack results: \tAccount does not exist\n" ));
|
|
|
|
|
|
//
|
|
// At this point, we know that the account does not exist
|
|
// If OU is passed, simply verify it
|
|
//
|
|
|
|
if ( OU != NULL ) {
|
|
LdapStatus = ldap_compare_s( Ldap,
|
|
OU,
|
|
NETSETUPP_OBJECTCLASS,
|
|
NETSETUPP_ORGANIZATIONALUNIT );
|
|
|
|
if ( LdapStatus == LDAP_COMPARE_FALSE ) {
|
|
NetStatus = ERROR_FILE_NOT_FOUND;
|
|
NetpLog(( "NetpGetComputerObjectDn: Specified path '%ws' is not an OU\n", OU ));
|
|
goto Cleanup;
|
|
} else if ( LdapStatus != LDAP_COMPARE_TRUE ) {
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
NetpLog(( "NetpGetComputerObjectDn: ldap_compare_s failed: 0x%lx 0x%lx\n",
|
|
LdapStatus, NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// OU has been verified.
|
|
// Allocate the computer object DN.
|
|
//
|
|
|
|
NetStatus = NetApiBufferAllocate(
|
|
( wcslen(NETSETUPP_OBJ_PREFIX) +
|
|
wcslen(ComputerName) + 1 + wcslen(OU) + 1 ) * sizeof(WCHAR),
|
|
&LocalComputerObjectDn );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We've got the computer object DN from the OU passed
|
|
//
|
|
swprintf( LocalComputerObjectDn, L"%ws%ws,%ws", NETSETUPP_OBJ_PREFIX, ComputerName, OU );
|
|
NetpLog(( "NetpGetComputerObjectDn: Got DN %ws from the passed OU\n", LocalComputerObjectDn ));
|
|
NetStatus = NO_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// At this point, the account does not exist
|
|
// and no OU was specified. So get the default
|
|
// computer container DN.
|
|
//
|
|
|
|
if ( CrackedName != NULL ) {
|
|
DsFreeNameResultW( CrackedName );
|
|
CrackedName = NULL;
|
|
}
|
|
|
|
//
|
|
// Be verbose
|
|
//
|
|
|
|
NetpLog(( "NetpGetComputerObjectDn: Cracking Netbios domain name %ws into root DN on %ws\n",
|
|
NetbiosDomainNameWithBackslash,
|
|
DcInfo->DomainControllerName ));
|
|
|
|
NetStatus = DsCrackNamesW( hDs,
|
|
0,
|
|
DS_NT4_ACCOUNT_NAME,
|
|
DS_FQDN_1779_NAME,
|
|
1,
|
|
&NetbiosDomainNameWithBackslash,
|
|
&CrackedName );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: CrackNames failed for %ws: 0x%lx\n",
|
|
NetbiosDomainNameWithBackslash,
|
|
NetStatus ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Check for consistency
|
|
//
|
|
|
|
if ( CrackedName->cItems != 1 ) {
|
|
NetStatus = ERROR_DS_NAME_ERROR_NOT_UNIQUE;
|
|
NetpLog(( "NetpGetComputerObjectDn: Cracked Name %ws is not unique: %lu\n",
|
|
NetbiosDomainNameWithBackslash,
|
|
CrackedName->cItems ));
|
|
goto Cleanup ;
|
|
}
|
|
|
|
if ( CrackedName->rItems[0].status != DS_NAME_NO_ERROR ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: CrackNames failed for %ws: substatus 0x%lx\n",
|
|
NetbiosDomainNameWithBackslash,
|
|
CrackedName->rItems[0].status ));
|
|
NetStatus = NetpCrackNamesStatus2Win32Error( CrackedName->rItems[0].status );
|
|
goto Cleanup ;
|
|
}
|
|
|
|
//
|
|
// Be verbose
|
|
//
|
|
|
|
NetpLog(( "NetpGetComputerObjectDn: Crack results: \tname = %ws\n",
|
|
CrackedName->rItems[0].pName ));
|
|
|
|
//
|
|
// Now get the computer container DN given the root DN.
|
|
// The DN of the computer container is part of the wellKnownObjects
|
|
// attribute in the root of the domain. So, look it up.
|
|
//
|
|
|
|
WellKnownObjectsAttr[0] = L"wellKnownObjects";
|
|
WellKnownObjectsAttr[1] = NULL;
|
|
|
|
LdapStatus = ldap_search_s( Ldap,
|
|
CrackedName->rItems[0].pName, // Root DN
|
|
LDAP_SCOPE_BASE,
|
|
L"objectclass=*",
|
|
WellKnownObjectsAttr,
|
|
0,
|
|
&LdapMessage);
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
NetpLog(( "NetpGetComputerObjectDn: ldap_search_s failed 0x%lx 0x%lx\n",
|
|
LdapStatus,
|
|
NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( ldap_count_entries(Ldap, LdapMessage) == 0 ) {
|
|
NetStatus = ERROR_FILE_NOT_FOUND;
|
|
NetpLog(( "NetpGetComputerObjectDn: ldap_search_s returned no entries\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
LdapEntry = ldap_first_entry( Ldap, LdapMessage );
|
|
|
|
if ( LdapEntry == NULL ) {
|
|
NetStatus = ERROR_FILE_NOT_FOUND;
|
|
NetpLog(( "NetpGetComputerObjectDn: ldap_first_entry returned NULL\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
WellKnownObjectValues = ldap_get_valuesW( Ldap,
|
|
LdapEntry,
|
|
L"wellKnownObjects" );
|
|
if ( WellKnownObjectValues == NULL ) {
|
|
NetStatus = ERROR_FILE_NOT_FOUND;
|
|
NetpLog(( "NetpGetComputerObjectDn: ldap_get_valuesW returned NULL\n" ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Lookup the default computer container
|
|
//
|
|
|
|
for ( Index = 0; WellKnownObjectValues[Index] != NULL; Index++ ) {
|
|
|
|
//
|
|
// The structure of this particular field is:
|
|
// L"B:32:GUID:DN" where GUID is AA312825768811D1ADED00C04FD8D5CD
|
|
//
|
|
if ( _wcsnicmp( WellKnownObjectValues[Index],
|
|
NETSETUPP_COMPUTER_CONTAINER_GUID_IN_B32_FORM,
|
|
wcslen(NETSETUPP_COMPUTER_CONTAINER_GUID_IN_B32_FORM) ) == 0 ) {
|
|
|
|
ComputerContainerDn = WellKnownObjectValues[Index] +
|
|
wcslen(NETSETUPP_COMPUTER_CONTAINER_GUID_IN_B32_FORM);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we couldn't get the computer container DN, error out
|
|
//
|
|
|
|
if ( ComputerContainerDn == NULL || *ComputerContainerDn == L'\0' ) {
|
|
NetpLog(( "NetpGetComputerObjectDn: Couldn't get computer container DN\n" ));
|
|
NetStatus = ERROR_FILE_NOT_FOUND;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Allocate the computer object DN
|
|
//
|
|
|
|
NetStatus = NetApiBufferAllocate(
|
|
( wcslen(NETSETUPP_OBJ_PREFIX) +
|
|
wcslen(ComputerName) + 1 + wcslen(ComputerContainerDn) + 1 ) * sizeof(WCHAR),
|
|
&LocalComputerObjectDn );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// We've got the computer object DN from the default computer container
|
|
//
|
|
|
|
swprintf( LocalComputerObjectDn, L"%ws%ws,%ws", NETSETUPP_OBJ_PREFIX, ComputerName, ComputerContainerDn );
|
|
NetpLog(( "NetpGetComputerObjectDn: Got DN %ws from the default computer container\n", LocalComputerObjectDn ));
|
|
NetStatus = NO_ERROR;
|
|
|
|
//
|
|
// Free locally used resources
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
if ( hDs ) {
|
|
DsUnBind( &hDs );
|
|
}
|
|
|
|
if ( CrackedName ) {
|
|
DsFreeNameResultW( CrackedName );
|
|
}
|
|
|
|
if ( AuthId ) {
|
|
DsFreePasswordCredentials( AuthId );
|
|
}
|
|
|
|
if ( WellKnownObjectValues != NULL ) {
|
|
ldap_value_free( WellKnownObjectValues );
|
|
}
|
|
|
|
if ( NameToCrack != NULL ) {
|
|
LocalFree( NameToCrack );
|
|
}
|
|
|
|
if ( AccountUserName != NULL ) {
|
|
NetApiBufferFree( AccountUserName );
|
|
}
|
|
|
|
if ( AccountDomainName != NULL ) {
|
|
NetApiBufferFree( AccountDomainName );
|
|
}
|
|
|
|
if ( NetbiosDomainNameWithBackslash != NULL ) {
|
|
LocalFree( NetbiosDomainNameWithBackslash );
|
|
}
|
|
|
|
if ( LdapMessage != NULL ) {
|
|
ldap_msgfree( LdapMessage );
|
|
}
|
|
|
|
if ( NetStatus == NO_ERROR ) {
|
|
*ComputerObjectDn = LocalComputerObjectDn;
|
|
} else if ( LocalComputerObjectDn != NULL ) {
|
|
NetApiBufferFree( LocalComputerObjectDn );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpModifyComputerObjectInDs(
|
|
IN LPWSTR DC,
|
|
IN PLDAP Ldap,
|
|
IN LPWSTR ComputerName,
|
|
IN LPWSTR ComputerObjectDn,
|
|
IN ULONG NumberOfAttributes,
|
|
IN OUT PNETSETUPP_MACH_ACC_ATTRIBUTE Attrib
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a computer account in the specified OU.
|
|
|
|
Arguments:
|
|
|
|
DC -- Domain controller on which to create the object
|
|
Ldap -- Ldap binding to the DC
|
|
ComputerName -- Name of the computer being joined
|
|
ComputerObjectDn -- DN of computer object being modified
|
|
NumberOfAttributes -- Number of attributes passed
|
|
Attrib -- List of attribute structures. The list may
|
|
be modified on return so that only those entries
|
|
that were not already set in the DS will be preserved.
|
|
|
|
NOTE: If the machine password (unicodePwd) is passed as one of the attributes,
|
|
it must be the last entry in the attribute list because this order is assumed
|
|
by the fail-over code below.
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
ULONG LdapStatus;
|
|
|
|
PWSTR *AttribTypesList = NULL;
|
|
LDAPMod *ModList = NULL;
|
|
PLDAPMod *Mods = NULL;
|
|
LDAPMessage *Message = NULL, *Entry;
|
|
ULONG Index;
|
|
ULONG ModIndex = 0;
|
|
BOOL NewAccount = FALSE;
|
|
BOOL AccountBeingEnabled = FALSE;
|
|
|
|
PWSTR SamAccountName = NULL;
|
|
USER_INFO_1 *CurrentUI1 = NULL;
|
|
|
|
//
|
|
// Allocate storage for the attribute list and the modifications block
|
|
//
|
|
|
|
NetStatus = NetApiBufferAllocate( (NumberOfAttributes+1)*sizeof(PWSTR),
|
|
(PVOID *) &AttribTypesList );
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = NetApiBufferAllocate( NumberOfAttributes * sizeof(LDAPMod),
|
|
(PVOID *) &ModList );
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetStatus = NetApiBufferAllocate( (NumberOfAttributes+1)*sizeof(PLDAPMod),
|
|
(PVOID *) &Mods );
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build modification list given the list of attributes
|
|
//
|
|
|
|
NetpLog(( "NetpModifyComputerObjectInDs: Initial attribute values:\n" ));
|
|
for ( Index = 0; Index < NumberOfAttributes; Index++ ) {
|
|
ModList[Index].mod_op = LDAP_MOD_ADD; // Set to add. We may adjust this below.
|
|
ModList[Index].mod_type = Attrib[Index].AttribType;
|
|
ModList[Index].mod_values = Attrib[Index].AttribValues;
|
|
|
|
//
|
|
// See whether we enable the account
|
|
//
|
|
if ( _wcsicmp(ModList[Index].mod_type, NETSETUPP_USERACCOUNTCONTROL) == 0 &&
|
|
_wcsicmp(*(ModList[Index].mod_values), NETSETUPP_ACCNT_TYPE_ENABLED) == 0 ) {
|
|
AccountBeingEnabled = TRUE;
|
|
}
|
|
|
|
//
|
|
// Be verbose - output all values of each attribute
|
|
//
|
|
NetpLog(( "\t\t%ws =", Attrib[Index].AttribType ));
|
|
|
|
//
|
|
// Don't leak sensitive info!
|
|
//
|
|
if ( _wcsicmp( Attrib[Index].AttribType, NETSETUPP_UNICODEPWD ) == 0 ) {
|
|
NetpLog(( " <SomePassword>" ));
|
|
} else {
|
|
PWSTR *CurrentValues;
|
|
|
|
for ( CurrentValues = Attrib[Index].AttribValues; *CurrentValues != NULL; CurrentValues++ ) {
|
|
NetpLog(( " %ws", *CurrentValues ));
|
|
}
|
|
}
|
|
NetpLog(( "\n" ));
|
|
}
|
|
|
|
//
|
|
// Now check which attribute values are already set in the DS
|
|
//
|
|
|
|
for ( Index = 0; Index < NumberOfAttributes; Index++ ) {
|
|
AttribTypesList[Index] = Attrib[Index].AttribType;
|
|
}
|
|
AttribTypesList[Index] = NULL; // Terminate the list
|
|
|
|
LdapStatus = ldap_search_s( Ldap,
|
|
ComputerObjectDn,
|
|
LDAP_SCOPE_BASE,
|
|
NULL,
|
|
AttribTypesList,
|
|
0,
|
|
&Message );
|
|
|
|
//
|
|
// If the computer object does not exist,
|
|
// we need to add all attributes
|
|
//
|
|
|
|
if ( LdapStatus == LDAP_NO_SUCH_OBJECT ) {
|
|
NetpLog(( "NetpModifyComputerObjectInDs: Computer Object does not exist in OU\n" ));
|
|
NewAccount = TRUE;
|
|
|
|
for ( ModIndex = 0; ModIndex < NumberOfAttributes; ModIndex++ ) {
|
|
Mods[ModIndex] = &ModList[ModIndex];
|
|
}
|
|
|
|
//
|
|
// Terminate the modification list
|
|
//
|
|
Mods[ModIndex] = NULL;
|
|
|
|
//
|
|
// Otherwise see which attribute values need modification
|
|
//
|
|
|
|
} else if ( LdapStatus == LDAP_SUCCESS ) {
|
|
NetpLog(( "NetpModifyComputerObjectInDs: Computer Object already exists in OU:\n" ));
|
|
|
|
//
|
|
// Get the first entry (there should be only one)
|
|
//
|
|
Entry = ldap_first_entry( Ldap, Message );
|
|
|
|
//
|
|
// Loop through the attributes and weed out those values
|
|
// which are already set.
|
|
//
|
|
for ( Index = 0; Index < NumberOfAttributes; Index++ ) {
|
|
PWSTR *AttribValueRet = NULL;
|
|
|
|
//
|
|
// Be verbose - output the values returned for each type
|
|
//
|
|
NetpLog(( "\t\t%ws =", Attrib[Index].AttribType ));
|
|
|
|
AttribValueRet = ldap_get_values( Ldap, Entry, Attrib[Index].AttribType );
|
|
|
|
if ( AttribValueRet != NULL ) {
|
|
|
|
//
|
|
// Don't leak sensitive info!
|
|
//
|
|
if ( _wcsicmp( Attrib[Index].AttribType, NETSETUPP_UNICODEPWD ) == 0 ) {
|
|
NetpLog(( " <SomePassword>" ));
|
|
} else {
|
|
PWSTR *CurrentValueRet;
|
|
|
|
for ( CurrentValueRet = AttribValueRet; *CurrentValueRet != NULL; CurrentValueRet++ ) {
|
|
NetpLog(( " %ws", *CurrentValueRet ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Remove those values from the modification which are alredy set
|
|
//
|
|
NetpRemoveDuplicateStrings( AttribValueRet, Attrib[Index].AttribValues );
|
|
|
|
ldap_value_free( AttribValueRet );
|
|
|
|
//
|
|
// If this is a single valued attribute, we need to
|
|
// replace (not add) its value since it already exists in the DS
|
|
//
|
|
if ( (Attrib[Index].AttribFlags & NETSETUPP_MULTIVAL_ATTRIB) == 0 ) {
|
|
ModList[Index].mod_op = LDAP_MOD_REPLACE;
|
|
}
|
|
}
|
|
NetpLog(( "\n" ));
|
|
|
|
//
|
|
// If there are any attribute values which are
|
|
// not already set, add them to the modification.
|
|
//
|
|
if ( *(Attrib[Index].AttribValues) != NULL ) {
|
|
Mods[ModIndex] = &ModList[Index];
|
|
ModIndex ++;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Terminate the modification list
|
|
//
|
|
Mods[ModIndex] = NULL;
|
|
|
|
//
|
|
// Otherwise, error out
|
|
//
|
|
|
|
} else {
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
NetpLog(( "NetpModifyComputerObjectInDs: ldap_search_s failed: 0x%lx 0x%lx\n",
|
|
LdapStatus, NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Do the modifications if there are any
|
|
//
|
|
|
|
if ( ModIndex == 0 ) {
|
|
NetpLog(( "NetpModifyComputerObjectInDs: There are _NO_ modifications to do\n" ));
|
|
NetStatus = NERR_Success;
|
|
} else {
|
|
|
|
//
|
|
// Be verbose - output the attribute values to be set
|
|
//
|
|
NetpLog(( "NetpModifyComputerObjectInDs: Attribute values to set:\n" ));
|
|
for ( Index = 0; Mods[Index] != NULL; Index++ ) {
|
|
NetpLog(( "\t\t%ws =", (*(Mods[Index])).mod_type ));
|
|
|
|
//
|
|
// Don't leak sensitive info!
|
|
//
|
|
if ( _wcsicmp( (*(Mods[Index])).mod_type, NETSETUPP_UNICODEPWD ) == 0 ) {
|
|
NetpLog(( " <SomePassword>" ));
|
|
} else {
|
|
ULONG ValIndex;
|
|
|
|
for ( ValIndex = 0; ((*(Mods[Index])).mod_values)[ValIndex] != NULL; ValIndex++ ) {
|
|
NetpLog(( " %ws", ((*(Mods[Index])).mod_values)[ValIndex] ));
|
|
}
|
|
}
|
|
NetpLog(( "\n" ));
|
|
}
|
|
|
|
//
|
|
// Now, add the missing attributes
|
|
//
|
|
|
|
if ( NewAccount ) {
|
|
LdapStatus = ldap_add_s( Ldap, ComputerObjectDn, Mods );
|
|
} else {
|
|
LdapStatus = ldap_modify_s( Ldap, ComputerObjectDn, Mods );
|
|
}
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
|
|
//
|
|
// Return the error code the user understands
|
|
//
|
|
if ( LdapStatus == LDAP_ALREADY_EXISTS ) {
|
|
NetStatus = NERR_UserExists;
|
|
} else {
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
}
|
|
NetpLog(( "NetpModifyComputerObjectInDs: %s failed: 0x%lx 0x%lx\n",
|
|
NewAccount ?
|
|
"ldap_add_s" : "ldap_modify_s",
|
|
LdapStatus, NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we enable the account, toggle the account type property.
|
|
// See comment in NetpSetMachineAccountPasswordAndTypeEx() for
|
|
// an explanation of why this is needed. (Search for USN).
|
|
//
|
|
|
|
if ( AccountBeingEnabled ) {
|
|
Mods[0] = NULL;
|
|
for ( Index = 0; Index < NumberOfAttributes; Index++ ) {
|
|
if ( _wcsicmp( ModList[Index].mod_type, NETSETUPP_USERACCOUNTCONTROL ) == 0 ) {
|
|
Mods[0] = &ModList[Index];
|
|
|
|
//
|
|
// If this is a single valued attribute (it is, but...), we need to
|
|
// replace (not add) its value since it already exists in the DS
|
|
//
|
|
if ( (Attrib[Index].AttribFlags & NETSETUPP_MULTIVAL_ATTRIB) == 0 ) {
|
|
ModList[Index].mod_op = LDAP_MOD_REPLACE;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
Mods[1] = NULL;
|
|
|
|
if ( Mods[0] != NULL ) {
|
|
|
|
//
|
|
// Disable the account
|
|
//
|
|
*(Mods[0]->mod_values) = NETSETUPP_ACCNT_TYPE_DISABLED;
|
|
LdapStatus = ldap_modify_s( Ldap, ComputerObjectDn, Mods );
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
NetpLog(( "NetpModifyComputerObjectInDs: set UserAccountControl (1) on '%ws' failed: 0x%lx 0x%lx\n",
|
|
ComputerObjectDn, LdapStatus, NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Re-enable the account
|
|
//
|
|
*(Mods[0]->mod_values) = NETSETUPP_ACCNT_TYPE_ENABLED;
|
|
LdapStatus = ldap_modify_s( Ldap, ComputerObjectDn, Mods );
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NetStatus = LdapMapErrorToWin32( LdapStatus );
|
|
NetpLog(( "NetpModifyComputerObjectInDs: set UserAccountControl (2) on '%ws' failed: 0x%lx 0x%lx\n",
|
|
ComputerObjectDn, LdapStatus, NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
NetpLog(( "NetpModifyComputerObjectInDs: Toggled UserAccountControl successfully\n" ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// If we've made up to this point, it's success!
|
|
//
|
|
|
|
NetStatus = NERR_Success;
|
|
|
|
//
|
|
// REVIEW: On error, consider using ldap_get_option to retrieve
|
|
// a human readable string describing the error that happend.
|
|
// Use LDAP_OPT_SERVER_ERROR as the option value. Return the
|
|
// string to the caller who may want to expose it to the user.
|
|
//
|
|
|
|
Cleanup:
|
|
|
|
if ( AttribTypesList != NULL ) {
|
|
NetApiBufferFree( AttribTypesList );
|
|
}
|
|
|
|
if ( ModList != NULL ) {
|
|
NetApiBufferFree( ModList );
|
|
}
|
|
|
|
if ( Mods != NULL ) {
|
|
NetApiBufferFree( Mods );
|
|
}
|
|
|
|
if ( Message != NULL ) {
|
|
ldap_msgfree( Message );
|
|
}
|
|
|
|
if ( SamAccountName != NULL ) {
|
|
NetApiBufferFree( SamAccountName );
|
|
}
|
|
|
|
if ( CurrentUI1 != NULL ) {
|
|
NetApiBufferFree( CurrentUI1 );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpCreateComputerObjectInDs(
|
|
IN PDOMAIN_CONTROLLER_INFO DcInfo,
|
|
IN LPWSTR Account OPTIONAL,
|
|
IN LPWSTR Password OPTIONAL,
|
|
IN LPWSTR ComputerName,
|
|
IN LPWSTR MachinePassword OPTIONAL,
|
|
IN LPWSTR DnsHostName OPTIONAL,
|
|
IN LPWSTR OU OPTIONAL
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Create a computer account in the specified OU.
|
|
|
|
Arguments:
|
|
|
|
DcInfo -- Domain controller on which to create the object
|
|
Account -- Account to use for the LDAP and DS binds.
|
|
If NULL, the default creds of the current user
|
|
context are used.
|
|
Password -- Password to used for the binds. Ignored if
|
|
Account is NULL.
|
|
ComputerName -- (Netbios) Name of the computer being joined
|
|
MachinePassword -- Password to set on the machine object
|
|
DnsHostName -- DNS host name of the computer being joined
|
|
OU -- OU under which to create the object.
|
|
The name must be a fully qualified name
|
|
e.g.: "ou=test,dc=ntdev,dc=microsoft,dc=com"
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
PLDAP Ldap = NULL;
|
|
PWSTR ComputerObjectDn = NULL;
|
|
PWSTR SamAccountName = NULL;
|
|
PWSTR DnsSpn = NULL;
|
|
PWSTR NetbiosSpn = NULL;
|
|
ULONG AttribCount;
|
|
|
|
PWSTR ClassValues[ 2 ];
|
|
PWSTR AccntNameValues[ 2 ];
|
|
PWSTR DnsHostNameValues[ 2 ];
|
|
PWSTR SpnValues[ 3 ];
|
|
PWSTR PasswordValues[ 2 ];
|
|
PWSTR AccntTypeValues[ 2 ];
|
|
NETSETUPP_MACH_ACC_ATTRIBUTE Attributes[NETSETUPP_COMP_OBJ_ATTR_COUNT];
|
|
|
|
USER_INFO_1003 UserInfo1003 = {NULL};
|
|
|
|
//
|
|
// Validate parameters
|
|
//
|
|
|
|
if ( DcInfo == NULL ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: No DcInfo passed\n" ));
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( ComputerName == NULL ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: No ComputerName passed\n" ));
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Verify that the DC runs DS
|
|
//
|
|
|
|
if ( (DcInfo->Flags & DS_DS_FLAG) == 0 ||
|
|
(DcInfo->Flags & DS_WRITABLE_FLAG) == 0 ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: DC passed '%ws' doesn't have writable DS 0x%lx\n",
|
|
DcInfo->DomainControllerName,
|
|
DcInfo->Flags ));
|
|
NetStatus = ERROR_NOT_SUPPORTED;
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// First, try to bind to the server
|
|
//
|
|
|
|
NetStatus = NetpLdapBind( DcInfo->DomainControllerName, Account, Password, &Ldap );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: NetpLdapBind failed: 0x%lx\n", NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Next get the computer object DN
|
|
//
|
|
|
|
NetStatus = NetpGetComputerObjectDn( DcInfo,
|
|
Account,
|
|
Password,
|
|
Ldap,
|
|
ComputerName,
|
|
OU,
|
|
&ComputerObjectDn );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: NetpGetComputerObjectDn failed: 0x%lx\n", NetStatus ));
|
|
|
|
//
|
|
// Return meaningful error
|
|
//
|
|
if ( NetStatus == ERROR_FILE_EXISTS ) {
|
|
NetStatus = NERR_UserExists;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get SAM account name
|
|
//
|
|
|
|
NetStatus = NetpGetMachineAccountName( ComputerName, &SamAccountName );
|
|
if ( NetStatus != NO_ERROR ) {
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build SPN values
|
|
//
|
|
|
|
if ( DnsHostName != NULL ) {
|
|
DnsSpn = LocalAlloc( 0, (wcslen(NETSETUPP_HOST_SPN_PREFIX) + wcslen(DnsHostName) + 1) * sizeof(WCHAR) );
|
|
if ( DnsSpn == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
swprintf( DnsSpn, L"%ws%ws", NETSETUPP_HOST_SPN_PREFIX, DnsHostName );
|
|
|
|
NetbiosSpn = LocalAlloc( 0, (wcslen(NETSETUPP_HOST_SPN_PREFIX) + wcslen(ComputerName) + 1) * sizeof(WCHAR) );
|
|
if ( Netbios == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
swprintf( NetbiosSpn, L"%ws%ws", NETSETUPP_HOST_SPN_PREFIX, ComputerName );
|
|
}
|
|
|
|
//
|
|
// Prepare the list of attributes that need to be set in the DS
|
|
//
|
|
// Always keep unicodePwd as the last entry because this order is
|
|
// assumed by the API called below.
|
|
//
|
|
|
|
AttribCount = 0;
|
|
|
|
Attributes[AttribCount].AttribType = NETSETUPP_OBJECTCLASS; //
|
|
Attributes[AttribCount].AttribFlags = NETSETUPP_MULTIVAL_ATTRIB; //
|
|
Attributes[AttribCount].AttribValues = ClassValues; // ObjectClass
|
|
ClassValues[ 0 ] = NETSETUPP_COMPUTER_OBJECT; //
|
|
ClassValues[ 1 ] = NULL; //
|
|
|
|
AttribCount ++;
|
|
|
|
Attributes[AttribCount].AttribType = NETSETUPP_SAMACCOUNTNAME; //
|
|
Attributes[AttribCount].AttribFlags = 0; //
|
|
Attributes[AttribCount].AttribValues = AccntNameValues; // SamAccountName
|
|
AccntNameValues[ 0 ] = SamAccountName; //
|
|
AccntNameValues[ 1 ] = NULL; //
|
|
|
|
AttribCount ++;
|
|
|
|
Attributes[AttribCount].AttribType = NETSETUPP_USERACCOUNTCONTROL; //
|
|
Attributes[AttribCount].AttribFlags = 0; //
|
|
Attributes[AttribCount].AttribValues = AccntTypeValues; // userAccountControl
|
|
AccntTypeValues[ 0 ] = NETSETUPP_ACCNT_TYPE_ENABLED; //
|
|
AccntTypeValues[ 1 ] = NULL; //
|
|
|
|
AttribCount ++;
|
|
|
|
if ( DnsHostName != NULL ) {
|
|
Attributes[AttribCount].AttribType = NETSETUPP_DNSHOSTNAME; //
|
|
Attributes[AttribCount].AttribFlags = 0; //
|
|
Attributes[AttribCount].AttribValues = DnsHostNameValues; // DnsHostName
|
|
DnsHostNameValues[ 0 ] = DnsHostName; //
|
|
DnsHostNameValues[ 1 ] = NULL; //
|
|
|
|
AttribCount ++;
|
|
|
|
Attributes[AttribCount].AttribType = NETSETUPP_SERVICEPRINCIPALNAME; //
|
|
Attributes[AttribCount].AttribFlags = NETSETUPP_MULTIVAL_ATTRIB; //
|
|
Attributes[AttribCount].AttribValues = SpnValues; // ServicePrincipalName
|
|
SpnValues[ 0 ] = DnsSpn; //
|
|
SpnValues[ 1 ] = NetbiosSpn; //
|
|
SpnValues[ 2 ] = NULL; //
|
|
|
|
AttribCount ++;
|
|
}
|
|
|
|
//
|
|
// The following attribute is the machine password. We avoid
|
|
// updating it through ldap because it is hard to ensure that
|
|
// the ldap session uses the 128-bit encryption required by
|
|
// SAM on the DC for password updates.
|
|
//
|
|
// To enforce the encryption, we would need to set an option
|
|
// LDAP_OPT_ENCRYPT via a ldap_set_option call following ldap_open
|
|
// before calling ldap_bind_s. However, there is no guarantee that
|
|
// the established connection will use 128 bit encryption; it may
|
|
// use 56 bit encryption if either side does not support strong
|
|
// encryption. We could, in principle, find out the resulting encryption
|
|
// strength using some QueryContextAttribute call, but it's just too much
|
|
// trouble. So, we will just create the account without the password and
|
|
// we will then update the password using good old Net/SAM API.
|
|
//
|
|
#if 0
|
|
Attributes[AttribCount].AttribType = NETSETUPP_UNICODEPWD; //
|
|
Attributes[AttribCount].AttribFlags = 0; //
|
|
Attributes[AttribCount].AttribValues = PasswordValues; // unicodePwd
|
|
PasswordValues[ 0 ] = MachinePassword; //
|
|
PasswordValues[ 1 ] = NULL; //
|
|
|
|
AttribCount ++;
|
|
#endif
|
|
|
|
//
|
|
// Modify the computer object given the list of attributes
|
|
//
|
|
|
|
NetStatus = NetpModifyComputerObjectInDs( DcInfo->DomainControllerName,
|
|
Ldap,
|
|
ComputerName,
|
|
ComputerObjectDn,
|
|
AttribCount,
|
|
Attributes );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: NetpModifyComputerObjectInDs failed: 0x%lx\n", NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Now set the password using good old Net/SAM API.
|
|
//
|
|
|
|
UserInfo1003.usri1003_password = MachinePassword;
|
|
NetStatus = NetUserSetInfo( DcInfo->DomainControllerName,
|
|
SamAccountName,
|
|
1003,
|
|
(PBYTE) &UserInfo1003,
|
|
NULL );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: NetUserSetInfo (level 1003) failed on '%ws' for '%ws': 0x%lx."
|
|
" Deleting the account.\n",
|
|
DcInfo->DomainControllerName,
|
|
SamAccountName,
|
|
NetStatus ));
|
|
}
|
|
|
|
//
|
|
// Delete the account if we couldn't set the password.
|
|
// Ignore the failure if we cannot delete the account for some reason.
|
|
//
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
ULONG LdapStatus;
|
|
|
|
LdapStatus = ldap_delete_s( Ldap, ComputerObjectDn );
|
|
|
|
if ( LdapStatus != LDAP_SUCCESS ) {
|
|
NetpLog(( "NetpCreateComputerObjectInDs: Failed to delete '%ws': 0x%lx 0x%lx\n",
|
|
ComputerObjectDn, LdapStatus, LdapMapErrorToWin32( LdapStatus ) ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Tell Netlogon that it should avoid setting
|
|
// DnsHostName and SPN until the reboot
|
|
//
|
|
|
|
if ( NetStatus == NO_ERROR && DnsHostName != NULL ) {
|
|
NetpAvoidNetlogonSpnSet( TRUE );
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if ( Ldap != NULL ) {
|
|
NetpLdapUnbind( Ldap );
|
|
}
|
|
|
|
if ( ComputerObjectDn != NULL ) {
|
|
NetApiBufferFree( ComputerObjectDn );
|
|
}
|
|
|
|
if ( SamAccountName != NULL ) {
|
|
NetApiBufferFree( SamAccountName );
|
|
}
|
|
|
|
if ( DnsSpn != NULL ) {
|
|
LocalFree( DnsSpn );
|
|
}
|
|
|
|
if ( NetbiosSpn != NULL ) {
|
|
LocalFree( NetbiosSpn );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetDnsHostNameAndSpn(
|
|
IN PDOMAIN_CONTROLLER_INFO DcInfo,
|
|
IN LPWSTR Account,
|
|
IN LPWSTR Password,
|
|
IN LPWSTR ComputerName,
|
|
IN LPWSTR DnsHostName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Set DnsHostName and HOST SPN (ServicePrincipalName) attributes on the
|
|
computer object in the DS.
|
|
|
|
Arguments:
|
|
|
|
DcInfo -- Domain controller on which to create the object
|
|
Account -- Account to use for the LDAP bind
|
|
Password -- Password to used for the bind
|
|
ComputerName -- Name of the computer being joined
|
|
DnsHostName -- DNS host name of the machine
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
HANDLE hToken = NULL;
|
|
PLDAP Ldap = NULL;
|
|
PWSTR ComputerObjectDn = NULL;
|
|
|
|
PWSTR DnsSpn = NULL;
|
|
PWSTR NetbiosSpn = NULL;
|
|
|
|
PWSTR DnsHostNameValues[ 2 ];
|
|
PWSTR SpnValues[ 3 ] = {NULL};
|
|
|
|
NETSETUPP_MACH_ACC_ATTRIBUTE Attributes[ 2 ];
|
|
|
|
|
|
//
|
|
// REVIEW: Kerberos has a bug such that if this server is joined remotely
|
|
// and the impersonated client connected to this server using NTLM (as is
|
|
// the case if this server is not a member of a domain before the join),
|
|
// explicit credentials supplied to ldap_bind or DsBindWithCredW will not
|
|
// work (AcquireCredentialsHandle call will fail). To get around this, we
|
|
// temporarily un-impersonates, bind to the DC, and then impersonate again
|
|
// at the end of this routine.
|
|
//
|
|
|
|
if ( OpenThreadToken( GetCurrentThread(),
|
|
TOKEN_IMPERSONATE,
|
|
TRUE,
|
|
&hToken ) ) {
|
|
|
|
if ( RevertToSelf() == 0 ) {
|
|
NetpLog(( "NetpSetDnsHostNameAndSpn: RevertToSelf failed: 0x%lx\n",
|
|
GetLastError() ));
|
|
}
|
|
|
|
} else {
|
|
NetpLog(( "NetpSetDnsHostNameAndSpn: OpenThreadToken failed: 0x%lx\n",
|
|
GetLastError() ));
|
|
}
|
|
|
|
//
|
|
// Bind to the DC
|
|
//
|
|
|
|
NetStatus = NetpLdapBind( DcInfo->DomainControllerName, Account, Password, &Ldap );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpSetDnsHostNameAndSpn: NetpLdapBind failed: 0x%lx\n", NetStatus ));
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Next get the computer object DN
|
|
//
|
|
|
|
NetStatus = NetpGetComputerObjectDn( DcInfo,
|
|
Account,
|
|
Password,
|
|
Ldap,
|
|
ComputerName,
|
|
NULL, // Default computer container
|
|
&ComputerObjectDn );
|
|
|
|
if ( NetStatus != NO_ERROR ) {
|
|
NetpLog(( "NetpSetDnsHostNameAndSpn: NetpGetComputerObjectDn failed: 0x%lx\n", NetStatus ));
|
|
|
|
//
|
|
// Return meaningful error
|
|
//
|
|
if ( NetStatus == ERROR_FILE_EXISTS ) {
|
|
NetStatus = NERR_UserExists;
|
|
}
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Build DnsHostName values
|
|
//
|
|
|
|
DnsHostNameValues[ 0 ] = DnsHostName;
|
|
DnsHostNameValues[ 1 ] = NULL;
|
|
|
|
//
|
|
// Build SPN values
|
|
//
|
|
|
|
DnsSpn = LocalAlloc( 0,
|
|
(wcslen(NETSETUPP_HOST_SPN_PREFIX) + wcslen(DnsHostName) + 1) * sizeof(WCHAR) );
|
|
if ( DnsSpn == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
swprintf( DnsSpn, L"%ws%ws", NETSETUPP_HOST_SPN_PREFIX, DnsHostName );
|
|
|
|
NetbiosSpn = LocalAlloc( 0,
|
|
(wcslen(NETSETUPP_HOST_SPN_PREFIX) + wcslen(ComputerName) + 1) * sizeof(WCHAR) );
|
|
if ( NetbiosSpn == NULL ) {
|
|
NetStatus = ERROR_NOT_ENOUGH_MEMORY;
|
|
goto Cleanup;
|
|
}
|
|
swprintf( NetbiosSpn, L"%ws%ws", NETSETUPP_HOST_SPN_PREFIX, ComputerName );
|
|
|
|
SpnValues[0] = DnsSpn;
|
|
SpnValues[1] = NetbiosSpn;
|
|
SpnValues[2] = NULL;
|
|
|
|
//
|
|
// Prepare the list of attributes that need to be set in the DS
|
|
//
|
|
|
|
Attributes[0].AttribType = NETSETUPP_DNSHOSTNAME; //
|
|
Attributes[0].AttribFlags = 0; // DnsHostName
|
|
Attributes[0].AttribValues = DnsHostNameValues; //
|
|
|
|
Attributes[1].AttribType = NETSETUPP_SERVICEPRINCIPALNAME; //
|
|
Attributes[1].AttribFlags = NETSETUPP_MULTIVAL_ATTRIB; // ServicePrincipalName
|
|
Attributes[1].AttribValues = SpnValues; //
|
|
|
|
//
|
|
// Modify the computer object given the list of attributes
|
|
//
|
|
|
|
NetStatus = NetpModifyComputerObjectInDs( DcInfo->DomainControllerName,
|
|
Ldap,
|
|
ComputerName,
|
|
ComputerObjectDn,
|
|
2,
|
|
Attributes );
|
|
|
|
//
|
|
// Tell Netlogon that it should avoid setting
|
|
// DnsHostName and SPN until the reboot
|
|
//
|
|
|
|
if ( NetStatus == NO_ERROR ) {
|
|
NetpAvoidNetlogonSpnSet( TRUE );
|
|
}
|
|
|
|
Cleanup:
|
|
|
|
if ( Ldap != NULL ) {
|
|
NetpLdapUnbind( Ldap );
|
|
}
|
|
|
|
//
|
|
// REVIEW: Revert the impersonation
|
|
//
|
|
|
|
if ( hToken != NULL ) {
|
|
if ( SetThreadToken( NULL, hToken ) == 0 ) {
|
|
NetpLog(( "NetpSetDnsHostNameAndSpn: SetThreadToken failed: 0x%lx\n",
|
|
GetLastError() ));
|
|
}
|
|
CloseHandle( hToken );
|
|
}
|
|
|
|
//
|
|
// Free locally allocated memory
|
|
//
|
|
|
|
if ( ComputerObjectDn != NULL ) {
|
|
NetApiBufferFree( ComputerObjectDn );
|
|
}
|
|
|
|
if ( DnsSpn != NULL ) {
|
|
LocalFree( DnsSpn );
|
|
}
|
|
|
|
if ( NetbiosSpn != NULL ) {
|
|
LocalFree( NetbiosSpn );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpDeleteComputerObjectInOU(
|
|
IN LPWSTR DC,
|
|
IN LPWSTR OU,
|
|
IN LPWSTR ComputerName,
|
|
IN LPWSTR Account,
|
|
IN LPWSTR Password
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine will actually create a computer account in the specified OU.
|
|
|
|
Arguments:
|
|
|
|
DC -- Domain controller on which to create the object
|
|
OU -- OU under which to create the object
|
|
ComputerName -- Name of the computer being joined
|
|
Account -- Account to use for the LDAP bind
|
|
Password -- Password to used for the bind
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR ObjectName = NULL, SamAccountName = NULL;
|
|
PLDAP Ldap = NULL;
|
|
ULONG Len;
|
|
|
|
Len = wcslen( ComputerName );
|
|
|
|
NetStatus = NetApiBufferAllocate( sizeof( NETSETUPP_OBJ_PREFIX ) + ( wcslen( OU ) + Len + 1 ) * sizeof( WCHAR ),
|
|
( PVOID * ) &ObjectName );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
swprintf( ObjectName, L"%ws%ws,%ws", NETSETUPP_OBJ_PREFIX, ComputerName, OU );
|
|
|
|
NetStatus = NetApiBufferAllocate( ( Len + 2 ) * sizeof( WCHAR ),
|
|
( PVOID * )&SamAccountName );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
swprintf( SamAccountName, L"%ws$", ComputerName );
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// Try and bind to the server
|
|
//
|
|
NetStatus = NetpLdapBind( DC,
|
|
Account,
|
|
Password,
|
|
&Ldap );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// Now, do the delete..
|
|
//
|
|
NetStatus = LdapMapErrorToWin32( ldap_delete_s( Ldap, ObjectName ) );
|
|
|
|
NetpLdapUnbind( Ldap );
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "NetpCreateComputerObjectInOU failed with %lu\n",
|
|
NetStatus ));
|
|
|
|
}
|
|
|
|
NetApiBufferFree( ObjectName );
|
|
NetApiBufferFree( SamAccountName );
|
|
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "NetpDeleteComputerObjectInOU failed with %lu\n",
|
|
NetStatus ));
|
|
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
#if defined(REMOTE_BOOT)
|
|
|
|
NET_API_STATUS
|
|
NetpGetRemoteBootMachinePassword(
|
|
OUT LPWSTR Password
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if this is a remote boot client, and if so return
|
|
the machine account password.
|
|
This information is obtained via an IOCTL to the redirector.
|
|
|
|
Arguments:
|
|
|
|
Password - returns the password. Should be at least PWLEN WCHARs long.
|
|
|
|
Return Value:
|
|
|
|
NERR_Success if the password is found.
|
|
An error if this is not a remote boot machine.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
NTSTATUS Status;
|
|
|
|
UNICODE_STRING DeviceName;
|
|
|
|
IO_STATUS_BLOCK IoStatusBlock;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
HANDLE RedirHandle = NULL;
|
|
|
|
UCHAR PacketBuffer[sizeof(ULONG)+64];
|
|
PLMMR_RB_CHECK_FOR_NEW_PASSWORD RequestPacket = (PLMMR_RB_CHECK_FOR_NEW_PASSWORD)PacketBuffer;
|
|
|
|
//
|
|
// Open the redirector device.
|
|
//
|
|
RtlInitUnicodeString(&DeviceName, DD_NFS_DEVICE_NAME_U);
|
|
|
|
InitializeObjectAttributes(
|
|
&ObjectAttributes,
|
|
&DeviceName,
|
|
OBJ_CASE_INSENSITIVE,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
Status = NtOpenFile(
|
|
&RedirHandle,
|
|
SYNCHRONIZE,
|
|
&ObjectAttributes,
|
|
&IoStatusBlock,
|
|
0,
|
|
0
|
|
);
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = IoStatusBlock.Status;
|
|
}
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
NetpLog(( "Could not open redirector device %lx\n",
|
|
Status ));
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Send the request to the redir.
|
|
//
|
|
|
|
Status = NtFsControlFile(
|
|
RedirHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatusBlock,
|
|
FSCTL_LMMR_RB_CHECK_FOR_NEW_PASSWORD,
|
|
NULL, // no input buffer
|
|
0,
|
|
PacketBuffer,
|
|
sizeof(PacketBuffer));
|
|
|
|
if (NT_SUCCESS(Status)) {
|
|
Status = IoStatusBlock.Status;
|
|
}
|
|
|
|
//
|
|
// We expect this to work on a disked machine, since we need the password
|
|
// to join.
|
|
//
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "Could not open FSCTL_LMMR_RB_CHECK_FOR_NEW_PASSWORD %lx\n",
|
|
Status ));
|
|
NetStatus = NetpNtStatusToApiStatus( Status );
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Copy the result back to the caller's buffer.
|
|
// Guard against buffer overrun.
|
|
//
|
|
|
|
if ( RequestPacket->Length > PWLEN*sizeof(WCHAR) ) {
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
NetpLog(( "NetpGetRemoteBootMachinePassword: Password too long %d\n",
|
|
RequestPacket->Length ));
|
|
} else {
|
|
RtlCopyMemory(Password, RequestPacket->Data, RequestPacket->Length);
|
|
Password[RequestPacket->Length / 2] = L'\0';
|
|
|
|
NetStatus = NO_ERROR;
|
|
}
|
|
|
|
Cleanup:
|
|
if ( RedirHandle != NULL ) {
|
|
NtClose( RedirHandle );
|
|
}
|
|
return NetStatus;
|
|
|
|
}
|
|
#endif // REMOTE_BOOT
|
|
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetMachineAccountPasswordAndType(
|
|
IN LPWSTR lpDcName,
|
|
IN PSID DomainSid,
|
|
IN LPWSTR lpAccountName,
|
|
IN LPWSTR lpPassword
|
|
)
|
|
{
|
|
return( NetpSetMachineAccountPasswordAndTypeEx(
|
|
lpDcName,
|
|
DomainSid,
|
|
lpAccountName,
|
|
lpPassword,
|
|
0,
|
|
TRUE
|
|
) );
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetMachineAccountPasswordAndTypeEx(
|
|
IN LPWSTR lpDcName,
|
|
IN PSID DomainSid,
|
|
IN LPWSTR lpAccountName,
|
|
IN OUT OPTIONAL LPWSTR lpPassword,
|
|
IN OPTIONAL UCHAR AccountState,
|
|
IN BOOL fIsNt4Dc
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Due to a few strange reasons, we cannot use the supported, documented Net apis for
|
|
managing the machine account, so we have to use the undocumented Sam apis. This routine
|
|
will set the password and account type on an account that alread exists.
|
|
|
|
Arguments:
|
|
|
|
lpDcName - Name of the DC on which the account lives
|
|
|
|
DomainSid - Sid of the domain on which the account lives
|
|
|
|
lpAccountName - Name of the account
|
|
|
|
lpPassword - Password to be set on the account.
|
|
This function gets a strong password to begin with.
|
|
If the dc refuses to accept this password, this fn
|
|
can weaken the password by making it shorter.
|
|
The caller of this function should check if the length
|
|
of the supplied password was changed.
|
|
This function should preferably return a BOOL to
|
|
indicate this.
|
|
|
|
AccountState - if specified, the account will be set to this state.
|
|
possible values:
|
|
ACCOUNT_STATE_ENABLED, ACCOUNT_STATE_DISABLED
|
|
|
|
fIsNt4Dc - TRUE if the DC is NT4 or earlier.
|
|
|
|
Return Value:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus=NERR_Success;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
UNICODE_STRING DcName, AccountName;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
SAM_HANDLE SamHandle = NULL, DomainHandle = NULL, AccountHandle = NULL;
|
|
ULONG UserRid;
|
|
PULONG RidList = NULL;
|
|
PSID_NAME_USE NameUseList = NULL;
|
|
PUSER_CONTROL_INFORMATION UserAccountControl = NULL;
|
|
USER_SET_PASSWORD_INFORMATION PasswordInfo;
|
|
ULONG OldUserInfo;
|
|
BOOL fAccountControlModified = FALSE;
|
|
LPWSTR lpSamAccountName=lpAccountName;
|
|
ULONG AccountNameLen=0;
|
|
|
|
AccountNameLen = wcslen( lpAccountName );
|
|
|
|
//
|
|
// if caller has not passed in sam-account name,
|
|
// generate it from machine name ==> append $ at the end
|
|
//
|
|
if (lpAccountName[AccountNameLen-1] != L'$')
|
|
{
|
|
NetStatus = NetpGetMachineAccountName(lpAccountName,
|
|
&lpSamAccountName);
|
|
|
|
if (NetStatus != NERR_Success)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
|
|
|
|
RtlInitUnicodeString( &DcName, lpDcName );
|
|
RtlZeroMemory( &ObjectAttributes, sizeof( OBJECT_ATTRIBUTES ) );
|
|
|
|
Status = SamConnect( &DcName,
|
|
&SamHandle,
|
|
SAM_SERVER_CONNECT | SAM_SERVER_LOOKUP_DOMAIN,
|
|
&ObjectAttributes );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamConnect to %wZ failed with 0x%lx\n", &DcName, Status ));
|
|
|
|
goto SetPasswordError;
|
|
|
|
}
|
|
|
|
//
|
|
// Open the domain
|
|
//
|
|
Status = SamOpenDomain( SamHandle,
|
|
DOMAIN_LOOKUP,
|
|
DomainSid,
|
|
&DomainHandle );
|
|
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
#ifdef NETSETUP_VERBOSE_LOGGING
|
|
|
|
UNICODE_STRING DisplaySid;
|
|
NTSTATUS Status2;
|
|
RtlZeroMemory( &DisplaySid, sizeof( UNICODE_STRING ) );
|
|
|
|
Status2 = RtlConvertSidToUnicodeString( &DisplaySid, DomainSid, TRUE );
|
|
|
|
if ( NT_SUCCESS( Status2 ) ) {
|
|
|
|
NetpLog(( "SamOpenDomain on %wZ failed with 0x%lx\n",
|
|
&DisplaySid, Status ));
|
|
|
|
RtlFreeUnicodeString(&DisplaySid);
|
|
|
|
} else {
|
|
|
|
NetpLog(( "SamOpenDomain on <undisplayable sid> failed with 0x%lx\n",
|
|
Status ));
|
|
}
|
|
#endif
|
|
|
|
goto SetPasswordError;
|
|
|
|
}
|
|
|
|
//
|
|
// Get the RID of the user account
|
|
//
|
|
RtlInitUnicodeString( &AccountName, lpSamAccountName );
|
|
Status = SamLookupNamesInDomain( DomainHandle,
|
|
1,
|
|
&AccountName,
|
|
&RidList,
|
|
&NameUseList );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamLookupNamesInDomain on %wZ failed with 0x%lx\n",
|
|
&AccountName, Status ));
|
|
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
UserRid = RidList[ 0 ];
|
|
SamFreeMemory( RidList );
|
|
SamFreeMemory( NameUseList );
|
|
|
|
//
|
|
// Finally, open the user account
|
|
//
|
|
Status = SamOpenUser( DomainHandle,
|
|
USER_FORCE_PASSWORD_CHANGE | USER_READ_ACCOUNT | USER_WRITE_ACCOUNT,
|
|
UserRid,
|
|
&AccountHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
Status = SamOpenUser( DomainHandle,
|
|
USER_FORCE_PASSWORD_CHANGE | USER_READ_ACCOUNT,
|
|
UserRid,
|
|
&AccountHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamOpenUser on %lu failed with 0x%lx\n",
|
|
UserRid,
|
|
Status ));
|
|
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, read the current user account type and see if it needs to be modified
|
|
//
|
|
Status = SamQueryInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID * )&UserAccountControl );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamQueryInformationUser for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
OldUserInfo = UserAccountControl->UserAccountControl;
|
|
|
|
//
|
|
// Avoid whacking the account if it's anything other than a workstation account
|
|
//
|
|
|
|
if ( (OldUserInfo & USER_MACHINE_ACCOUNT_MASK) != USER_WORKSTATION_TRUST_ACCOUNT ) {
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: Broken account type 0x%lx -- error out\n",
|
|
OldUserInfo ));
|
|
Status = STATUS_USER_EXISTS;
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
//
|
|
// Determine if the account control changes. If the account is being enabled,
|
|
// we want to perform the following sequence of operations for NT5: enable, disable,
|
|
// and enable again. This is needed to increase the USN (Universal Sequence
|
|
// Number) of this attribute so that the enabled value will win if the DS
|
|
// replication resolves colliding changes, as the following example shows.
|
|
// Suppose we have two DCs in the domain we join, A abd B. Suppose the account
|
|
// is currently disabled on A (because the user unjoined using that DC),
|
|
// but it is still enabled on B (because the replication hasn't happened yet).
|
|
// Suppose the user performs now joining to the domain. Then we have discovered
|
|
// B and so we proceed with setting up the changes to the existing account. If
|
|
// we don't toggle the account control attribute, then the USN of this attribute
|
|
// will not change on B (since attribute's value doesn't change) while it was
|
|
// incremented on A as the result of unjoin. At the replication time the data
|
|
// from A will rule and the account will be incorrectly marked as diabled.
|
|
//
|
|
// NOTE: This design may fail for the case of unjoining a domain that has
|
|
// three (or more) DCs, A, B, and C if the following sequence of operations
|
|
// happens. Suppose that the account is originally enabled on all DCs (state [1]
|
|
// in the bellow diagram). Then the user unjoins using DC A (state [2]). Then the
|
|
// user joins using B where the account is still enabled (state [3]). Then the user
|
|
// unjoins using C where the account is still enabled (state [4]). The final
|
|
// operation is unjoin, so the user expects that his account is disabled. We've
|
|
// assumed here that for some reason no replication was happening when these
|
|
// operations were performed. Then at the replication time the value from B will
|
|
// win (because of the additional toggling performed at the join time). But the
|
|
// account state on B is Enabled, so the final result will be that the account is
|
|
// enabled on all DCs which is not what the user expects.
|
|
//
|
|
// A B C
|
|
// Enabled [1] Enabled [1] Enabled [1]
|
|
// Disabled [2] Enabled (no-op)+Disabled (1 op) Disabled [4]
|
|
// Enabled [3]
|
|
//
|
|
|
|
if ( AccountState != ACCOUNT_STATE_IGNORE ) {
|
|
|
|
if ( ( AccountState == ACCOUNT_STATE_ENABLED ) &&
|
|
( (OldUserInfo & USER_ACCOUNT_DISABLED) || !fIsNt4Dc ) ) {
|
|
|
|
fAccountControlModified = TRUE;
|
|
UserAccountControl->UserAccountControl &= ~USER_ACCOUNT_DISABLED;
|
|
}
|
|
|
|
if ( ( AccountState == ACCOUNT_STATE_DISABLED ) &&
|
|
!( OldUserInfo & USER_ACCOUNT_DISABLED ) ) {
|
|
|
|
fAccountControlModified = TRUE;
|
|
UserAccountControl->UserAccountControl |= USER_ACCOUNT_DISABLED;
|
|
}
|
|
}
|
|
|
|
if ( fAccountControlModified == FALSE ) {
|
|
|
|
SamFreeMemory( UserAccountControl );
|
|
UserAccountControl = NULL;
|
|
}
|
|
|
|
//
|
|
// First, set the account type if required
|
|
//
|
|
if ( UserAccountControl ) {
|
|
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamSetInformationUser for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
|
|
goto SetPasswordError;
|
|
|
|
//
|
|
// If we are enabling the account, disable and re-enable it to
|
|
// make the two additional account state toggles.
|
|
//
|
|
} else if ( AccountState == ACCOUNT_STATE_ENABLED ) {
|
|
|
|
UserAccountControl->UserAccountControl |= USER_ACCOUNT_DISABLED;
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NetpLog(( "SamSetInformationUser (second) for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
UserAccountControl->UserAccountControl &= ~USER_ACCOUNT_DISABLED;
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NetpLog(( "SamSetInformationUser (third) for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If requested, set the password on the account
|
|
//
|
|
if ( lpPassword != NULL )
|
|
{
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
PasswordInfo.PasswordExpired = FALSE;
|
|
|
|
//
|
|
// Ok, then, set the password on the account
|
|
//
|
|
// The caller has passed in a strong password, try that first
|
|
// NT5 dcs will always accept a strong password.
|
|
//
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
if ( (Status == STATUS_PASSWORD_RESTRICTION) &&
|
|
!NetpIsDefaultPassword( lpAccountName, lpPassword ))
|
|
{
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: STATUS_PASSWORD_RESTRICTION error setting password. retrying...\n" ));
|
|
//
|
|
// SAM did not accpet a long password, try LM20_PWLEN
|
|
//
|
|
// This is probably because the dc is NT4 dc.
|
|
// NT4 dcs will not accept a password longer than LM20_PWLEN
|
|
//
|
|
lpPassword[LM20_PWLEN] = UNICODE_NULL;
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
if ( Status == STATUS_PASSWORD_RESTRICTION )
|
|
{
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: STATUS_PASSWORD_RESTRICTION error setting password. retrying...\n" ));
|
|
//
|
|
// SAM did not accpet a LM20_PWLEN password, try shorter one
|
|
//
|
|
// SAM uses RtlUpcaseUnicodeStringToOemString internally.
|
|
// In this process it is possible that in the worst case,
|
|
// n unicode char password will get mapped to 2*n dbcs
|
|
// char password. This will make it exceed LM20_PWLEN.
|
|
// To guard against this worst case, try a password
|
|
// with LM20_PWLEN/2 length
|
|
//
|
|
// One might say that LM20_PWLEN/2 length password
|
|
// is not really secure. I agree, but it is definitely
|
|
// better than the default password which we will have
|
|
// to fall back to otherwise.
|
|
//
|
|
lpPassword[LM20_PWLEN/2] = UNICODE_NULL;
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
if ( Status == STATUS_PASSWORD_RESTRICTION )
|
|
{
|
|
//
|
|
// SAM did not accpet a short pwd, try default pwd
|
|
//
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: STATUS_PASSWORD_RESTRICTION error setting password. retrying...\n" ));
|
|
|
|
NetpGenerateDefaultPassword(lpAccountName, lpPassword);
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpGenerateDefaultPassword: successfully set password\n" ));
|
|
}
|
|
else
|
|
{
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: SamSetInformationUser for UserSetPasswordInformation failed: 0x%lx\n", Status ));
|
|
|
|
//
|
|
// Make sure we try to restore the account control
|
|
//
|
|
if ( UserAccountControl )
|
|
{
|
|
NTSTATUS Status2;
|
|
|
|
UserAccountControl->UserAccountControl = OldUserInfo;
|
|
Status2 = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS( Status2 ) )
|
|
{
|
|
NetpLog(( "SamSetInformationUser for UserControlInformation (RESTORE) failed with 0x%lx\n", Status2 ));
|
|
}
|
|
}
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
}
|
|
|
|
SetPasswordError:
|
|
|
|
if ( lpSamAccountName != lpAccountName )
|
|
{
|
|
NetApiBufferFree( lpSamAccountName );
|
|
}
|
|
|
|
if ( AccountHandle ) {
|
|
|
|
SamCloseHandle( AccountHandle );
|
|
}
|
|
|
|
if ( DomainHandle ) {
|
|
|
|
SamCloseHandle( DomainHandle );
|
|
}
|
|
|
|
if ( SamHandle ) {
|
|
|
|
SamCloseHandle( SamHandle );
|
|
}
|
|
|
|
NetStatus = RtlNtStatusToDosError( Status );
|
|
|
|
SamFreeMemory( UserAccountControl );
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpUpdateDnsRegistrations (
|
|
IN BOOL AddRegistrations
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function removes or adds DNS registration entries via an entrypoint defined
|
|
in DHCPCSVC.DLL called DhcpRemoveDNSRegistrations or DhcpStaticRefreshParams,
|
|
respectively.
|
|
|
|
Arguments:
|
|
|
|
AddRegistrations -- TRUE if records should added. FALSE if records
|
|
should be deleted.
|
|
|
|
Return Value:
|
|
|
|
Error in the dll load. Otherwise the DHCP API return code.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NO_ERROR;
|
|
HMODULE hModule = NULL;
|
|
DNS_REGISTRATION_REMOVAL_FN pfnRemove = NULL;
|
|
DNS_REGISTRATION_ADDITION_FN pfnAdd = NULL;
|
|
|
|
hModule = LoadLibraryW( L"dhcpcsvc.dll" );
|
|
|
|
if ( hModule != NULL ) {
|
|
if ( AddRegistrations ) {
|
|
pfnAdd = (DNS_REGISTRATION_ADDITION_FN)GetProcAddress(
|
|
hModule,
|
|
"DhcpStaticRefreshParams"
|
|
);
|
|
if ( pfnAdd != NULL ) {
|
|
NetStatus = ( *pfnAdd )( NULL ); // register on all adapters
|
|
} else {
|
|
NetStatus = ERROR_INVALID_DLL;
|
|
}
|
|
|
|
} else {
|
|
pfnRemove = (DNS_REGISTRATION_REMOVAL_FN)GetProcAddress(
|
|
hModule,
|
|
"DhcpRemoveDNSRegistrations"
|
|
);
|
|
if ( pfnRemove != NULL ) {
|
|
NetStatus = ( *pfnRemove )();
|
|
} else {
|
|
NetStatus = ERROR_INVALID_DLL;
|
|
}
|
|
}
|
|
|
|
FreeLibrary( hModule );
|
|
|
|
} else {
|
|
NetStatus = ERROR_DLL_NOT_FOUND;
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
//
|
|
// Helper functions
|
|
//
|
|
LPWSTR
|
|
GetStrPtr(IN LPWSTR szString OPTIONAL)
|
|
{
|
|
return szString ? szString : L"(NULL)";
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpDuplicateString(IN LPCWSTR szSrc,
|
|
IN LONG cchSrc,
|
|
OUT LPWSTR* pszDst)
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
if (cchSrc < 0)
|
|
{
|
|
cchSrc = wcslen(szSrc);
|
|
}
|
|
|
|
++cchSrc;
|
|
|
|
NetStatus = NetApiBufferAllocate(cchSrc * sizeof( WCHAR ),
|
|
pszDst);
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
wcsncpy(*pszDst, szSrc, cchSrc);
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpConcatStrings(IN LPCWSTR szSrc1,
|
|
IN LONG cchSrc1,
|
|
IN LPCWSTR szSrc2,
|
|
IN LONG cchSrc2,
|
|
OUT LPWSTR* pszDst)
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
|
|
if (cchSrc1 < 0)
|
|
{
|
|
cchSrc1 = wcslen(szSrc1);
|
|
}
|
|
|
|
if (cchSrc2 < 0)
|
|
{
|
|
cchSrc2 = wcslen(szSrc2);
|
|
}
|
|
|
|
NetStatus = NetApiBufferAllocate((cchSrc1 + cchSrc2 + 1) * sizeof( WCHAR ),
|
|
pszDst);
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
wcsncpy(*pszDst, szSrc1, cchSrc1);
|
|
wcsncpy(*pszDst + cchSrc1, szSrc2, cchSrc2+1);
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpConcatStrings3(IN LPCWSTR szSrc1,
|
|
IN LONG cchSrc1,
|
|
IN LPCWSTR szSrc2,
|
|
IN LONG cchSrc2,
|
|
IN LPCWSTR szSrc3,
|
|
IN LONG cchSrc3,
|
|
OUT LPWSTR* pszDst)
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
|
|
if (cchSrc1 < 0)
|
|
{
|
|
cchSrc1 = wcslen(szSrc1);
|
|
}
|
|
|
|
if (cchSrc2 < 0)
|
|
{
|
|
cchSrc2 = wcslen(szSrc2);
|
|
}
|
|
|
|
if (cchSrc3 < 0)
|
|
{
|
|
cchSrc3 = wcslen(szSrc3);
|
|
}
|
|
|
|
NetStatus = NetApiBufferAllocate((cchSrc1 + cchSrc2 + cchSrc3 + 1) *
|
|
sizeof( WCHAR ), pszDst);
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
wcsncpy(*pszDst, szSrc1, cchSrc1);
|
|
wcsncpy(*pszDst + cchSrc1, szSrc2, cchSrc2);
|
|
wcsncpy(*pszDst + cchSrc1 + cchSrc2, szSrc3, cchSrc3+1);
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetMachineAccountName(
|
|
IN LPCWSTR szMachineName,
|
|
OUT LPWSTR* pszMachineAccountName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get machine account name from machine name.
|
|
|
|
Arguments:
|
|
|
|
szMachineName -- name of a computer
|
|
pszMachineAccountName -- receives the name of computer account
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
Notes:
|
|
|
|
Caller must free the allocated memory using NetApiBufferFree.
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus;
|
|
ULONG ulLen;
|
|
LPWSTR szMachineAccountName;
|
|
|
|
ulLen = wcslen(szMachineName);
|
|
|
|
NetStatus = NetApiBufferAllocate( (ulLen + 2) * sizeof(WCHAR),
|
|
(PBYTE *) &szMachineAccountName );
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
wcscpy(szMachineAccountName, szMachineName);
|
|
_wcsupr(szMachineAccountName);
|
|
szMachineAccountName[ulLen] = L'$';
|
|
szMachineAccountName[ulLen+1] = UNICODE_NULL;
|
|
*pszMachineAccountName = szMachineAccountName;
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGeneratePassword(
|
|
IN LPCWSTR szMachine,
|
|
IN BOOL fRandomPwdPreferred,
|
|
IN LPCWSTR szDcName,
|
|
IN BOOL fIsNt4Dc,
|
|
OUT LPWSTR szPassword
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
szMachine -- name of a computer
|
|
|
|
szPassword -- receives the generated password this buffer must be
|
|
atleast PWLEN+1 char long.
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
BOOL fUseDefaultPwd = FALSE;
|
|
|
|
// The default password is used if we are joining an NT4 DC
|
|
// that has RefusePasswordChange set. This is determined by
|
|
// remotely reading the appropriate netlogon regval.
|
|
// If the key cannot be read, it is assumed that the value is not set
|
|
//
|
|
if ( fIsNt4Dc )
|
|
{
|
|
//
|
|
// we are joining an NT4 domain, see if RefusePasswordChange is set
|
|
//
|
|
NetStatus = NetpGetNt4RefusePasswordChangeStatus( szDcName,
|
|
&fUseDefaultPwd );
|
|
}
|
|
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
//
|
|
// if we are explicitly asked to use a default password, generate one
|
|
//
|
|
if ( fUseDefaultPwd )
|
|
{
|
|
NetpGenerateDefaultPassword(szMachine, szPassword);
|
|
}
|
|
//
|
|
// otherwise if the caller prefers a random password, generate one
|
|
//
|
|
else if ( fRandomPwdPreferred )
|
|
{
|
|
NetStatus = NetpGenerateRandomPassword(szPassword);
|
|
}
|
|
#if defined(REMOTE_BOOT)
|
|
//
|
|
// If it's a remote boot machine, then this will return the
|
|
// current machine account password, so use that.
|
|
//
|
|
else if (NERR_Success ==
|
|
NetpGetRemoteBootMachinePassword(szPassword))
|
|
{
|
|
// do nothing since the above already generated the password
|
|
}
|
|
#endif
|
|
else
|
|
{
|
|
//
|
|
// if none of the above apply,
|
|
// we end up generating a default password
|
|
//
|
|
NetpGenerateDefaultPassword(szMachine, szPassword);
|
|
NetStatus = NERR_Success;
|
|
}
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
|
|
void
|
|
NetpGenerateDefaultPassword(
|
|
IN LPCWSTR szMachine,
|
|
OUT LPWSTR szPassword
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Generate the default password from machine name.
|
|
This is simply the first 14 characters of the machine name lower cased.
|
|
|
|
Arguments:
|
|
|
|
szMachine -- name of a computer
|
|
szPassword -- receives the generated password
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
wcsncpy( szPassword, szMachine, LM20_PWLEN );
|
|
szPassword[LM20_PWLEN] = UNICODE_NULL;
|
|
_wcslwr( szPassword );
|
|
}
|
|
|
|
BOOL
|
|
NetpIsDefaultPassword(
|
|
IN LPCWSTR szMachine,
|
|
IN LPWSTR szPassword
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Determine if szPassword is the default password for szMachine
|
|
|
|
Arguments:
|
|
|
|
szMachine -- name of a computer
|
|
szPassword -- machine password
|
|
|
|
Returns:
|
|
|
|
TRUE if szPassword is the default password,
|
|
FALSE otherwise
|
|
|
|
--*/
|
|
{
|
|
WCHAR szPassword2[LM20_PWLEN+1];
|
|
|
|
NetpGenerateDefaultPassword(szMachine, szPassword2);
|
|
|
|
return (wcscmp(szPassword, szPassword2) == 0);
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGenerateRandomPassword(
|
|
OUT LPWSTR szPassword
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus=NERR_Success;
|
|
ULONG Length, i;
|
|
BYTE n;
|
|
HCRYPTPROV CryptProvider = 0;
|
|
LPWSTR szPwd=szPassword;
|
|
BOOL fStatus;
|
|
|
|
#define PWD_CHAR_MIN 32 // ' ' space
|
|
#define PWD_CHAR_MAX 122 // 'z'
|
|
|
|
//
|
|
// there is a reason behind this number
|
|
//
|
|
Length = 120;
|
|
szPassword[Length] = UNICODE_NULL;
|
|
|
|
//
|
|
// Generate a random password.
|
|
//
|
|
// the password is made of english printable chars. when w2k client
|
|
// joins NT4 dc. SAM on the dc calls RRtlUpcaseUnicodeStringToOemString
|
|
// the password length will remain unchanged. If we do not do this,
|
|
// the dc returns STATUS_PASSWORD_RESTRICTION and we have to
|
|
// fall back to default password.
|
|
//
|
|
if ( CryptAcquireContext( &CryptProvider, NULL, NULL,
|
|
PROV_RSA_FULL, CRYPT_VERIFYCONTEXT ) )
|
|
{
|
|
for ( i = 0; i < Length; i++, szPwd++ )
|
|
{
|
|
//
|
|
// the method we use here is not very efficient.
|
|
// This does not matter much in the context of NetJoin apis
|
|
// but it should not be used where perf is a criterion
|
|
//
|
|
while ( ( fStatus = CryptGenRandom( CryptProvider, sizeof(BYTE),
|
|
(LPBYTE) &n ) ) &&
|
|
( ( n < PWD_CHAR_MIN ) || ( n > PWD_CHAR_MAX ) ) )
|
|
{
|
|
// try till we get a non-zero random number
|
|
}
|
|
|
|
if ( fStatus )
|
|
{
|
|
*szPwd = (WCHAR) n;
|
|
}
|
|
else
|
|
{
|
|
NetStatus = GetLastError();
|
|
break;
|
|
}
|
|
}
|
|
CryptReleaseContext( CryptProvider, 0 );
|
|
}
|
|
else
|
|
{
|
|
NetStatus = GetLastError();
|
|
}
|
|
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
NetpLog(( "NetpGenerateRandomPassword: failed: 0x%lx\n", NetStatus ));
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpStoreIntialDcRecord(
|
|
IN PDOMAIN_CONTROLLER_INFO DcInfo
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will cache the name of the domain controller on which we successfully
|
|
created/modified the machine account, so that the auth packages will know which dc to
|
|
try first
|
|
|
|
Arguments:
|
|
|
|
lpDcName - Name of the DC on which the account was created/modified
|
|
|
|
CreateNetlogonStoppedKey - If TRUE, a volatile key will be created
|
|
in the Netlogon registry section. The presence of this key
|
|
will instruct the client side of DsGetDcName( ) and the MSV1
|
|
package not to wait on netlogon to start.
|
|
|
|
Return Value:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
HKEY hNetLogon, hJoinKey = NULL;
|
|
ULONG Disp;
|
|
|
|
NetStatus = RegOpenKey( HKEY_LOCAL_MACHINE,
|
|
NETSETUPP_NETLOGON_JD_PATH,
|
|
&hNetLogon );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
NetStatus = RegCreateKeyEx( hNetLogon,
|
|
NETSETUPP_NETLOGON_JD,
|
|
0,
|
|
NULL,
|
|
REG_OPTION_NON_VOLATILE,
|
|
KEY_WRITE,
|
|
NULL,
|
|
&hJoinKey,
|
|
&Disp );
|
|
|
|
//
|
|
// Now, start creating all of the values. Ignore any failures, and don't write out
|
|
// NULL values
|
|
//
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
PWSTR String = DcInfo->DomainControllerName;
|
|
|
|
//
|
|
// DomainControllerName
|
|
//
|
|
if ( String ) {
|
|
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DC,
|
|
0,
|
|
REG_SZ,
|
|
( const PBYTE )String,
|
|
( wcslen( String ) + 1 ) * sizeof( WCHAR ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DC, String, NetStatus ));
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// DomainControllerAddress
|
|
//
|
|
String = DcInfo->DomainControllerAddress;
|
|
if ( String ) {
|
|
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DCA,
|
|
0,
|
|
REG_SZ,
|
|
( const PBYTE )String,
|
|
( wcslen( String ) + 1 ) * sizeof( WCHAR ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DCA, String, NetStatus ));
|
|
}
|
|
}
|
|
|
|
//
|
|
// DomainControllerType
|
|
//
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DCAT,
|
|
0,
|
|
REG_DWORD,
|
|
( const PBYTE )&DcInfo->DomainControllerAddressType,
|
|
sizeof( ULONG ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %lu failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DCAT,
|
|
DcInfo->DomainControllerAddressType, NetStatus ));
|
|
|
|
}
|
|
|
|
//
|
|
// DomainControllerType
|
|
//
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DG,
|
|
0,
|
|
REG_BINARY,
|
|
( const PBYTE )&DcInfo->DomainGuid,
|
|
sizeof( GUID ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DG, NetStatus ));
|
|
|
|
}
|
|
|
|
|
|
//
|
|
// DomainName
|
|
//
|
|
String = DcInfo->DomainName;
|
|
if ( String ) {
|
|
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DN,
|
|
0,
|
|
REG_SZ,
|
|
( const PBYTE )String,
|
|
( wcslen( String ) + 1 ) * sizeof( WCHAR ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DN, String, NetStatus ));
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// DnsForestName
|
|
//
|
|
String = DcInfo->DnsForestName;
|
|
if ( String ) {
|
|
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DFN,
|
|
0,
|
|
REG_SZ,
|
|
( const PBYTE )String,
|
|
( wcslen( String ) + 1 ) * sizeof( WCHAR ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DFN, String, NetStatus ));
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// Flags
|
|
//
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_F,
|
|
0,
|
|
REG_DWORD,
|
|
( const PBYTE )&DcInfo->Flags,
|
|
sizeof( ULONG ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %lu failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_F, DcInfo->Flags, NetStatus ));
|
|
|
|
}
|
|
|
|
//
|
|
// DcSiteName
|
|
//
|
|
String = DcInfo->DcSiteName;
|
|
if ( String ) {
|
|
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_DSN,
|
|
0,
|
|
REG_SZ,
|
|
( const PBYTE )String,
|
|
( wcslen( String ) + 1 ) * sizeof( WCHAR ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_DSN, String, NetStatus ));
|
|
|
|
}
|
|
}
|
|
|
|
//
|
|
// DcSiteName
|
|
//
|
|
String = DcInfo->ClientSiteName;
|
|
if ( String ) {
|
|
|
|
NetStatus = RegSetValueEx( hJoinKey,
|
|
NETSETUPP_NETLOGON_JD_CSN,
|
|
0,
|
|
REG_SZ,
|
|
( const PBYTE )String,
|
|
( wcslen( String ) + 1 ) * sizeof( WCHAR ) );
|
|
if ( NetStatus != NERR_Success ) {
|
|
|
|
NetpLog(( "Set of value %ws to %ws failed with %lu\n",
|
|
NETSETUPP_NETLOGON_JD_CSN, String, NetStatus ));
|
|
|
|
}
|
|
}
|
|
|
|
RegCloseKey( hJoinKey );
|
|
}
|
|
|
|
RegCloseKey( hNetLogon );
|
|
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
VOID
|
|
NetpAvoidNetlogonSpnSet(
|
|
BOOL AvoidSet
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function will write into Netlogon reg key to instruct Netlogon
|
|
not to register DnsHostName and SPNs. This is needed because
|
|
Netlogon could otherwise set incorrect values based on the old computer
|
|
name. The registry key that this function writes is volatile so that
|
|
Netlogon will notice it before the reboot but it will not exist after
|
|
the reboot when Netlogon will have the new computer name.
|
|
|
|
Arguments:
|
|
|
|
AvoidSet - If TRUE, this routine will inform netlogon to not write SPNs
|
|
Otherwise, it will delete the reg key which we may have set previously.
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
HKEY hNetLogon = NULL;
|
|
HKEY hNetLogonAvoidSpnSet = NULL;
|
|
ULONG Disp;
|
|
|
|
NetStatus = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
|
|
NETSETUPP_NETLOGON_JD_PATH,
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&hNetLogon );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
|
|
//
|
|
// If we are to avoid SPN setting by netlogon,
|
|
// write the appropriate reg key to inform Netlogon accordingly
|
|
//
|
|
if ( AvoidSet ) {
|
|
NetStatus = RegCreateKeyEx( hNetLogon,
|
|
NETSETUPP_NETLOGON_AVOID_SPN,
|
|
0,
|
|
NULL,
|
|
REG_OPTION_VOLATILE,
|
|
KEY_WRITE,
|
|
NULL,
|
|
&hNetLogonAvoidSpnSet,
|
|
&Disp );
|
|
|
|
if ( NetStatus == NERR_Success ) {
|
|
RegCloseKey( hNetLogonAvoidSpnSet );
|
|
}
|
|
|
|
//
|
|
// Otherwise, delete the reg key which we may have set previously.
|
|
//
|
|
} else {
|
|
RegDeleteKey( hNetLogon,
|
|
NETSETUPP_NETLOGON_AVOID_SPN );
|
|
}
|
|
|
|
RegCloseKey( hNetLogon );
|
|
}
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpWaitForNetlogonSc(
|
|
IN LPCWSTR szDomainName
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
NTSTATUS NlSubStatus=STATUS_SUCCESS;
|
|
LPBYTE pNetlogonInfo=NULL;
|
|
UINT cAttempts=0;
|
|
BOOLEAN fScSetup=FALSE;
|
|
PNETLOGON_INFO_2 pNetlogonInfo2;
|
|
|
|
|
|
#define NL_SC_WAIT_INTERVAL 2000
|
|
#define NL_SC_WAIT_NUM_ATTEMPTS 60
|
|
|
|
NetpLog(( "NetpWaitForNetlogonSc: waiting for netlogon secure channel setup...\n"));
|
|
|
|
while (!fScSetup && (cAttempts < NL_SC_WAIT_NUM_ATTEMPTS))
|
|
{
|
|
cAttempts++;
|
|
NetStatus = I_NetLogonControl2( NULL, NETLOGON_CONTROL_TC_QUERY,
|
|
2, (LPBYTE) &szDomainName,
|
|
(LPBYTE *) &pNetlogonInfo );
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
pNetlogonInfo2 = (PNETLOGON_INFO_2) pNetlogonInfo;
|
|
NlSubStatus = pNetlogonInfo2->netlog2_tc_connection_status;
|
|
fScSetup = NlSubStatus == NERR_Success;
|
|
NetApiBufferFree(pNetlogonInfo);
|
|
}
|
|
|
|
if (!fScSetup)
|
|
{
|
|
Sleep(NL_SC_WAIT_INTERVAL);
|
|
}
|
|
}
|
|
|
|
NetpLog(( "NetpWaitForNetlogonSc: status: 0x%lx, sub-status: 0x%lx\n",
|
|
NetStatus, NlSubStatus));
|
|
|
|
return NetStatus;
|
|
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetDefaultLcidOnMachine(
|
|
IN LPCWSTR szMachine,
|
|
OUT LCID* plcidMachine
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
HKEY hkeyRemoteMachine, hkeyLanguage;
|
|
WCHAR szLocale[16];
|
|
DWORD dwLocaleSize=0;
|
|
DWORD dwType;
|
|
|
|
static WCHAR c_szRegKeySystemLanguage[] =
|
|
L"System\\CurrentControlSet\\Control\\Nls\\Locale";
|
|
static WCHAR c_szRegValDefault[] = L"(Default)";
|
|
|
|
//
|
|
// Connect to the remote registry
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
NetStatus = RegConnectRegistry( szMachine,
|
|
HKEY_LOCAL_MACHINE,
|
|
&hkeyRemoteMachine );
|
|
//
|
|
// Now, open the system language key
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
NetStatus = RegOpenKeyEx( hkeyRemoteMachine,
|
|
c_szRegKeySystemLanguage,
|
|
0, KEY_READ, &hkeyLanguage);
|
|
//
|
|
// get default locale
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
dwLocaleSize = sizeof( szLocale );
|
|
NetStatus = RegQueryValueEx( hkeyLanguage,
|
|
c_szRegValDefault,
|
|
NULL, &dwType,
|
|
(LPBYTE) szLocale,
|
|
&dwLocaleSize );
|
|
if ( NetStatus == NERR_Success)
|
|
{
|
|
if ((dwType == REG_SZ) &&
|
|
(swscanf(szLocale, L"%lx", plcidMachine) != 1))
|
|
{
|
|
//$ REVIEW kumarp 29-May-1999
|
|
// better errorcode?
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
RegCloseKey( hkeyLanguage );
|
|
}
|
|
RegCloseKey( hkeyRemoteMachine );
|
|
}
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpVerifyStrOemCompatibleInLocale(
|
|
IN LPCWSTR szString,
|
|
IN LCID lcidRemote
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
NTSTATUS NtStatus=STATUS_SUCCESS;
|
|
OEM_STRING osLocal = { 0 };
|
|
OEM_STRING osRemote = { 0 };
|
|
UNICODE_STRING sString;
|
|
LCID lcidLocal;
|
|
|
|
lcidLocal = GetThreadLocale();
|
|
|
|
RtlInitUnicodeString(&sString, szString);
|
|
NtStatus = RtlUnicodeStringToOemString(&osLocal, &sString, TRUE);
|
|
|
|
__try
|
|
{
|
|
if (NtStatus == STATUS_SUCCESS)
|
|
{
|
|
if (SetThreadLocale(lcidRemote))
|
|
{
|
|
NtStatus = RtlUnicodeStringToOemString(&osRemote,
|
|
&sString, TRUE);
|
|
if (NtStatus == STATUS_SUCCESS)
|
|
{
|
|
if (!RtlEqualMemory(osLocal.Buffer, osRemote.Buffer,
|
|
osLocal.Length))
|
|
{
|
|
NetStatus = NERR_NameUsesIncompatibleCodePage;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NetStatus = RtlNtStatusToDosError(NtStatus);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NetStatus = GetLastError();
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
NetStatus = RtlNtStatusToDosError(NtStatus);
|
|
}
|
|
}
|
|
|
|
__finally
|
|
{
|
|
if (!SetThreadLocale(lcidLocal))
|
|
{
|
|
NetStatus = GetLastError();
|
|
}
|
|
// RtlFreeOemString checks for NULL Buffer
|
|
RtlFreeOemString(&osLocal);
|
|
RtlFreeOemString(&osRemote);
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpVerifyStrOemCompatibleOnMachine(
|
|
IN LPCWSTR szRemoteMachine,
|
|
IN LPCWSTR szString
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
LCID lcidRemoteMachine;
|
|
|
|
NetStatus = NetpGetDefaultLcidOnMachine(szRemoteMachine,
|
|
&lcidRemoteMachine);
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
NetStatus = NetpVerifyStrOemCompatibleInLocale(szString,
|
|
lcidRemoteMachine);
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
|
|
|
|
#define NETP_NETLOGON_PATH L"System\\CurrentControlSet\\services\\Netlogon\\parameters\\"
|
|
#define NETP_NETLOGON_RPC L"RefusePasswordChange"
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetNt4RefusePasswordChangeStatus(
|
|
IN LPCWSTR Nt4Dc,
|
|
OUT BOOL* RefusePasswordChangeSet
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the regkey NETP_NETLOGON_PATH\NETP_NETLOGON_RPC on Nt4Dc.
|
|
Return the value read in the out parameter.
|
|
|
|
Arguments:
|
|
|
|
Nt4Dc -- name of machine to read reg. from
|
|
RefusePasswordChangeSet -- value returned
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
PWSTR FullComputerName = NULL;
|
|
HKEY NetlogonRootKey, DcKey;
|
|
ULONG Length, Type;
|
|
DWORD Value;
|
|
|
|
*RefusePasswordChangeSet = FALSE;
|
|
|
|
//
|
|
// Build the full computer name if necessary
|
|
//
|
|
if ( *Nt4Dc != L'\\' )
|
|
{
|
|
NetStatus = NetApiBufferAllocate( ( wcslen( Nt4Dc ) + 3 ) * sizeof( WCHAR ),
|
|
( LPVOID * )&FullComputerName );
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
swprintf( FullComputerName, L"\\\\%ws", Nt4Dc );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FullComputerName = (LPWSTR) Nt4Dc;
|
|
}
|
|
|
|
NetpLog(( "NetpGetNt4RefusePasswordChangeStatus: trying to read from '%ws'\n", FullComputerName));
|
|
|
|
//
|
|
// Connect to the remote registry
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
NetStatus = RegConnectRegistry( FullComputerName,
|
|
HKEY_LOCAL_MACHINE,
|
|
&DcKey );
|
|
//
|
|
// Now, open the netlogon parameters section
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
NetStatus = RegOpenKeyEx( DcKey,
|
|
NETP_NETLOGON_PATH,
|
|
0,
|
|
KEY_READ,
|
|
&NetlogonRootKey);
|
|
|
|
//
|
|
// Now, see if the key actually exists...
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
Length = sizeof( Value );
|
|
NetStatus = RegQueryValueEx( NetlogonRootKey,
|
|
NETP_NETLOGON_RPC,
|
|
NULL,
|
|
&Type,
|
|
( LPBYTE )&Value,
|
|
&Length );
|
|
if ( NetStatus == NERR_Success)
|
|
{
|
|
NetpLog(( "NetpGetNt4RefusePasswordChangeStatus: RefusePasswordChange == %d\n", Value));
|
|
|
|
if ( Value != 0 )
|
|
{
|
|
*RefusePasswordChangeSet = TRUE;
|
|
}
|
|
|
|
}
|
|
RegCloseKey( NetlogonRootKey );
|
|
}
|
|
RegCloseKey( DcKey );
|
|
}
|
|
}
|
|
|
|
if ( FullComputerName != Nt4Dc )
|
|
{
|
|
NetApiBufferFree( FullComputerName );
|
|
}
|
|
|
|
//
|
|
// If anything went wrong, ignore it...
|
|
//
|
|
if ( NetStatus != NERR_Success )
|
|
{
|
|
NetpLog(( "NetpGetNt4RefusePasswordChangeStatus: failed but ignored the failure: 0x%lx\n", NetStatus ));
|
|
NetStatus = NERR_Success;
|
|
}
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpGetComputerNameAllocIfReqd(
|
|
OUT LPWSTR* ppwszMachine,
|
|
IN UINT cLen
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Get name of the computer on which this runs. Alloc a buffer
|
|
if the name is longer than cLen.
|
|
|
|
Arguments:
|
|
|
|
ppwszMachine -- pointer to buffer. this receives a buffer if allocated.
|
|
cLen -- length of buffer pointed to by *ppwszMachine.
|
|
If the computer name to be returned is longer than this
|
|
a new buffer is allocated.
|
|
|
|
Returns:
|
|
|
|
NERR_Success -- Success
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus=NERR_Success;
|
|
|
|
if ( GetComputerName( *ppwszMachine, &cLen ) == FALSE )
|
|
{
|
|
NetStatus = GetLastError();
|
|
|
|
if ( (NetStatus == ERROR_INSUFFICIENT_BUFFER) ||
|
|
(NetStatus == ERROR_BUFFER_OVERFLOW) )
|
|
{
|
|
// allocate an extra char for the append-$ case
|
|
NetStatus = NetApiBufferAllocate( (cLen + 1 + 1) * sizeof(WCHAR),
|
|
(PBYTE *) ppwszMachine );
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
if ( GetComputerName( *ppwszMachine, &cLen ) == FALSE )
|
|
{
|
|
NetStatus = GetLastError();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
// ======================================================================
|
|
//
|
|
// Note: all code below this has been added as helper code for
|
|
// NetpSetComputerAccountPassword. this function is used by
|
|
// netdom.exe to fix a dc that was rendered unusable because
|
|
// of a ds restore resulting into 2+ password mismatch on
|
|
// machine account.
|
|
//
|
|
// This entire code is temporary and should be removed and
|
|
// rewritten post w2k.
|
|
//
|
|
|
|
static
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpEncodePassword(
|
|
IN LPWSTR lpPassword,
|
|
IN OUT PUCHAR Seed,
|
|
OUT LPWSTR *EncodedPassword,
|
|
OUT PULONG EncodedPasswordLength
|
|
)
|
|
{
|
|
NET_API_STATUS status = NERR_Success;
|
|
UNICODE_STRING EncodedPasswordU;
|
|
PWSTR PasswordPart;
|
|
ULONG PwdLen;
|
|
|
|
*EncodedPassword = NULL;
|
|
*EncodedPasswordLength = 0;
|
|
|
|
if ( lpPassword ) {
|
|
|
|
PwdLen = wcslen( ( LPWSTR )lpPassword ) * sizeof( WCHAR );
|
|
|
|
PwdLen += sizeof( WCHAR ) + sizeof( WCHAR );
|
|
|
|
status = NetApiBufferAllocate( PwdLen,
|
|
( PVOID * )EncodedPassword );
|
|
|
|
if ( status == NERR_Success ) {
|
|
|
|
//
|
|
// We'll put the encode byte as the first character in the string
|
|
//
|
|
PasswordPart = ( *EncodedPassword ) + 1;
|
|
wcscpy( PasswordPart, ( LPWSTR )lpPassword );
|
|
RtlInitUnicodeString( &EncodedPasswordU, PasswordPart );
|
|
|
|
*Seed = 0;
|
|
RtlRunEncodeUnicodeString( Seed, &EncodedPasswordU );
|
|
|
|
*( PWCHAR )( *EncodedPassword ) = ( WCHAR )*Seed;
|
|
|
|
//
|
|
// Encode the old password as well...
|
|
//
|
|
RtlInitUnicodeString( &EncodedPasswordU, lpPassword );
|
|
RtlRunEncodeUnicodeString( Seed, &EncodedPasswordU );
|
|
*EncodedPasswordLength = PwdLen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return( status );
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
NetpLsaOpenSecret2(
|
|
IN LSA_HANDLE hLsa,
|
|
IN PUNICODE_STRING pusSecretName,
|
|
IN ACCESS_MASK DesiredAccess,
|
|
OUT PLSA_HANDLE phSecret
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_UNSUCCESSFUL;
|
|
HANDLE hToken=NULL;
|
|
|
|
__try
|
|
{
|
|
if (OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE,
|
|
TRUE, &hToken))
|
|
{
|
|
if (SetThreadToken(NULL, NULL))
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (GetLastError() == ERROR_NO_TOKEN)
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
if ( NT_SUCCESS(Status) )
|
|
{
|
|
Status = LsaOpenSecret(hLsa, pusSecretName,
|
|
DesiredAccess, phSecret);
|
|
}
|
|
}
|
|
__finally
|
|
{
|
|
if (hToken)
|
|
{
|
|
if ( !SetThreadToken(NULL, hToken) ) {
|
|
NetpLog(( "NetpLsaOpenSecret2: Couldn't reset the user token 0x%lx\n",
|
|
GetLastError() ));
|
|
Status = NetpApiStatusToNtStatus( GetLastError() );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if ( hToken ) {
|
|
CloseHandle( hToken );
|
|
}
|
|
|
|
NetpLog(( "NetpLsaOpenSecret: status: 0x%lx\n", Status ));
|
|
|
|
return Status;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpManageMachineSecret2(
|
|
IN LPWSTR lpMachine,
|
|
IN LPWSTR lpPassword,
|
|
IN DWORD Action,
|
|
OUT PLSA_HANDLE pPolicyHandle OPTIONAL
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LSA_HANDLE LocalPolicy = NULL, SecretHandle = NULL;
|
|
UNICODE_STRING Key, Data, *CurrentValue = NULL;
|
|
BOOLEAN SecretCreated = FALSE;
|
|
|
|
if( Action == NETSETUPP_CREATE )
|
|
{
|
|
ASSERT( lpPassword );
|
|
}
|
|
|
|
UNREFERENCED_PARAMETER( lpMachine );
|
|
|
|
Status = NetpGetLsaHandle( NULL, pPolicyHandle, &LocalPolicy );
|
|
|
|
//
|
|
// open/create the secret
|
|
//
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
RtlInitUnicodeString( &Key, L"$MACHINE.ACC" );
|
|
RtlInitUnicodeString( &Data, lpPassword );
|
|
|
|
Status = NetpLsaOpenSecret2( LocalPolicy, &Key,
|
|
Action == NETSETUPP_CREATE ?
|
|
SECRET_SET_VALUE | SECRET_QUERY_VALUE : DELETE,
|
|
&SecretHandle );
|
|
|
|
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND )
|
|
{
|
|
if ( Action == NETSETUPP_DELETE )
|
|
{
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
Status = LsaCreateSecret( LocalPolicy, &Key,
|
|
SECRET_SET_VALUE, &SecretHandle );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
SecretCreated = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpManageMachineSecret: Open/Create secret failed: 0x%lx\n", Status ));
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
if ( Action == NETSETUPP_CREATE )
|
|
{
|
|
//
|
|
// First, read the current value, so we can save it as the old value
|
|
//
|
|
if ( SecretCreated )
|
|
{
|
|
CurrentValue = &Data;
|
|
}
|
|
else
|
|
{
|
|
Status = LsaQuerySecret( SecretHandle, &CurrentValue,
|
|
NULL, NULL, NULL );
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
//
|
|
// Now, store both the new password and the old
|
|
//
|
|
Status = LsaSetSecret( SecretHandle, &Data, CurrentValue );
|
|
|
|
if ( !SecretCreated )
|
|
{
|
|
LsaFreeMemory( CurrentValue );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No secret handle means we failed earlier in
|
|
// some intermediate state. That's ok, just press on.
|
|
//
|
|
if ( SecretHandle != NULL )
|
|
{
|
|
Status = LsaDelete( SecretHandle );
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
SecretHandle = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( SecretHandle )
|
|
{
|
|
LsaClose( SecretHandle );
|
|
}
|
|
}
|
|
|
|
NetpSetLsaHandle( LocalPolicy, pPolicyHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpManageMachineSecret: '%s' operation failed: 0x%lx\n",
|
|
Action == NETSETUPP_CREATE ? "CREATE" : "DELETE", Status ));
|
|
}
|
|
|
|
return( RtlNtStatusToDosError( Status ) );
|
|
}
|
|
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetMachineAccountPasswordAndTypeEx2(
|
|
IN LPWSTR lpDcName,
|
|
IN PSID DomainSid,
|
|
IN LPWSTR lpAccountName,
|
|
IN OUT OPTIONAL LPWSTR lpPassword,
|
|
IN OPTIONAL UCHAR AccountState
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus=NERR_Success;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
UNICODE_STRING DcName, AccountName;
|
|
OBJECT_ATTRIBUTES ObjectAttributes;
|
|
SAM_HANDLE SamHandle = NULL, DomainHandle = NULL, AccountHandle = NULL;
|
|
ULONG UserRid;
|
|
PULONG RidList = NULL;
|
|
PSID_NAME_USE NameUseList = NULL;
|
|
PUSER_CONTROL_INFORMATION UserAccountControl = NULL;
|
|
USER_SET_PASSWORD_INFORMATION PasswordInfo;
|
|
ULONG OldUserInfo;
|
|
BOOL fAccountControlModified = FALSE;
|
|
LPWSTR lpSamAccountName=lpAccountName;
|
|
ULONG AccountNameLen=0;
|
|
|
|
AccountNameLen = wcslen( lpAccountName );
|
|
|
|
//
|
|
// if caller has not passed in sam-account name,
|
|
// generate it from machine name ==> append $ at the end
|
|
//
|
|
if (lpAccountName[AccountNameLen-1] != L'$')
|
|
{
|
|
NetStatus = NetpGetMachineAccountName(lpAccountName,
|
|
&lpSamAccountName);
|
|
|
|
if (NetStatus != NERR_Success)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
|
|
|
|
RtlInitUnicodeString( &DcName, lpDcName );
|
|
RtlZeroMemory( &ObjectAttributes, sizeof( OBJECT_ATTRIBUTES ) );
|
|
|
|
Status = SamConnect( &DcName,
|
|
&SamHandle,
|
|
SAM_SERVER_CONNECT | SAM_SERVER_LOOKUP_DOMAIN,
|
|
&ObjectAttributes );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamConnect to %wZ failed with 0x%lx\n", &DcName, Status ));
|
|
|
|
goto SetPasswordError;
|
|
|
|
}
|
|
|
|
//
|
|
// Open the domain
|
|
//
|
|
Status = SamOpenDomain( SamHandle,
|
|
DOMAIN_LOOKUP,
|
|
DomainSid,
|
|
&DomainHandle );
|
|
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
#ifdef NETSETUP_VERBOSE_LOGGING
|
|
|
|
UNICODE_STRING DisplaySid;
|
|
NTSTATUS Status2;
|
|
RtlZeroMemory( &DisplaySid, sizeof( UNICODE_STRING ) );
|
|
|
|
Status2 = RtlConvertSidToUnicodeString( &DisplaySid, DomainSid, TRUE );
|
|
|
|
if ( NT_SUCCESS( Status2 ) ) {
|
|
|
|
NetpLog(( "SamOpenDomain on %wZ failed with 0x%lx\n",
|
|
&DisplaySid, Status ));
|
|
|
|
RtlFreeUnicodeString(&DisplaySid);
|
|
|
|
} else {
|
|
|
|
NetpLog(( "SamOpenDomain on <undisplayable sid> failed with 0x%lx\n",
|
|
Status ));
|
|
}
|
|
#endif
|
|
|
|
goto SetPasswordError;
|
|
|
|
}
|
|
|
|
//
|
|
// Get the RID of the user account
|
|
//
|
|
RtlInitUnicodeString( &AccountName, lpSamAccountName );
|
|
Status = SamLookupNamesInDomain( DomainHandle,
|
|
1,
|
|
&AccountName,
|
|
&RidList,
|
|
&NameUseList );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamLookupNamesInDomain on %wZ failed with 0x%lx\n",
|
|
&AccountName, Status ));
|
|
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
UserRid = RidList[ 0 ];
|
|
SamFreeMemory( RidList );
|
|
SamFreeMemory( NameUseList );
|
|
|
|
//
|
|
// Finally, open the user account
|
|
//
|
|
Status = SamOpenUser( DomainHandle,
|
|
USER_FORCE_PASSWORD_CHANGE | USER_READ_ACCOUNT | USER_WRITE_ACCOUNT,
|
|
UserRid,
|
|
&AccountHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
Status = SamOpenUser( DomainHandle,
|
|
USER_FORCE_PASSWORD_CHANGE | USER_READ_ACCOUNT,
|
|
UserRid,
|
|
&AccountHandle );
|
|
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamOpenUser on %lu failed with 0x%lx\n",
|
|
UserRid,
|
|
Status ));
|
|
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Now, read the current user account type and see if it needs to be modified
|
|
//
|
|
Status = SamQueryInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID * )&UserAccountControl );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamQueryInformationUser for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
OldUserInfo = UserAccountControl->UserAccountControl;
|
|
|
|
|
|
//
|
|
// Determine if the account control changes. If the account is being enabled,
|
|
// we want to perform the following sequence of operations: enable, disable,
|
|
// and enable again. This is needed to increase the USN (Universal Sequence
|
|
// Number) of this attribute so that the enabled value will win if the DS
|
|
// replication resolves colliding changes, as the following example shows.
|
|
// Suppose we have two DCs in the domain we join, A abd B. Suppose the account
|
|
// is currently disabled on A (because the user unjoined using that DC),
|
|
// but it is still enabled on B (because the replication hasn't happened yet).
|
|
// Suppose the user performs now joining to the domain. Then we have discovered
|
|
// B and so we proceed with setting up the changes to the existing account. If
|
|
// we don't toggle the account control attribute, then the USN of this attribute
|
|
// will not change on B (since attribute's value doesn't change) while it was
|
|
// incremented on A as the result of unjoin. At the replication time the data
|
|
// from A will rule and the account will be incorrectly marked as diabled.
|
|
//
|
|
// NOTE: This design may fail for the case of unjoining a domain that has
|
|
// three (or more) DCs, A, B, and C if the following sequence of operations
|
|
// happens. Suppose that the account is originally enabled on all DCs (state [1]
|
|
// in the bellow diagram). Then the user unjoins using DC A (state [2]). Then the
|
|
// user joins using B where the account is still enabled (state [3]). Then the user
|
|
// unjoins using C where the account is still enabled (state [4]). The final
|
|
// operation is unjoin, so the user expects that his account is disabled. We've
|
|
// assumed here that for some reason no replication was happening when these
|
|
// operations were performed. Then at the replication time the value from B will
|
|
// win (because of the additional toggling performed at the join time). But the
|
|
// account state on B is Enabled, so the final result will be that the account is
|
|
// enabled on all DCs which is not what the user expects.
|
|
//
|
|
// A B C
|
|
// Enabled [1] Enabled [1] Enabled [1]
|
|
// Disabled [2] Enabled (no-op)+Disabled (1 op) Disabled [4]
|
|
// Enabled [3]
|
|
//
|
|
|
|
if ( AccountState != ACCOUNT_STATE_IGNORE ) {
|
|
|
|
if ( AccountState == ACCOUNT_STATE_ENABLED ) {
|
|
|
|
fAccountControlModified = TRUE;
|
|
UserAccountControl->UserAccountControl &= ~USER_ACCOUNT_DISABLED;
|
|
}
|
|
|
|
if ( ( AccountState == ACCOUNT_STATE_DISABLED ) &&
|
|
!( OldUserInfo & USER_ACCOUNT_DISABLED ) ) {
|
|
|
|
fAccountControlModified = TRUE;
|
|
UserAccountControl->UserAccountControl |= USER_ACCOUNT_DISABLED;
|
|
}
|
|
}
|
|
|
|
if ( fAccountControlModified == FALSE ) {
|
|
|
|
SamFreeMemory( UserAccountControl );
|
|
UserAccountControl = NULL;
|
|
}
|
|
|
|
//
|
|
// First, set the account type if required
|
|
//
|
|
if ( UserAccountControl ) {
|
|
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS( Status ) ) {
|
|
|
|
NetpLog(( "SamSetInformationUser for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
|
|
goto SetPasswordError;
|
|
|
|
//
|
|
// If we are enabling the account, disable and re-enable it to
|
|
// make the two additional account state toggles.
|
|
//
|
|
} else if ( AccountState == ACCOUNT_STATE_ENABLED ) {
|
|
|
|
UserAccountControl->UserAccountControl |= USER_ACCOUNT_DISABLED;
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NetpLog(( "SamSetInformationUser (second) for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
goto SetPasswordError;
|
|
}
|
|
|
|
UserAccountControl->UserAccountControl &= ~USER_ACCOUNT_DISABLED;
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS(Status) ) {
|
|
NetpLog(( "SamSetInformationUser (third) for UserControlInformation "
|
|
"failed with 0x%lx\n", Status ));
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// If requested, set the password on the account
|
|
//
|
|
if ( lpPassword != NULL )
|
|
{
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
PasswordInfo.PasswordExpired = FALSE;
|
|
|
|
//
|
|
// Ok, then, set the password on the account
|
|
//
|
|
// The caller has passed in a strong password, try that first
|
|
// NT5 dcs will always accept a strong password.
|
|
//
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
if ( !NT_SUCCESS( Status ) )
|
|
{
|
|
if ( (Status == STATUS_PASSWORD_RESTRICTION) &&
|
|
!NetpIsDefaultPassword( lpAccountName, lpPassword ))
|
|
{
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: STATUS_PASSWORD_RESTRICTION error setting password. retrying...\n" ));
|
|
//
|
|
// SAM did not accpet a long password, try LM20_PWLEN
|
|
//
|
|
// This is probably because the dc is NT4 dc.
|
|
// NT4 dcs will not accept a password longer than LM20_PWLEN
|
|
//
|
|
lpPassword[LM20_PWLEN] = UNICODE_NULL;
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
if ( Status == STATUS_PASSWORD_RESTRICTION )
|
|
{
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: STATUS_PASSWORD_RESTRICTION error setting password. retrying...\n" ));
|
|
//
|
|
// SAM did not accpet a LM20_PWLEN password, try shorter one
|
|
//
|
|
// SAM uses RtlUpcaseUnicodeStringToOemString internally.
|
|
// In this process it is possible that in the worst case,
|
|
// n unicode char password will get mapped to 2*n dbcs
|
|
// char password. This will make it exceed LM20_PWLEN.
|
|
// To guard against this worst case, try a password
|
|
// with LM20_PWLEN/2 length
|
|
//
|
|
// One might say that LM20_PWLEN/2 length password
|
|
// is not really secure. I agree, but it is definitely
|
|
// better than the default password which we will have
|
|
// to fall back to otherwise.
|
|
//
|
|
lpPassword[LM20_PWLEN/2] = UNICODE_NULL;
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
if ( Status == STATUS_PASSWORD_RESTRICTION )
|
|
{
|
|
//
|
|
// SAM did not accpet a short pwd, try default pwd
|
|
//
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: STATUS_PASSWORD_RESTRICTION error setting password. retrying...\n" ));
|
|
|
|
NetpGenerateDefaultPassword(lpAccountName, lpPassword);
|
|
RtlInitUnicodeString( &PasswordInfo.Password, lpPassword );
|
|
Status = SamSetInformationUser( AccountHandle,
|
|
UserSetPasswordInformation,
|
|
( PVOID )&PasswordInfo );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( NT_SUCCESS( Status ) )
|
|
{
|
|
NetpLog(( "NetpGenerateDefaultPassword: successfully set password\n" ));
|
|
}
|
|
else
|
|
{
|
|
NetpLog(( "NetpSetMachineAccountPasswordAndTypeEx: SamSetInformationUser for UserSetPasswordInformation failed: 0x%lx\n", Status ));
|
|
|
|
//
|
|
// Make sure we try to restore the account control
|
|
//
|
|
if ( UserAccountControl )
|
|
{
|
|
NTSTATUS Status2;
|
|
|
|
UserAccountControl->UserAccountControl = OldUserInfo;
|
|
Status2 = SamSetInformationUser( AccountHandle,
|
|
UserControlInformation,
|
|
( PVOID )UserAccountControl );
|
|
if ( !NT_SUCCESS( Status2 ) )
|
|
{
|
|
NetpLog(( "SamSetInformationUser for UserControlInformation (RESTORE) failed with 0x%lx\n", Status2 ));
|
|
}
|
|
}
|
|
goto SetPasswordError;
|
|
}
|
|
}
|
|
}
|
|
|
|
SetPasswordError:
|
|
|
|
if ( lpSamAccountName != lpAccountName )
|
|
{
|
|
NetApiBufferFree( lpSamAccountName );
|
|
}
|
|
|
|
if ( AccountHandle ) {
|
|
|
|
SamCloseHandle( AccountHandle );
|
|
}
|
|
|
|
if ( DomainHandle ) {
|
|
|
|
SamCloseHandle( DomainHandle );
|
|
}
|
|
|
|
if ( SamHandle ) {
|
|
|
|
SamCloseHandle( SamHandle );
|
|
}
|
|
|
|
NetStatus = RtlNtStatusToDosError( Status );
|
|
|
|
SamFreeMemory( UserAccountControl );
|
|
|
|
return( NetStatus );
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpSetComputerAccountPassword(
|
|
IN PWSTR szMachine,
|
|
IN PWSTR szDomainController,
|
|
IN PWSTR szUser,
|
|
IN PWSTR szUserPassword,
|
|
IN PVOID Reserved
|
|
)
|
|
{
|
|
NET_API_STATUS NetStatus=NERR_Success;
|
|
NET_API_STATUS NetStatus2=NERR_Success;
|
|
BOOL fIpcConnected = FALSE;
|
|
BYTE bSeed;
|
|
PPOLICY_PRIMARY_DOMAIN_INFO pPolicyPDI = NULL;
|
|
PPOLICY_DNS_DOMAIN_INFO pPolicyDns = NULL;
|
|
LSA_HANDLE hDC = NULL;
|
|
WCHAR szMachinePassword[ PWLEN + 1];
|
|
WCHAR szMachineNameBuf[MAX_COMPUTERNAME_LENGTH + 1];
|
|
PWSTR szMachineName=szMachineNameBuf;
|
|
|
|
NetSetuppOpenLog();
|
|
NetpLog(( "NetpSetComputerAccountPassword: for '%ws' on '%ws' using '%ws' creds\n", GetStrPtr(szMachine), GetStrPtr(szDomainController), GetStrPtr(szUser) ));
|
|
|
|
if ( ( szDomainController == NULL ) ||
|
|
( szUser == NULL ) ||
|
|
( szUserPassword == NULL ) )
|
|
{
|
|
NetStatus = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
if ( szMachine == NULL )
|
|
{
|
|
NetStatus = NetpGetComputerNameAllocIfReqd(&szMachineName,
|
|
MAX_COMPUTERNAME_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
szMachineName = szMachine;
|
|
}
|
|
|
|
NetStatus = NetpManageIPCConnect( szDomainController,
|
|
szUser, szUserPassword,
|
|
NETSETUPP_CONNECT_IPC );
|
|
|
|
NetpLog(( "NetpSetComputerAccountPassword: status of connecting to dc '%ws': 0x%lx\n", szDomainController, NetStatus ));
|
|
|
|
//
|
|
// get the lsa domain info on the DC
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
fIpcConnected = TRUE;
|
|
NetStatus = NetpGetLsaPrimaryDomain(szDomainController,
|
|
&pPolicyPDI, &pPolicyDns, &hDC);
|
|
}
|
|
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
//
|
|
// Generate the password to use on the machine account.
|
|
//
|
|
NetStatus = NetpGeneratePassword( szMachineName,
|
|
TRUE, // fRandomPwdPreferred
|
|
szDomainController,
|
|
FALSE, // fIsNt4Dc
|
|
szMachinePassword );
|
|
NetpLog(( "NetpSetComputerAccountPassword: status of generating machine password: 0x%lx\n", NetStatus ));
|
|
}
|
|
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
NetStatus = NetpSetMachineAccountPasswordAndTypeEx2(
|
|
szDomainController, pPolicyPDI->Sid,
|
|
szMachineName, szMachinePassword,
|
|
ACCOUNT_STATE_IGNORE
|
|
);
|
|
NetpLog(( "NetpSetComputerAccountPassword: status of setting machine password on %ws: 0x%lx\n", GetStrPtr(szDomainController), NetStatus ));
|
|
}
|
|
|
|
if (NetStatus == NERR_Success)
|
|
{
|
|
// if we are not creating the machine account,
|
|
// just set the password
|
|
NetStatus = NetpSetMachineAccountPasswordAndTypeEx2(
|
|
szMachineName, pPolicyPDI->Sid,
|
|
szMachineName, szMachinePassword,
|
|
ACCOUNT_STATE_IGNORE
|
|
);
|
|
NetpLog(( "NetpSetComputerAccountPassword: status of setting machine password on %ws: 0x%lx\n", GetStrPtr(szMachineName), NetStatus ));
|
|
}
|
|
|
|
//
|
|
// set the local machine secret
|
|
//
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
if ( NetStatus == NERR_Success )
|
|
{
|
|
NetStatus = NetpManageMachineSecret2( szMachineName,
|
|
szMachinePassword,
|
|
NETSETUPP_CREATE, NULL );
|
|
}
|
|
NetpLog(( "NetpSetComputerAccountPassword: status of setting local secret: 0x%lx\n", NetStatus ));
|
|
}
|
|
|
|
|
|
//
|
|
// Now, we no longer need our session to our dc
|
|
//
|
|
if ( fIpcConnected )
|
|
{
|
|
//RtlRunDecodeUnicodeString( bSeed, &usEncodedPassword );
|
|
NetStatus2 = NetpManageIPCConnect( szDomainController, szUser,
|
|
//usEncodedPassword.Buffer,
|
|
NULL,
|
|
NETSETUPP_DISCONNECT_IPC );
|
|
//RtlRunEncodeUnicodeString( &bSeed, &usEncodedPassword );
|
|
NetpLog(( "NetpJoinDomain: status of disconnecting from '%ws': 0x%lx\n", szDomainController, NetStatus2));
|
|
}
|
|
|
|
|
|
Cleanup:
|
|
if ( (szMachineName != szMachine) &&
|
|
(szMachineName != szMachineNameBuf) )
|
|
{
|
|
NetApiBufferFree( szMachineName );
|
|
}
|
|
|
|
if ( pPolicyPDI != NULL ) {
|
|
LsaFreeMemory( pPolicyPDI );
|
|
}
|
|
|
|
if ( pPolicyDns != NULL ) {
|
|
LsaFreeMemory( pPolicyDns );
|
|
}
|
|
|
|
if ( hDC != NULL ) {
|
|
LsaClose( hDC );
|
|
}
|
|
|
|
NetpLog(( "NetpSetComputerAccountPassword: status: 0x%lx\n", NetStatus ));
|
|
|
|
NetSetuppCloseLog();
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpUpdateW32timeConfig(
|
|
IN PCSTR szW32timeJoinConfigFuncName
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call entry point in w32time service so that it can update
|
|
its internal info after a domain join/unjoin
|
|
|
|
Arguments:
|
|
|
|
szW32timeJoinConfigFuncName - name of entry point to call
|
|
(must be W32TimeVerifyJoinConfig or W32TimeVerifyUnjoinConfig)
|
|
|
|
Return Value:
|
|
|
|
NERR_Success -- on Success
|
|
otherwise win32 error codes returned by LoadLibrary, GetProcAddress
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
HANDLE hDll = NULL;
|
|
typedef VOID (*PW32TimeUpdateJoinConfig)( VOID );
|
|
|
|
PW32TimeUpdateJoinConfig pfnW32timeUpdateJoinConfig = NULL;
|
|
|
|
//
|
|
// Call into the time service to allow it initialize itself properly
|
|
//
|
|
|
|
hDll = LoadLibraryA( "w32Time" );
|
|
|
|
if ( hDll != NULL )
|
|
{
|
|
pfnW32timeUpdateJoinConfig =
|
|
(PW32TimeUpdateJoinConfig) GetProcAddress(hDll,
|
|
szW32timeJoinConfigFuncName);
|
|
|
|
if ( pfnW32timeUpdateJoinConfig != NULL )
|
|
{
|
|
pfnW32timeUpdateJoinConfig();
|
|
}
|
|
else
|
|
{
|
|
NetStatus = GetLastError();
|
|
|
|
NetpLog(( "NetpUpdateW32timeConfig: Failed to get proc address for %s: 0x%lx\n", szW32timeJoinConfigFuncName, NetStatus ));
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
NetStatus = GetLastError();
|
|
|
|
NetpLog(( "NetpUpdateW32timeConfig: Failed to load w32time: 0x%lx\n", NetStatus ));
|
|
}
|
|
|
|
if ( hDll != NULL )
|
|
{
|
|
FreeLibrary( hDll );
|
|
}
|
|
|
|
NetpLog(( "NetpUpdateW32timeConfig: 0x%lx\n", NetStatus ));
|
|
|
|
return NetStatus;
|
|
}
|
|
|
|
NET_API_STATUS
|
|
NET_API_FUNCTION
|
|
NetpUpdateAutoenrolConfig(
|
|
IN BOOL UnjoinDomain
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Call entry point in pautoenr service so that it can update
|
|
its internal info after a domain join/unjoin
|
|
|
|
Arguments:
|
|
|
|
UnjoinDomain - If TRUE, we are unjoining from a domain.
|
|
Otherwise, we are roling back from unsuccessful domain
|
|
unjoin.
|
|
|
|
Return Value:
|
|
|
|
NERR_Success -- on Success
|
|
otherwise win32 error codes returned by LoadLibrary, GetProcAddress
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
{
|
|
NET_API_STATUS NetStatus = NERR_Success;
|
|
HANDLE hDll = NULL;
|
|
ULONG Flags = 0;
|
|
|
|
typedef BOOL (*PCertAutoRemove)( DWORD );
|
|
|
|
PCertAutoRemove pfnCertAutoRemove = NULL;
|
|
|
|
//
|
|
// Prepare the flags to pass to autoenrol routine
|
|
//
|
|
|
|
if ( UnjoinDomain ) {
|
|
Flags = CERT_AUTO_REMOVE_COMMIT;
|
|
} else {
|
|
Flags = CERT_AUTO_REMOVE_ROLL_BACK;
|
|
}
|
|
|
|
//
|
|
// Call into the time service to allow it initialize itself properly
|
|
//
|
|
|
|
hDll = LoadLibraryA( "pautoenr" );
|
|
|
|
if ( hDll != NULL ) {
|
|
|
|
pfnCertAutoRemove =
|
|
(PCertAutoRemove) GetProcAddress( hDll, "CertAutoRemove" );
|
|
|
|
if ( pfnCertAutoRemove != NULL ) {
|
|
if ( !pfnCertAutoRemove(Flags) ) {
|
|
NetStatus = GetLastError();
|
|
NetpLog(( "NetpUpdateAutoenrolConfig: CertAutoRemove failed 0x%lx\n",
|
|
NetStatus ));
|
|
}
|
|
} else {
|
|
NetStatus = GetLastError();
|
|
NetpLog(( "NetpUpdateAutoenrolConfig: Failed to get CertAutoRemove proc address 0x%lx\n",
|
|
NetStatus ));
|
|
}
|
|
|
|
} else {
|
|
NetStatus = GetLastError();
|
|
NetpLog(( "NetpUpdateAutoenrolConfig: Failed to load pautoenr: 0x%lx\n", NetStatus ));
|
|
}
|
|
|
|
if ( hDll != NULL ) {
|
|
FreeLibrary( hDll );
|
|
}
|
|
|
|
return NetStatus;
|
|
}
|