/*++ 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 #include #include #define WKSTA_NETLOGON #define NETSETUP_JOIN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // NetpNameCanonicalize #include // STRLEN #include // 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 ); // // Locally defined macros // #define clearncb(x) memset((char *)x,'\0',sizeof(NCB)) NTSTATUS NetpGetLsaHandle( IN LPWSTR lpServer, OPTIONAL IN LSA_HANDLE PolicyHandle, OPTIONAL OUT PLSA_HANDLE pPolicyHandle ) /*++ Routine Description: Either returns the given LSA handle if it's valid, or opens a new one Arguments: lpServer -- server name : NULL == local policy PolicyHandle -- Potentially open policy handle pPolicyHandle -- Open policy handle returned here Returns: STATUS_SUCCESS -- Success --*/ { NTSTATUS Status = STATUS_SUCCESS; OBJECT_ATTRIBUTES OA; UNICODE_STRING Server, *pServer = NULL; if ( PolicyHandle == 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, pPolicyHandle ); if ( !NT_SUCCESS( Status ) ) { NetpLog(( "NetpGetLsaHandle: LsaOpenPolicy on %ws failed: 0x%lx\n", GetStrPtr(lpServer), Status )); } } else { *pPolicyHandle = PolicyHandle; } return( Status ); } VOID NetpSetLsaHandle( IN LSA_HANDLE PassedHandle, IN LSA_HANDLE OpenHandle, OUT PLSA_HANDLE pReturnedHandle ) /*++ Routine Description: Either closes the opened handle or returns it Arguments: PassedHandle -- Handle passed to the original API call OpenHandle -- Handle returned from NetpGetLsaHandle pReturnedHandle -- handle is passed back to the caller if requested Returns: VOID --*/ { if ( pReturnedHandle == NULL ) { if ((PassedHandle == NULL) && (OpenHandle != NULL)) { LsaClose( OpenHandle ); } } else { *pReturnedHandle = OpenHandle; } } NET_API_STATUS NET_API_FUNCTION NetpSetLsaPrimaryDomain( IN LSA_HANDLE PolicyHandle, OPTIONAL 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: PolicyHandle -- Handle to the open policy 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, PolicyHandle, &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( PolicyHandle, LocalPolicy, pPolicyHandle ); NetpLog(( "NetpSetLsaPrimaryDomain: for '%ws' status: 0x%lx\n", GetStrPtr(lpDomain), Status )); return( RtlNtStatusToDosError( Status ) ); } NET_API_STATUS NET_API_FUNCTION NetpGetLsaPrimaryDomain( IN LSA_HANDLE PolicyHandle, OPTIONAL 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, PolicyHandle, &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( PolicyHandle, 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; Status = NetpGetLsaHandle( lpMachine, NULL, &hPolicy ); // // Now, get the server role info // if ( NT_SUCCESS( Status ) ) { Status = LsaQueryInformationPolicy( hPolicy, PolicyLsaServerRoleInformation, &pBuff); 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) { SetThreadToken(NULL, hToken); } } NetpLog(( "NetpLsaOpenSecret: status: 0x%lx\n", Status )); return Status; } //$ REVIEW kumarp 13-May-1999 // the fDelete param is used as uint but is declared as BOOL NET_API_STATUS NET_API_FUNCTION NetpManageMachineSecret( IN LSA_HANDLE PolicyHandle, OPTIONAL IN LPWSTR lpMachine, IN LPWSTR lpPassword, IN BOOL fDelete, IN BOOL UseDefaultForOldPwd, OUT PLSA_HANDLE pPolicyHandle OPTIONAL ) /*++ Routine Description: Create/delete the machine secret Arguments: PolicyHandle -- Optional open handle to the policy lpMachine -- Machine to add/delete the secret for lpPassword -- Machine password to use. fDelete -- if TRUE, the secret is removed 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( fDelete == FALSE ) { ASSERT( lpPassword ); } Status = NetpGetLsaHandle( NULL, PolicyHandle, &LocalPolicy ); // // open/create the secret // if ( NT_SUCCESS( Status ) ) { RtlInitUnicodeString( &Key, L"$MACHINE.ACC" ); RtlInitUnicodeString( &Data, lpPassword ); Status = NetpLsaOpenSecret( LocalPolicy, &Key, fDelete == NETSETUPP_CREATE ? SECRET_SET_VALUE | SECRET_QUERY_VALUE : DELETE, &SecretHandle ); if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) { if ( fDelete ) { 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 ( fDelete == 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( PolicyHandle, LocalPolicy, pPolicyHandle ); if ( !NT_SUCCESS( Status ) ) { NetpLog(( "NetpManageMachineSecret: '%s' operation failed: 0x%lx\n", fDelete == NETSETUPP_CREATE ? "CREATE" : "DELETE", Status )); } return( RtlNtStatusToDosError( Status ) ); } NET_API_STATUS NET_API_FUNCTION NetpReadCurrentSecret( IN LSA_HANDLE PolicyHandle, OPTIONAL OUT LPWSTR *lpCurrentSecret, OUT PLSA_HANDLE pPolicyHandle OPTIONAL ) /*++ Routine Description: Reads the value of the current secret Arguments: PolicyHandle -- Optional open handle to the policy 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; Status = NetpGetLsaHandle( NULL, PolicyHandle, &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( PolicyHandle, LocalPolicy, pPolicyHandle ); 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 ) { 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 failed with ERROR_NOT_SUPPORTED, this looks like a NT3.51 DC. The failure was ignored. dc list not written to netlogon cache.\n")); dwErr = ERROR_SUCCESS; } } if ( ( dwErr == NO_ERROR ) && TrustedDomainCount ) { // // Write the trusted domain list to a file where netlogon will find it. // dwErr = NlWriteFileForestTrustList ( NL_FOREST_BINARY_LOG_FILE_JOIN, TrustedDomains, TrustedDomainCount ); } else { NetpLog(( "NetpSetNetlogonDomainCache: NlWriteFileForestTrustList failed: 0x%lx\n", dwErr )); } // // 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, DOMAIN_ALIAS_RID_REMOTE_DESKTOP_USERS // this LAST item is only used on TS-App-Servers }; PWSTR ppwszLocalGroups[ sizeof( LocalRids ) / sizeof( ULONG )] = { NULL, NULL, NULL }; ULONG Rids[] = { DOMAIN_GROUP_RID_ADMINS, DOMAIN_GROUP_RID_USERS, DOMAIN_GROUP_RID_USERS // this LAST item is only used on TS-App-Servers }; BOOLEAN GroupMustExist[ sizeof( LocalRids ) / sizeof( ULONG )] = { TRUE, 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); // if we are NOT on an app server, we ignore the last entry if ( ! IsAppServer() ) { numOfGroups -= 1; } 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 " "[Rid 0x%lx] 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 LSA_HANDLE PolicyHandle, OPTIONAL IN PNETSETUP_SAVED_JOIN_STATE SavedState, IN BOOLEAN Save, OUT PLSA_HANDLE ReturnedPolicyHandle OPTIONAL ) /*++ Routine Description: Saves or restores the join state info. Arguments: PolicyHandle -- Local LSA policy handle. If not supplied, this fn. opens it 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, PolicyHandle, &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( LocalPolicy, NULL, &( SavedState->PrimaryDomainInfo ), &( SavedState->DnsDomainInfo ), NULL ); } else { Status = LsaSetInformationPolicy( LocalPolicy, PolicyPrimaryDomainInformation, SavedState->PrimaryDomainInfo ); if ( NT_SUCCESS( Status ) ) { Status = LsaSetInformationPolicy( LocalPolicy, PolicyDnsDomainInformation, SavedState->DnsDomainInfo ); } } } } NetpSetLsaHandle( PolicyHandle, 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; // // 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 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, *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; 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 ); NetStatus = NetpDuplicateString(DN, -1, DefaultOu); break; } i++; } ldap_value_free( Classes ); } 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 ( 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 ); 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 { 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 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; NetSetuppOpenLog(); 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; } // // 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, 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 . 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 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 // 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; 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; // // 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(( " " )); } 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(( " " )); } 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; } // // If there are no modifications to do // we are done. // if ( ModIndex == 0 ) { NetpLog(( "NetpModifyComputerObjectInDs: There are _NO_ modifications to do\n" )); NetStatus = NERR_Success; goto Cleanup; } // // 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(( " " )); } 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 we tried to create against a server that doesn't support 128 bit encryption, // we get LDAP_UNWILLING_TO_PERFORM back from the server. So, we'll create the // account again without the password, and then reset it using NetApi. // if ( LdapStatus == LDAP_UNWILLING_TO_PERFORM ) { LPWSTR MachinePassword; NetpLog(( "NetpModifyComputerObjectInDs: ldap_modify_s (1) returned" " LDAP_UNWILLING_TO_PERFORM. Trying to recover.\n" )); // // If we didn't try to set a password, there is no legitimate reason // for the add to fail. // if ( _wcsicmp( (*(Mods[ModIndex-1])).mod_type, NETSETUPP_UNICODEPWD ) != 0 ) { NetpLog(( "NetpModifyComputerObjectInDs: ldap_modify_s retuned LDAP_UNWILLING_TO_PERFORM" " but password was not being set\n" )); NetStatus = LdapMapErrorToWin32( LdapStatus ); goto Cleanup; } MachinePassword = *((*(Mods[ModIndex-1])).mod_values); // // If there are other entries besides the password, retry to add them // if ( ModIndex > 1 ) { Mods[ModIndex-1] = NULL; if ( NewAccount ) { LdapStatus = ldap_add_s( Ldap, ComputerObjectDn, Mods ); } else{ LdapStatus = ldap_modify_s( Ldap, ComputerObjectDn, Mods ); } if ( LdapStatus != LDAP_SUCCESS ) { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpModifyComputerObjectInDs: ldap_modify_s (2) failed: 0x%lx 0x%lx\n", LdapStatus, NetStatus )); goto Cleanup; } } // // Reset the password // NetStatus = NetpGetMachineAccountName( ComputerName, &SamAccountName ); if ( NetStatus != NO_ERROR ) { goto Cleanup; } NetStatus = NetUserGetInfo( DC, SamAccountName, 1, ( PBYTE * )&CurrentUI1 ); if ( NetStatus == NERR_Success ) { CurrentUI1->usri1_password = MachinePassword; if ( !FLAG_ON( CurrentUI1->usri1_flags, UF_WORKSTATION_TRUST_ACCOUNT ) ) { CurrentUI1->usri1_flags = UF_WORKSTATION_TRUST_ACCOUNT | UF_SCRIPT; } NetStatus = NetUserSetInfo( DC, SamAccountName, 1, ( PBYTE )CurrentUI1, NULL ); if ( NetStatus != NERR_Success ) { NetpLog(( "NetpModifyComputerObjectInDs: NetUserSetInfo failed on '%ws' for '%ws': 0x%lx." " Deleting the account.\n", DC, SamAccountName, NetStatus )); } } else { NetpLog(( "NetpModifyComputerObjectInDs: NetUserGetInfo failed on '%ws' for '%ws': 0x%lx." " Deleting the account.\n", DC, SamAccountName, NetStatus )); } // // Delete the user if it fails // if ( NetStatus != NERR_Success ) { LdapStatus = ldap_delete_s( Ldap, ComputerObjectDn ); if ( LdapStatus != LDAP_SUCCESS ) { NetpLog(( "NetpModifyComputerObjectInDs: Failed to delete '%ws': 0x%lx 0x%lx\n", ComputerObjectDn, LdapStatus, LdapMapErrorToWin32( LdapStatus ) )); } goto Cleanup; } } else 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: ldap_modify_s (1) failed: 0x%lx 0x%lx\n", LdapStatus, NetStatus )); goto Cleanup; } // // Toggle the account type property. See comment in // NetpSetMachineAccountPasswordAndTypeEx() for an // explanation of why this is needed. (Search for USN). // 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, 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_1 *CurrentUI1 = 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 though 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. // First get the current account info. // NetStatus = NetUserGetInfo( DcInfo->DomainControllerName, SamAccountName, 1, ( PBYTE * )&CurrentUI1 ); // // Update the password and reset it // if ( NetStatus == NO_ERROR ) { CurrentUI1->usri1_password = MachinePassword; if ( !FLAG_ON( CurrentUI1->usri1_flags, UF_WORKSTATION_TRUST_ACCOUNT ) ) { CurrentUI1->usri1_flags = UF_WORKSTATION_TRUST_ACCOUNT | UF_SCRIPT; } NetStatus = NetUserSetInfo( DcInfo->DomainControllerName, SamAccountName, 1, ( PBYTE )CurrentUI1, NULL ); if ( NetStatus != NERR_Success ) { NetpLog(( "NetpCreateComputerObjectInDs: NetUserSetInfo failed on '%ws' for '%ws': 0x%lx." " Deleting the account.\n", DcInfo->DomainControllerName, SamAccountName, NetStatus )); } } else { NetpLog(( "NetpCreateComputerObjectInDs: NetUserGetInfo 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 ); } if ( CurrentUI1 != NULL ) { NetApiBufferFree( CurrentUI1 ); } 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 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 // SpnValues[0] = LocalAlloc( 0, (wcslen(NETSETUPP_HOST_SPN_PREFIX) + wcslen(DnsHostName) + 1) * sizeof(WCHAR) ); if ( SpnValues[0] == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } swprintf( SpnValues[0], L"%ws%ws", NETSETUPP_HOST_SPN_PREFIX, DnsHostName ); SpnValues[1] = LocalAlloc( 0, (wcslen(NETSETUPP_HOST_SPN_PREFIX) + wcslen(ComputerName) + 1) * sizeof(WCHAR) ); if ( SpnValues[1] == NULL ) { NetStatus = ERROR_NOT_ENOUGH_MEMORY; goto Cleanup; } swprintf( SpnValues[1], L"%ws%ws", NETSETUPP_HOST_SPN_PREFIX, ComputerName ); 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 ( SpnValues[0] != NULL ) { LocalFree( SpnValues[0] ); } if ( SpnValues[1] != NULL ) { LocalFree( SpnValues[1] ); } return NetStatus; } NET_API_STATUS NET_API_FUNCTION NetpCreateComputerObjectInOU( IN LPWSTR DC, IN LPWSTR OU, IN LPWSTR ComputerName, IN LPWSTR Account, IN LPWSTR Password, IN LPWSTR MachinePassword ) /*++ Routine Description: 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. The name must be a fully qualified name e.g.: "ou=test,dc=ntdev,dc=microsoft,dc=com" ComputerName -- Name of the computer being joined Account -- Account to use for the LDAP bind Password -- Password to used for the bind MachinePassword -- Password to set on the machine object Returns: NERR_Success -- Success --*/ { NET_API_STATUS NetStatus = NERR_Success; ULONG LdapStatus; PWSTR ObjectName = NULL, SamAccountName = NULL; PLDAP Ldap = NULL; PWSTR ClassValues[ 2 ]; PWSTR AccntNameValues[ 2 ]; PWSTR PasswordValues[ 2 ]; PWSTR AccntTypeValues[ 2 ]; PWSTR AttribTypes[NETSETUPP_COMP_OBJ_ATTR_COUNT]; ULONG AttribFlags[NETSETUPP_COMP_OBJ_ATTR_COUNT]; PWSTR *AttribValues[NETSETUPP_COMP_OBJ_ATTR_COUNT]; LDAPMod ModList[NETSETUPP_COMP_OBJ_ATTR_COUNT]; PLDAPMod Mods[NETSETUPP_COMP_OBJ_ATTR_COUNT + 1]; LDAPMessage *Message = NULL, *Entry; USER_INFO_1 *CurrentUI1; ULONG Index; ULONG ModIndex = 0; BOOL NewAccount = FALSE; // // First, try to bind to the server // NetStatus = NetpLdapBind( DC, Account, Password, &Ldap ); if ( NetStatus != NO_ERROR ) { NetpLog(( "NetpCreateComputerObjectInOU: NetpLdapBind failed: 0x%lx\n", NetStatus )); goto Cleanup; } // // Next verify that the OU exists // LdapStatus = ldap_compare_s( Ldap, OU, NETSETUPP_OBJECTCLASS, NETSETUPP_ORGANIZATIONALUNIT ); if ( LdapStatus == LDAP_COMPARE_FALSE ) { NetStatus = ERROR_FILE_NOT_FOUND; NetpLog(( "NetpCreateComputerObjectInOU: Specified path '%ws' is not an OU\n", OU )); goto Cleanup; } else if ( LdapStatus != LDAP_COMPARE_TRUE ) { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpCreateComputerObjectInOU: ldap_compare_s failed: 0x%lx 0x%lx\n", LdapStatus, NetStatus )); goto Cleanup; } // // Allocate the object name // NetStatus = NetApiBufferAllocate( sizeof( NETSETUPP_OBJ_PREFIX ) + ( wcslen( OU ) + wcslen( ComputerName ) + 1 ) * sizeof( WCHAR ), ( PVOID * ) &ObjectName ); if ( NetStatus != NO_ERROR ) { goto Cleanup; } swprintf( ObjectName, L"%ws%ws,%ws", NETSETUPP_OBJ_PREFIX, ComputerName, OU ); NetStatus = NetpGetMachineAccountName(ComputerName, &SamAccountName); if ( NetStatus != NO_ERROR ) { goto Cleanup; } // // Build attribute values. Always keep unicodePwd as the last entry // because this order is assumed by the fail-over code below. // AttribTypes[ 0 ] = NETSETUPP_OBJECTCLASS; // AttribFlags[ 0 ] = NETSETUPP_MULTIVAL_ATTRIB; // AttribValues[ 0 ] = ClassValues; // ObjectClass ClassValues[ 0 ] = NETSETUPP_COMPUTER_OBJECT; // ClassValues[ 1 ] = NULL; // AttribTypes[ 1 ] = NETSETUPP_SAMACCOUNTNAME; // AttribFlags[ 1 ] = 0; // AttribValues[ 1 ] = AccntNameValues; // SamAccountName AccntNameValues[ 0 ] = SamAccountName; // AccntNameValues[ 1 ] = NULL; // AttribTypes[ 2 ] = NETSETUPP_USERACCOUNTCONTROL; // AttribFlags[ 2 ] = 0; // AttribValues[ 2 ] = AccntTypeValues; // userAccountControl AccntTypeValues[ 0 ] = NETSETUPP_ACCNT_TYPE_ENABLED; // AccntTypeValues[ 1 ] = NULL; // AttribTypes[ 3 ] = NETSETUPP_UNICODEPWD; // AttribFlags[ 3 ] = 0; // AttribValues[ 3 ] = PasswordValues; // unicodePwd PasswordValues[ 0 ] = MachinePassword; // PasswordValues[ 1 ] = NULL; // // // Build modification list given the list of attributes // NetpLog(( "NetpCreateComputerObjectInOU: Initial attribute values:\n" )); for ( Index = 0; Index < NETSETUPP_COMP_OBJ_ATTR_COUNT; Index++ ) { ModList[Index].mod_op = LDAP_MOD_ADD; // Set to add. We may adjust this below. ModList[Index].mod_type = AttribTypes[Index]; ModList[Index].mod_values = AttribValues[Index]; // // Be verbose - output all values of each attribute // NetpLog(( "\t\t%ws =", AttribTypes[Index] )); // // Don't leak sensitive info! // if ( _wcsicmp( AttribTypes[Index], NETSETUPP_UNICODEPWD ) == 0 ) { NetpLog(( " " )); } else { ULONG ValIndex; for ( ValIndex = 0; AttribValues[Index][ValIndex] != NULL; ValIndex++ ) { NetpLog(( " %ws", AttribValues[Index][ValIndex] )); } } NetpLog(( "\n" )); } // // Now check which attribute values are already set in the DS // LdapStatus = ldap_search_s( Ldap, ObjectName, LDAP_SCOPE_BASE, NULL, AttribTypes, 0, &Message ); // // If the computer object does not exist, // we need to add all attributes // if ( LdapStatus == LDAP_NO_SUCH_OBJECT ) { NetpLog(( "NetpCreateComputerObjectInOU: Computer Object does not exist in OU\n" )); NewAccount = TRUE; for ( ModIndex = 0; ModIndex < NETSETUPP_COMP_OBJ_ATTR_COUNT; 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(( "NetpCreateComputerObjectInOU: 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 < NETSETUPP_COMP_OBJ_ATTR_COUNT; Index++ ) { PWSTR *AttribValueRet = NULL; // // Be verbose - output the values returned for each type // NetpLog(( "\t\t%ws =", AttribTypes[Index] )); AttribValueRet = ldap_get_values( Ldap, Entry, AttribTypes[Index] ); if ( AttribValueRet != NULL ) { // // Don't leak sensitive info! // if ( _wcsicmp( AttribTypes[Index], NETSETUPP_UNICODEPWD ) == 0 ) { NetpLog(( " " )); } else { ULONG ValIndex; for ( ValIndex = 0; AttribValueRet[ValIndex] != NULL; ValIndex++ ) { NetpLog(( " %ws", AttribValueRet[ValIndex] )); } } // // Remove those values from the modification which are alredy set // NetpRemoveDuplicateStrings( AttribValueRet, AttribValues[Index] ); 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 ( (AttribFlags[Index] & 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 ( *AttribValues[Index] != NULL ) { Mods[ModIndex] = &ModList[Index]; ModIndex ++; } } // // Terminate the modification list // Mods[ModIndex] = NULL; // // Otherwise, error out // } else { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpCreateComputerObjectInOU: ldap_search_s failed: 0x%lx 0x%lx\n", LdapStatus, NetStatus )); goto Cleanup; } // // If there are no modifications to do // we are done. // if ( ModIndex == 0 ) { NetpLog(( "NetpCreateComputerObjectInOU: There are _NO_ modifications to do\n" )); NetStatus = NERR_Success; goto Cleanup; } // // Be verbose - output the attribute values to be set // NetpLog(( "NetpCreateComputerObjectInOU: 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(( " " )); } 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, ObjectName, Mods ); } else { LdapStatus = ldap_modify_s( Ldap, ObjectName, Mods ); } // // If we tried to create against a server that doesn't support 128 bit encryption, // we get LDAP_UNWILLING_TO_PERFORM back from the server. So, we'll create the // account again without the password, and then reset it using NetApi. // if ( LdapStatus == LDAP_UNWILLING_TO_PERFORM ) { NetpLog(( "NetpCreateComputerObjectInOU: ldap_modify_s (1) returned" " LDAP_UNWILLING_TO_PERFORM. Trying to recover.\n" )); // // If we didn't try to set a password, there is no legitimate reason // for the add to fail. // if ( _wcsicmp( (*(Mods[ModIndex-1])).mod_type, NETSETUPP_UNICODEPWD ) != 0 ) { NetpLog(( "NetpCreateComputerObjectInOU: ldap_modify_s retuned LDAP_UNWILLING_TO_PERFORM" " but password was not being set\n" )); NetStatus = LdapMapErrorToWin32( LdapStatus ); goto Cleanup; } // // If there are other entries besides the password, retry to add them // if ( ModIndex > 1 ) { Mods[ModIndex-1] = NULL; if ( NewAccount ) { LdapStatus = ldap_add_s( Ldap, ObjectName, Mods ); } else{ LdapStatus = ldap_modify_s( Ldap, ObjectName, Mods ); } if ( LdapStatus != LDAP_SUCCESS ) { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpCreateComputerObjectInOU: ldap_modify_s (2) failed: 0x%lx 0x%lx\n", LdapStatus, NetStatus )); goto Cleanup; } } // // Reset the password // NetStatus = NetUserGetInfo( DC, SamAccountName, 1, ( PBYTE * )&CurrentUI1 ); if ( NetStatus == NERR_Success ) { CurrentUI1->usri1_password = MachinePassword; if ( !FLAG_ON( CurrentUI1->usri1_flags, UF_WORKSTATION_TRUST_ACCOUNT ) ) { CurrentUI1->usri1_flags = UF_WORKSTATION_TRUST_ACCOUNT | UF_SCRIPT; } NetStatus = NetUserSetInfo( DC, SamAccountName, 1, ( PBYTE )CurrentUI1, NULL ); if ( NetStatus != NERR_Success ) { NetpLog(( "NetpCreateComputerObjectInOU: NetUserSetInfo failed on '%ws' for '%ws': 0x%lx." " Deleting the account.\n", DC, SamAccountName, NetStatus )); } NetApiBufferFree( CurrentUI1 ); } else { NetpLog(( "NetpCreateComputerObjectInOU: NetUserGetInfo failed on '%ws' for '%ws': 0x%lx.", " Deleting the account.\n", DC, SamAccountName, NetStatus )); } // // Delete the user if it fails // if ( NetStatus != NERR_Success ) { LdapStatus = ldap_delete_s( Ldap, ObjectName ); if ( LdapStatus != LDAP_SUCCESS ) { NetpLog(( "NetpCreateComputerObjectInOU: Failed to delete '%ws': 0x%lx 0x%lx\n", ObjectName, LdapStatus, LdapMapErrorToWin32( LdapStatus ) )); } goto Cleanup; } } else if ( LdapStatus != LDAP_SUCCESS ) { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpCreateComputerObjectInOU: ldap_modify_s (1) failed: 0x%lx 0x%lx\n", LdapStatus, NetStatus )); goto Cleanup; } // // Toggle the account type property. See comment in // NetpSetMachineAccountPasswordAndTypeEx() for an // explanation of why this is needed. (Search for USN). // Mods[0] = NULL; for ( Index = 0; Index < NETSETUPP_COMP_OBJ_ATTR_COUNT; Index++ ) { if ( _wcsicmp( ModList[Index].mod_type, NETSETUPP_USERACCOUNTCONTROL ) == 0 ) { Mods[0] = &ModList[Index]; break; } } Mods[1] = NULL; if ( Mods[0] != NULL ) { // // Disable the account // AccntTypeValues[0] = NETSETUPP_ACCNT_TYPE_DISABLED; LdapStatus = ldap_modify_s( Ldap, ObjectName, Mods ); if ( LdapStatus != LDAP_SUCCESS ) { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpCreateComputerObjectInOU: set UserAccountControl (1) on '%ws' failed: 0x%lx 0x%lx\n", ObjectName, LdapStatus, NetStatus )); goto Cleanup; } // // Re-enable the account // AccntTypeValues[0] = NETSETUPP_ACCNT_TYPE_ENABLED; LdapStatus = ldap_modify_s( Ldap, ObjectName, Mods ); if ( LdapStatus != LDAP_SUCCESS ) { NetStatus = LdapMapErrorToWin32( LdapStatus ); NetpLog(( "NetpCreateComputerObjectInOU: set UserAccountControl (2) on '%ws' failed: 0x%lx 0x%lx\n", ObjectName, LdapStatus, NetStatus )); goto Cleanup; } } else { NetpLog(( "NetpCreateComputerObjectInOU: UserAccountControl was not in the list\n" )); } // // If we've made up to this point, it's success! // NetStatus = NERR_Success; Cleanup: if ( Ldap != NULL ) { NetpLdapUnbind( Ldap ); } if ( ObjectName != NULL ) { NetApiBufferFree( ObjectName ); } if ( SamAccountName != NULL ) { NetApiBufferFree( SamAccountName ); } if ( Message != NULL ) { ldap_msgfree( Message ); } 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. // 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 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; if ( !FLAG_ON( UserAccountControl->UserAccountControl, USER_WORKSTATION_TRUST_ACCOUNT ) ) { fAccountControlModified = TRUE; UserAccountControl->UserAccountControl |= USER_WORKSTATION_TRUST_ACCOUNT; UserAccountControl->UserAccountControl &= ~( USER_INTERDOMAIN_TRUST_ACCOUNT | USER_SERVER_TRUST_ACCOUNT ); } // // 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 NetpRemoveDnsRegistrations ( VOID ) /*++ Routine Description: This function removes DNS registration entries via an entrypoint defined in DHCPCSVC.DLL called DhcpRemoveDNSRegistrations Return Value: NERR_Success -- Success --*/ { HMODULE hModule = NULL; DNS_REGISTRATION_REMOVAL_FN pfn = NULL; hModule = LoadLibraryW( L"dhcpcsvc.dll" ); if ( hModule != NULL ) { pfn = (DNS_REGISTRATION_REMOVAL_FN)GetProcAddress( hModule, "DhcpRemoveDNSRegistrations" ); if ( pfn != NULL ) { ( *pfn )(); } FreeLibrary( hModule ); } return( NERR_Success ); } // // 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 ); } } LONG cNetsetupLogRefCount=0; LONG LogWriterCount=0; HANDLE hDebugLog = NULL; BOOL LogFileInitialized = FALSE; BOOL LogShutdownInProgress = FALSE; DWORD NetpOpenLogThreadId = 0; void NetSetuppOpenLog() { // // Increment the ref count atomically // LONG LocalLogRefCount = InterlockedIncrement( &cNetsetupLogRefCount ); // // If we are the first thread to access the log, // initialize the log and open it // if ( LocalLogRefCount == 1 ) { // // It's possible that the log hasn't been // shut down yet by the last thread leaving // the log, so wait until it is. // while ( LogFileInitialized || LogShutdownInProgress ) { Sleep(100); } NetpInitializeLogFile(); NetpOpenLogThreadId = GetCurrentThreadId(); // // Now open the log and mark the start of the output // hDebugLog = NetpOpenDebugFile( L"NetSetup", FALSE ); LogFileInitialized = TRUE; NetpLog(("-----------------------------------------------------------------\n" )); // // If we are not the first one, it's possible // that the log hasn't been initilized by the // first thread yet, so wait until it is // } else { while ( !LogFileInitialized ) { Sleep(100); } } } void NetSetuppCloseLog() { LONG LocalLogRefCount; // // We can walk into this routine only if // we previously initialized the log // ASSERT( cNetsetupLogRefCount > 0 ); ASSERT( LogFileInitialized == TRUE ); // // Decrement the ref count atomically // LocalLogRefCount = InterlockedDecrement( &cNetsetupLogRefCount ); // // If we are the last thread to close the log, // shut the log down // if ( LocalLogRefCount == 0 ) { // // Inform writers we are shutting the log down // LogShutdownInProgress = TRUE; // // Wait until all writers leave the log // while ( LogWriterCount > 0 ) { Sleep(100); } // // Close the log // NetpCloseDebugFile( hDebugLog ); hDebugLog = NULL; NetpOpenLogThreadId = 0; NetpShutdownLogFile(); LogFileInitialized = FALSE; // // We are done with log shutdown // LogShutdownInProgress = FALSE; } } void NetpLogPrintHelper( IN LPCSTR Format, ...) { va_list arglist; // // Don't go any further if the file isn't ready // if ( !LogFileInitialized || LogShutdownInProgress ) { return; } // // Increment the number of log writers to prevent // the log from shutting down behind our back // InterlockedIncrement( &LogWriterCount ); // // Now that we have our writer reference, // it's safe to write into the log if // it is ready at this point // if ( LogFileInitialized && !LogShutdownInProgress ) { va_start(arglist, Format); NetpLogPrintRoutineVEx(hDebugLog, &NetpOpenLogThreadId, (LPSTR) Format, arglist); va_end(arglist); } // // Remove our reference // InterlockedDecrement( &LogWriterCount ); } 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 NetpDsSetSPN2( IN LPCWSTR szComputerName, IN LPCWSTR szDnsDomainName, IN LPCWSTR szUncDcName, IN LPCWSTR szUser, IN LPCWSTR szUserPassword ) /*++ Routine Description: Arguments: Return Value: ignored - this is a thread pool worker function. --*/ { DWORD NetStatus=NERR_Success; ULONG LdapStatus; LPWSTR Spn = NULL; LPWSTR SpnNetbios = NULL; LPWSTR SamName = NULL; LPWSTR SamAccountName = NULL; HANDLE hDs = NULL; LDAP *hLdap = NULL; PDS_NAME_RESULTW CrackedName = NULL; LPWSTR DnOfAccount; LONG LdapOption; LPWSTR DnsHostName = NULL; LPWSTR DnsHostNameValues[2]; LDAPModW DnsHostNameAttr; LDAPModW *Mods[2]; RPC_AUTH_IDENTITY_HANDLE AuthId = 0; WCHAR szComputerNetbiosName[MAX_COMPUTERNAME_LENGTH+1]; LPWSTR szUserName=NULL; LPWSTR szDomainName=NULL; LPWSTR szNetbiosDomainName=NULL; LPWSTR szT; ULONG uLen; static WCHAR c_szHostPrefix[] = L"HOST/"; NetStatus = NetpSeparateUserAndDomain(szUser, &szUserName, &szDomainName); if (NetStatus == NERR_Success) { NetStatus = DsMakePasswordCredentials(szUserName, szDomainName, szUserPassword, &AuthId); if (NetStatus != NERR_Success) { goto Cleanup; } } // // first build various names // // // build Netbios domain name // uLen = wcslen(szDnsDomainName); NetStatus = NetpDuplicateString(szDnsDomainName, uLen, &szNetbiosDomainName); if (NetStatus == NERR_Success) { if (uLen > MAX_COMPUTERNAME_LENGTH) { szNetbiosDomainName[MAX_COMPUTERNAME_LENGTH] = UNICODE_NULL; } szT = wcschr(szNetbiosDomainName, L'.'); if (szT) { *szT = UNICODE_NULL; } } // // build SamAccountName // NetStatus = NetpGetMachineAccountName(szComputerName, &SamAccountName); if (NetStatus == NERR_Success) { // // build SamName // NetStatus = NetpConcatStrings3(szNetbiosDomainName, -1, L"\\", 1, SamAccountName, -1, &SamName); if (NetStatus == NERR_Success) { wcsncpy(szComputerNetbiosName, szComputerName, MAX_COMPUTERNAME_LENGTH); szComputerNetbiosName[MAX_COMPUTERNAME_LENGTH] = UNICODE_NULL; // // build DnsHostName // NetStatus = NetpConcatStrings3( szComputerNetbiosName, -1, L".", 1, szDnsDomainName, -1, &DnsHostName ); if (NetStatus == NERR_Success) { // // build SPN // NetStatus = NetpConcatStrings(c_szHostPrefix, (sizeof( c_szHostPrefix )/ sizeof(WCHAR) ) - 1, DnsHostName, -1, &Spn); if (NetStatus == NERR_Success) { // // build NebBios SPN // NetStatus = NetpConcatStrings(c_szHostPrefix, (sizeof( c_szHostPrefix )/ sizeof(WCHAR) ) - 1, szComputerNetbiosName, -1, &SpnNetbios); } } } } if (NetStatus != NERR_Success) { goto Cleanup; } // // Bind to the DS on the DC. // NetStatus = DsBindWithCredW(szUncDcName, NULL, AuthId, &hDs); if ( NetStatus != NO_ERROR ) { NetpLog(("NetpDsSetSPN: Unable to bind to DS on '%ws': 0x%lx\n", szUncDcName, NetStatus )); goto Cleanup ; } // // Crack the sam account name into a DN: // NetStatus = DsCrackNamesW( hDs, 0, DS_NT4_ACCOUNT_NAME, DS_FQDN_1779_NAME, 1, &SamName, &CrackedName ); if ( ( NetStatus != NO_ERROR ) || ( CrackedName->cItems != 1 ) ) { NetpLog(( "NetpDsSetSPN: CrackNames failed on '%ws' for '%ws': 0x%lx\n", szUncDcName, SamName, NetStatus )); goto Cleanup ; } if ( CrackedName->rItems[ 0 ].status != 0 ) { //$ kumarp 15-July-1999 // // we dont change NetStatus here thus treat this failure as success // and end up not setting the DnsHostName/SPN // NetpLog(( "NetpDsSetSPN: CrackNames failed on %ws for %ws: substatus 0x%lx\n", szUncDcName, SamName, CrackedName->rItems[ 0 ].status )); goto Cleanup ; } DnOfAccount = CrackedName->rItems[0].pName; NetStatus = NetpLdapBind( (LPWSTR) szUncDcName, (LPWSTR) szUser, (LPWSTR) szUserPassword, &hLdap ); if (NetStatus == NERR_Success) { // // Write the DNS host name // DnsHostNameValues[0] = DnsHostName; DnsHostNameValues[1] = NULL; DnsHostNameAttr.mod_op = LDAP_MOD_REPLACE; DnsHostNameAttr.mod_type = L"DnsHostName"; DnsHostNameAttr.mod_values = DnsHostNameValues; Mods[0] = &DnsHostNameAttr; Mods[1] = NULL; NetpLog (("NetpDsSetSPN: Setting DnsHostName '%ws' on '%ws'\n", DnsHostName, DnOfAccount)); LdapStatus = ldap_modify_sW( hLdap, DnOfAccount, Mods ); if ( LdapStatus != LDAP_SUCCESS ) { NetStatus = LdapMapErrorToWin32(LdapStatus); NetpLog(( "NetpDsSetSPN: ldap_modify_s failed on '%ws' for '%ws': '%s' : 0x%lx\n", szUncDcName, SamName, ldap_err2stringA( LdapStatus ), NetStatus)); goto Cleanup; } } if (NetStatus == NERR_Success) { // // Write the SPN. // NetpLog (("NetpDsSetSPN: Setting SPN '%ws' on '%ws'\n", Spn, DnOfAccount)); NetStatus = DsWriteAccountSpn( hDs, DS_SPN_ADD_SPN_OP, DnOfAccount, 1, &Spn ); if (NetStatus == NERR_Success) { NetpLog (("NetpDsSetSPN: Setting SPN '%ws' on '%ws'\n", SpnNetbios, DnOfAccount)); NetStatus = DsWriteAccountSpn( hDs, DS_SPN_ADD_SPN_OP, DnOfAccount, 1, &SpnNetbios ); } if ( NetStatus != NO_ERROR ) { NetpLog(( "NetpDsSetSPN: DsWriteAccountSpn failed on '%ws' for '%ws': 0x%lx\n", szUncDcName, SamName, NetStatus )); } } Cleanup: if ( hDs ) { DsUnBind( &hDs ); } if ( CrackedName ) { DsFreeNameResultW( CrackedName ); } if ( hLdap != NULL ) { ldap_unbind_s( hLdap ); } if ( AuthId ) { DsFreePasswordCredentials( AuthId ); } NetApiBufferFree( Spn ); NetApiBufferFree( SpnNetbios ); NetApiBufferFree( SamAccountName ); NetApiBufferFree( SamName ); NetApiBufferFree( szNetbiosDomainName ); NetApiBufferFree( szDomainName ); NetApiBufferFree( szUserName ); 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) { SetThreadToken(NULL, hToken); } } NetpLog(( "NetpLsaOpenSecret: status: 0x%lx\n", Status )); return Status; } NET_API_STATUS NET_API_FUNCTION NetpManageMachineSecret2( IN LSA_HANDLE PolicyHandle, OPTIONAL IN LPWSTR lpMachine, IN LPWSTR lpPassword, IN BOOL fDelete, 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( fDelete == FALSE ) { ASSERT( lpPassword ); } UNREFERENCED_PARAMETER( lpMachine ); Status = NetpGetLsaHandle( NULL, PolicyHandle, &LocalPolicy ); // // open/create the secret // if ( NT_SUCCESS( Status ) ) { RtlInitUnicodeString( &Key, L"$MACHINE.ACC" ); RtlInitUnicodeString( &Data, lpPassword ); Status = NetpLsaOpenSecret2( LocalPolicy, &Key, fDelete == NETSETUPP_CREATE ? SECRET_SET_VALUE | SECRET_QUERY_VALUE : DELETE, &SecretHandle ); if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) { if ( fDelete ) { 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 ( fDelete == 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( PolicyHandle, LocalPolicy, pPolicyHandle ); if ( !NT_SUCCESS( Status ) ) { NetpLog(( "NetpManageMachineSecret: '%s' operation failed: 0x%lx\n", fDelete == 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 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 hLsa = NULL, 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 ); RtlZeroMemory( szUserPassword, wcslen( szUserPassword ) * sizeof(WCHAR) ); 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(NULL, 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 ) { NetStatus = NetpGetLsaHandle( NULL, NULL, &hLsa ); if ( NetStatus == NERR_Success ) { NetStatus = NetpManageMachineSecret2( NULL, szMachineName, szMachinePassword, NETSETUPP_CREATE, &hLsa ); LsaClose( hLsa ); } 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 ); } 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 w32time: 0x%lx\n", NetStatus )); } if ( hDll != NULL ) { FreeLibrary( hDll ); } return NetStatus; }