// Copyright (c) 1997-1999 Microsoft Corporation // // Tab state // // 03-31-98 sburns // 10-05-00 jonn changed to CredUIGetPassword #include "headers.hxx" #include "state.hpp" #include "resource.h" #include "cred.hpp" TCHAR const c_szWizardFilename[] = L"netplwiz.dll"; class Settings { public: // default ctor, copy ctor, op=, dtor used void Refresh(); String ComputerDomainDnsName; String DomainName; String FullComputerName; String PolicyDomainDnsName; String ShortComputerName; String NetbiosComputerName; bool SyncDNSNames; bool JoinedToWorkgroup; bool NeedsReboot; }; static State* instance = 0; static bool machineIsDc = false; static bool networkingInstalled = false; static bool policyInEffect = false; static bool mustReboot = false; // no static initialization worries here, as these are rebuilt when the State // instance is constructed/initialized/refreshed. static Settings original; static Settings current; // not static String instances to avoid order of static initialization any // problems static const wchar_t* TCPIP_PARAMS_KEY = L"System\\CurrentControlSet\\Services\\Tcpip\\Parameters"; static const wchar_t* SYNC_VALUE = L"SyncDomainWithMembership"; static const wchar_t* NEW_HOSTNAME_VALUE = L"NV Hostname"; static const wchar_t* NEW_SUFFIX_VALUE = L"NV Domain"; bool readSyncFlag() { bool retval = true; do { RegistryKey key; HRESULT hr = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY); BREAK_ON_FAILED_HRESULT(hr); // default is to sync. DWORD data = 1; hr = key.GetValue(SYNC_VALUE, data); BREAK_ON_FAILED_HRESULT(hr); retval = data ? true : false; } while (0); return retval; } // JonN 1/03/01 106601 // When the dns suffix checkbox is unchecked, // domain join fails with a confusing message // // LevonE: When Join fails with ERROR_DS_COULDNT_UPDATE_SPNS the UI must check // if (HKLM/System/CCS/Services/Tcpip/Parameters/SyncDomainWithMembership // == 0x0 && // HKLM/System/CCS/Services/Tcpip/Parameters/NV Domain // != AD_Domain_To_Be_Joined) bool WarnDnsSuffix( const String& refNewDomainName ) { if (readSyncFlag()) return false; String strNVDomain; RegistryKey key; HRESULT hr2 = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY); if (!SUCCEEDED(hr2)) return false; hr2 = key.GetValue(NEW_SUFFIX_VALUE, strNVDomain); if (!SUCCEEDED(hr2)) return false; return !!strNVDomain.icompare( refNewDomainName ); } HRESULT WriteSyncFlag(HWND dialog, bool flag) { LOG_FUNCTION(WriteSyncFlag); ASSERT(Win::IsWindow(dialog)); HRESULT hr = S_OK; do { RegistryKey key; hr = key.Create(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY); BREAK_ON_FAILED_HRESULT(hr); hr = key.SetValue(SYNC_VALUE, flag ? 1 : 0); BREAK_ON_FAILED_HRESULT(hr); } while (0); if (FAILED(hr)) { popup.Error( dialog, hr, IDS_CHANGE_SYNC_FLAG_FAILED); } return hr; } // returns true if the machine is a domain controller running under ds repair // mode, false if not bool IsDcInDsRepairMode() { LOG_FUNCTION(IsDcInDsRepairMode); // We infer a DC in repair mode if we're told that the machine is a server // and the safe boot option is ds repair, and the real product type is // LanManNT. // // By "real" product type, I mean that which is written in the registry, // not that which is reported by RtlGetNtProductType. The API gets the // result from shared memory which is adjusted at boot to reflect the // ds repair mode (from LanManNt to Server). The registry entry is not // changed by repair mode. // // We have to check both because it is possible to boot a normal server // in ds repair mode. DWORD safeBoot = 0; NT_PRODUCT_TYPE product = NtProductWinNt; HRESULT hr = Computer::GetSafebootOption(HKEY_LOCAL_MACHINE, safeBoot); // don't assert the result: the key may not be present hr = Computer::GetProductTypeFromRegistry(HKEY_LOCAL_MACHINE, product); ASSERT(SUCCEEDED(hr)); if (safeBoot == SAFEBOOT_DSREPAIR and product == NtProductLanManNt) { return true; } return false; } void Settings::Refresh() { LOG_FUNCTION(Settings::Refresh); String unknown = String::load(IDS_UNKNOWN); ComputerDomainDnsName = unknown; DomainName = unknown; FullComputerName = unknown; ShortComputerName = unknown; PolicyDomainDnsName = unknown; SyncDNSNames = readSyncFlag(); JoinedToWorkgroup = true; // CODEWORK: we should reconcile this with the Computer object added // to idpage.cpp DSROLE_PRIMARY_DOMAIN_INFO_BASIC* info = 0; HRESULT hr = MyDsRoleGetPrimaryDomainInformation(0, info); if (SUCCEEDED(hr)) { if (info->DomainNameDns) { DomainName = info->DomainNameDns; } else if (info->DomainNameFlat) { DomainName = info->DomainNameFlat; } // this is the workgroup name iff JoinedToWorkgroup == true switch (info->MachineRole) { case DsRole_RoleBackupDomainController: case DsRole_RolePrimaryDomainController: { machineIsDc = true; JoinedToWorkgroup = false; break; } case DsRole_RoleStandaloneWorkstation: { machineIsDc = false; JoinedToWorkgroup = true; if (DomainName.empty()) { LOG(L"empty domain name, using default WORKGROUP"); DomainName = String::load(IDS_DEFAULT_WORKGROUP); } break; } case DsRole_RoleStandaloneServer: { machineIsDc = false; JoinedToWorkgroup = true; // I wonder if we're really a DC booted in ds repair mode? if (IsDcInDsRepairMode()) { LOG(L"machine is in ds repair mode"); machineIsDc = true; JoinedToWorkgroup = false; // we can't determine the domain name (LSA won't tell // us when running ds repair mode), so we fall back to // unknown. This is better than "WORKGROUP" -- which is // what info contains. DomainName = unknown; } else { if (DomainName.empty()) { LOG(L"empty domain name, using default WORKGROUP"); DomainName = String::load(IDS_DEFAULT_WORKGROUP); } } break; } case DsRole_RoleMemberWorkstation: case DsRole_RoleMemberServer: { machineIsDc = false; JoinedToWorkgroup = false; break; } default: { ASSERT(false); break; } } ::DsRoleFreeMemory(info); } else { popup.Error( Win::GetDesktopWindow(), hr, String::load(IDS_ERROR_READING_MEMBERSHIP)); // fall back to other APIs to fill in the holes as best we can. JoinedToWorkgroup = false; machineIsDc = false; // workstation, server, or DC? (imprescise, but better than a stick // in the eye) NT_PRODUCT_TYPE ntp = NtProductWinNt; BOOLEAN result = ::RtlGetNtProductType(&ntp); if (result) { switch (ntp) { case NtProductWinNt: { break; } case NtProductServer: { break; } case NtProductLanManNt: { machineIsDc = true; break; } default: { ASSERT(false); } } } } networkingInstalled = IsNetworkingInstalled(); bool isTcpInstalled = networkingInstalled && IsTcpIpInstalled(); String activeFullName; NetbiosComputerName = Computer::GetFuturePhysicalNetbiosName(); if (isTcpInstalled) { // When TCP/IP is installed on the computer, then we are interested // in the computer DNS domain name suffix, and the short name is the // computer's DNS hostname. String activeShortName; String futureShortName; String activeDomainName; String futureDomainName; RegistryKey key; HRESULT hr = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY); if (SUCCEEDED(hr)) { // Read these values without checking for failure, as empty string // is ok. activeShortName = key.GetString(L"Hostname"); activeDomainName = key.GetString(L"Domain"); futureShortName = key.GetString(NEW_HOSTNAME_VALUE); ShortComputerName = futureShortName.empty() ? activeShortName : futureShortName; // here, check that the value was successfully read, because // it may not be present. hr = key.GetValue(NEW_SUFFIX_VALUE, futureDomainName); if (SUCCEEDED(hr)) { ComputerDomainDnsName = futureDomainName; } else { ComputerDomainDnsName = activeDomainName; } } // Determine if DNS domain name policy is in effect. This may change // at any moment, asynchronously, so we save the result as a setting. policyInEffect = Computer::IsDnsSuffixPolicyInEffect(PolicyDomainDnsName); // The full computer name is the short name + . + dns domain name // if policy is in effect, the policy dns domain name takes precedence // over the computer's dns domain name. FullComputerName = Computer::ComposeFullDnsComputerName( ShortComputerName, policyInEffect ? PolicyDomainDnsName : ComputerDomainDnsName); activeFullName = Computer::ComposeFullDnsComputerName( activeShortName, policyInEffect ? PolicyDomainDnsName : activeDomainName); } else { // 371944 activeFullName = Computer::GetActivePhysicalNetbiosName(); // when there is no TCP/IP, the short name is the NetBIOS name ShortComputerName = NetbiosComputerName; FullComputerName = ShortComputerName; } // This test does not take into account domain membership changes, as we // have no prior membership info to compare the current membership to. NeedsReboot = activeFullName != FullComputerName; } void State::Delete() { LOG_FUNCTION(State::Delete); delete instance; instance = 0; } State& State::GetInstance() { ASSERT(instance); return *instance; } void State::Init() { LOG_FUNCTION(State::Init); ASSERT(!instance); if (!instance) { instance = new State(); } } void State::Refresh() { LOG_FUNCTION(State::Refresh); State::Delete(); State::Init(); } State::State() { LOG_CTOR(State); original.Refresh(); current = original; } State::~State() { LOG_DTOR(State); } bool State::NeedsReboot() const { return original.NeedsReboot; } bool State::IsMachineDc() const { return machineIsDc; } bool State::IsNetworkingInstalled() const { return networkingInstalled; } String State::GetFullComputerName() const { return current.FullComputerName; } String State::GetDomainName() const { return current.DomainName; } void State::SetDomainName(const String& name) { // LOG_FUNCTION2(State::SetDomainName, name); current.DomainName = name; } bool State::IsMemberOfWorkgroup() const { return current.JoinedToWorkgroup; } void State::SetIsMemberOfWorkgroup(bool yesNo) { current.JoinedToWorkgroup = yesNo; } String State::GetShortComputerName() const { return current.ShortComputerName; } void State::SetShortComputerName(const String& name) { current.ShortComputerName = name; if (!name.empty()) { current.NetbiosComputerName = Dns::HostnameToNetbiosName(name); SetFullComputerName(); } else { // This avoids an assert in Dns::HostnameToNetbiosName and // Computer::ComposeFullDnsComputerName. 119901 current.NetbiosComputerName = name; current.FullComputerName = name; } } bool State::WasShortComputerNameChanged() const { return original.ShortComputerName.icompare(current.ShortComputerName) != 0; } bool State::WasNetbiosComputerNameChanged() const { return original.NetbiosComputerName.icompare(current.NetbiosComputerName) != 0; } String State::GetComputerDomainDnsName() const { return current.ComputerDomainDnsName; } void State::SetComputerDomainDnsName(const String& newName) { current.ComputerDomainDnsName = newName; SetFullComputerName(); } void State::SetFullComputerName() { current.FullComputerName = Computer::ComposeFullDnsComputerName( current.ShortComputerName, policyInEffect ? current.PolicyDomainDnsName : current.ComputerDomainDnsName); } bool State::WasMembershipChanged() const { if (current.DomainName.empty()) { // this can happen when the domain name is not yet set or has been // cleared by the user return true; } return (Dns::CompareNames( original.DomainName, current.DomainName) != DnsNameCompareEqual) // 97064 || original.JoinedToWorkgroup != current.JoinedToWorkgroup; } bool State::ChangesNeedSaving() const { if ( original.ComputerDomainDnsName.icompare( current.ComputerDomainDnsName) != 0 || WasMembershipChanged() || WasShortComputerNameChanged() || SyncDNSNamesWasChanged()) { return true; } return false; } bool State::GetSyncDNSNames() const { return current.SyncDNSNames; } void State::SetSyncDNSNames(bool yesNo) { current.SyncDNSNames = yesNo; } bool State::SyncDNSNamesWasChanged() const { return original.SyncDNSNames != current.SyncDNSNames; } // Prepend the domain name to the user name (making it a fully-qualified name // in the form "domain\username") if the username does not appear to be an // UPN, and the username does not appear to be fully-qualified already. // // domainName - netbios or DNS domain name. // // userName - user account name // // JonN 6/27/01 26151 // Attempting to join domain with username "someone\" gives a cryptic error // returntype becomes HRESULT, userName becomes IN/OUT parameter // HRESULT MassageUserName(const String& domainName, String& userName) { LOG_FUNCTION(MassageUserName); // ASSERT(!userName.empty()); JonN 2/6/01 306520 static const String UPN_DELIMITER(L"@"); if (userName.find(UPN_DELIMITER) != String::npos) { // assume the name is a UPN: foouser@bar.com. This is not // necessarily true, as account names may contain an '@' symbol. // If that's the case, then they had better fully-qualify the name // as domain\foo@bar.... return S_OK; } if (!domainName.empty() && !userName.empty()) { static const String DOMAIN_DELIMITER(L"\\"); size_t pos = userName.find(DOMAIN_DELIMITER); if (pos == String::npos) { userName = domainName + DOMAIN_DELIMITER + userName; } // // JonN 6/27/01 26151 // Attempting to join domain with username "someone\" gives a cryptic error // else if (pos == userName.length() - 1) { return HRESULT_FROM_WIN32(NERR_BadUsername); } } return S_OK; } // Calls NetJoinDomain. The first call specifies the create computer account // flag. If that fails with an access denied error, the call is repeated // without the flag. (This is to cover the case where the domain // administrator may have pre-created the computer account.) // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. // // domain - domain to join. May be the netbios or DNS domain name. // // username - user account to be used. If empty, the currently logged in // user's context is used. // // password - password for the above account. May be empty. HRESULT JoinDomain( HWND dialog, const String& domainName, const String& username, const String& password, const String& computerDomainDnsName, // 106601 bool deferSpn) { LOG_FUNCTION(JoinDomain); ASSERT(!domainName.empty()); ASSERT(Win::IsWindow(dialog)); Win::CursorSetting cursor(IDC_WAIT); // first attempt without create flag in case account was precreated // 105306 DWORD flags = NETSETUP_JOIN_DOMAIN | NETSETUP_DOMAIN_JOIN_IF_JOINED | NETSETUP_ACCT_DELETE; if (deferSpn) { flags |= NETSETUP_DEFER_SPN_SET; } HRESULT hr = MyNetJoinDomain(domainName, username, password, flags); if (FAILED(hr)) { LOG(L"Retry with account create flag"); flags |= NETSETUP_ACCT_CREATE; hr = MyNetJoinDomain(domainName, username, password, flags); } if (SUCCEEDED(hr)) { popup.Info( dialog, String::format( IDS_DOMAIN_WELCOME, domainName.c_str())); HINSTANCE hNetWiz = LoadLibrary(c_szWizardFilename); if (hNetWiz) { HRESULT (*pfnClearAutoLogon)(VOID) = (HRESULT (*)(VOID)) GetProcAddress( hNetWiz, "ClearAutoLogon" ); if (pfnClearAutoLogon) { (*pfnClearAutoLogon)(); } FreeLibrary(hNetWiz); } } else if (hr == Win32ToHresult(ERROR_DISK_FULL)) // 17367 { popup.Error( dialog, String::format(IDS_DISK_FULL, domainName.c_str())); } // JonN 1/03/01 106601 // When the dns suffix checkbox is unchecked, // domain join fails with a confusing message else if (hr == Win32ToHresult(ERROR_DS_COULDNT_UPDATE_SPNS)) // 106601 { bool fWarnDnsSuffix = WarnDnsSuffix(domainName); popup.Error( dialog, String::format( (fWarnDnsSuffix) ? IDS_JOIN_DOMAIN_COULDNT_UPDATE_SPNS_SUFFIX : IDS_JOIN_DOMAIN_COULDNT_UPDATE_SPNS, domainName.c_str(), computerDomainDnsName.c_str())); } else // any other error { popup.Error( dialog, hr, String::format(IDS_JOIN_DOMAIN_FAILED, domainName.c_str())); } return hr; } // Changes the local computer's DNS domain suffix. // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. HRESULT SetDomainDnsName(HWND dialog) { LOG_FUNCTION2(SetDomainDnsName, current.ComputerDomainDnsName); ASSERT(Win::IsWindow(dialog)); HRESULT hr = Win::SetComputerNameEx( ComputerNamePhysicalDnsDomain, current.ComputerDomainDnsName); if (FAILED(hr)) { // 335055 popup.Error( dialog, hr, String::format( IDS_SET_DOMAIN_DNS_NAME_FAILED, current.ComputerDomainDnsName.c_str())); } return hr; } // Changes the local netbios computer name, and, if tcp/ip is installed, // the local DNS hostname. // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. HRESULT SetShortName(HWND dialog) { LOG_FUNCTION2(setShortName, current.ShortComputerName); ASSERT(!current.ShortComputerName.empty()); HRESULT hr = S_OK; bool isTcpInstalled = networkingInstalled && IsTcpIpInstalled(); if (isTcpInstalled) { // also sets the netbios name hr = Win::SetComputerNameEx( ComputerNamePhysicalDnsHostname, current.ShortComputerName); } else { String netbiosName = Dns::HostnameToNetbiosName(current.ShortComputerName); hr = Win::SetComputerNameEx(ComputerNamePhysicalNetBIOS, netbiosName); } // the only reason that this is likely to fail is if the user is not // a local administrator. The other cases are that the machine is // in a hosed state. if (FAILED(hr)) { popup.Error( dialog, hr, String::format( IDS_SHORT_NAME_CHANGE_FAILED, current.ShortComputerName.c_str())); } return hr; } // Returns true if a new netbios computer name has been saved, but the machine // has not yet been rebooted. In other words, true if the netbios computer // name will change on next reboot. 417570 bool ShortComputerNameHasChangedSinceReboot() { LOG_FUNCTION(ShortComputerNameHasChangedSinceReboot()); String active = Computer::GetActivePhysicalNetbiosName(); String future = Computer::GetFuturePhysicalNetbiosName(); return (active != future) ? true : false; } // Return true if all changes were successful, false if not. Called when a // machine is to be joined to a domain, or if a machine is changing membership // from one domain to another. // // workgroup -> domain // domain A -> domain B // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. bool State::DoSaveDomainChange(HWND dialog) { LOG_FUNCTION(State::DoSaveDomainChange); ASSERT(Win::IsWindow(dialog)); String username; String password; if (!RetrieveCredentials(dialog, IDS_JOIN_CREDENTIALS, username, password)) { return false; } HRESULT hr = S_OK; bool result = true; bool joinFailed = false; bool changedSyncFlag = false; bool shortNameNeedsChange = false; bool changedShortName = false; bool dnsSuffixNeedsChange = false; bool changedDnsSuffix = false; bool isTcpInstalled = networkingInstalled && IsTcpIpInstalled(); do { // // JonN 6/27/01 26151 // Attempting to join domain with username "someone\" gives a cryptic error // hr = MassageUserName(current.DomainName, username); if (FAILED(hr)) { break; } // update the sync dns suffix flag, if necessary. We do this before // calling NetJoinDomain, so that it will see the new flag and set the // DNS suffix accordingly. This means if the join fails, we need to // undo the change to the flag. if (original.SyncDNSNames != current.SyncDNSNames) { hr = WriteSyncFlag(dialog, current.SyncDNSNames); if (SUCCEEDED(hr)) { changedSyncFlag = true; } // we don't break on failure, as the flag is less consequential // than the joined state and computer name. } // update NV Hostname and NV Domain, if necessary. This is required // before calling NetJoinDomain in order to fix bugs 31084 and 40496. // If the join fails, then we need to undo this change if ( // short name changed since changes last saved in this session (original.ShortComputerName.icompare( current.ShortComputerName) != 0) or ShortComputerNameHasChangedSinceReboot() ) { shortNameNeedsChange = true; } if (original.ComputerDomainDnsName.icompare( current.ComputerDomainDnsName) != 0 ) { dnsSuffixNeedsChange = true; } // JonN 12/5/00 244762 // NV Domain only applies when TCP/IP is present. if (isTcpInstalled && dnsSuffixNeedsChange) { RegistryKey key; hr = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY, KEY_WRITE); BREAK_ON_FAILED_HRESULT(hr); hr = key.SetValue(NEW_SUFFIX_VALUE, current.ComputerDomainDnsName); BREAK_ON_FAILED_HRESULT(hr); changedDnsSuffix = true; } hr = JoinDomain( dialog, current.DomainName, username, password, current.ComputerDomainDnsName, shortNameNeedsChange); if (FAILED(hr)) { joinFailed = true; // JonN 12/5/00 244762 // If we set NW Domain before the join attempt, // and the join failed, we need to undo that setting now. if (isTcpInstalled && changedDnsSuffix) { RegistryKey key; HRESULT hr2 = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY, KEY_WRITE); ASSERT(SUCCEEDED(hr2)); hr2 = key.SetValue(NEW_SUFFIX_VALUE, original.ComputerDomainDnsName); ASSERT(SUCCEEDED(hr2)); } // don't attempt to save any other changes. If the machine is // already joined to a domain, changing the short name will cause the // netbios machine name to not match the machine account, and the // user will not be able to log in with a domain account. // // If the machine is not already joined to the domain, then it // is possible to change the short name and the dns suffix, and // emit a message that those things were changed even though // the join failed. break; } // At this point, the machine is joined to the new domain. But, it will // have joined with the old netbios computer name. So, if the user has // changed the name, or the name has been changed at all since the last // reboot, then we must rename the machine. // // ever get the feeling that NetJoinDomain is a poor API? if (shortNameNeedsChange) { // short name changed. // JonN 12/5/00 244762 // We don't set NV Hostname until after the join succeeds. if (isTcpInstalled) { RegistryKey key; hr = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY, KEY_WRITE); BREAK_ON_FAILED_HRESULT(hr); hr = key.SetValue(NEW_HOSTNAME_VALUE, current.ShortComputerName); BREAK_ON_FAILED_HRESULT(hr); changedShortName = true; } bool renameFailed = false; hr = MyNetRenameMachineInDomain( // We need to pass the hostname instead of the // netbios name here in order to get the correct DNS hostname // and SPN set on the computer object. See ntraid (ntbug9) // #128204 current.ShortComputerName, username, password, NETSETUP_ACCT_CREATE); if (FAILED(hr)) { renameFailed = true; // JonN 12/5/00 244762 // If we set NV Hostname before the rename attempt, // and the rename failed, we need to undo that setting now. if (isTcpInstalled) { RegistryKey key; HRESULT hr2 = key.Open(HKEY_LOCAL_MACHINE, TCPIP_PARAMS_KEY, KEY_WRITE); ASSERT(SUCCEEDED(hr2)); hr2 = key.SetValue(NEW_HOSTNAME_VALUE, original.ShortComputerName); ASSERT(SUCCEEDED(hr2)); } // don't fail the whole operation 'cause the rename failed. // We make a big noise about how the join worked under the old // name. We need to succeed with the operation as a whole so // the change dialog will close and the joined state of the // machine is refreshed. Otherwise, the change dialog stays up, // the domain name has changed but we don't realize it, so if // the user types a new domain name in the still open change // dialog that is the same as the domain the machine was joined // to (matching the stale state), we don't attempt to join // again. Whew. // JonN 1/03/01 106601 // When the dns suffix checkbox is unchecked, // domain join fails with a confusing message if (hr == Win32ToHresult(ERROR_DS_COULDNT_UPDATE_SPNS)) // 106601 { bool fWarnDnsSuffix = WarnDnsSuffix(current.DomainName); popup.Error( dialog, String::format( (fWarnDnsSuffix) ? IDS_RENAME_JOINED_WITH_OLD_NAME_COULDNT_UPDATE_SPNS_SUFFIX : IDS_RENAME_JOINED_WITH_OLD_NAME_COULDNT_UPDATE_SPNS, current.ShortComputerName.c_str(), current.DomainName.c_str(), original.ShortComputerName.c_str(), current.ComputerDomainDnsName.c_str())); } else { popup.Error( dialog, hr, String::format( IDS_RENAME_FAILED_JOINED_WITH_OLD_NAME, current.ShortComputerName.c_str(), current.DomainName.c_str(), original.ShortComputerName.c_str())); } hr = S_FALSE; } // don't change the hostname if the rename failed, as this will // prevent the user from logging in as the new computer name will not // match the sam account name. if (!renameFailed) // 401355 { // now set the new hostname and netbios name hr = SetShortName(dialog); // this had better work... ASSERT(SUCCEEDED(hr)); } } // NetJoinDomain will change the DNS suffix, if it succeeded. If it // failed, then we shouldn't change the suffix anyway. 421824 // this is only true if the sync flag is true: otherwise, we need to // save the suffix when join succeeds. if ( !current.SyncDNSNames && dnsSuffixNeedsChange && !changedDnsSuffix) { hr = SetDomainDnsName(dialog); // this had better work... ASSERT(SUCCEEDED(hr)); } } while (0); if (joinFailed) { HRESULT hr2 = S_OK; if (changedSyncFlag) { // change the sync flag back to its original state. hr2 = WriteSyncFlag(dialog, original.SyncDNSNames); // if we can't restore the flag (unlikely), then that's just tough // potatos. ASSERT(SUCCEEDED(hr2)); } // JonN 11/27/00 233783 JoinDomain reports its own errors } else if (FAILED(hr)) { popup.Error(dialog, hr, IDS_JOIN_FAILED); } return SUCCEEDED(hr) ? true : false; } // Call NetUnjoinDomain, first with the account delete flag, if that fails // then again without it (which almost always "works"). If the first // attempt fails, but the second succeeds, raise a message to the user // informing him of the orphaned computer account. // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. // // domain - domain to unjoin, i.e. the domain the machine is currently // a member of. // // username - user account to be used. If empty, the currently logged in // user's context is used. // // password - password for the above account. May be empty. HRESULT UnjoinDomain( HWND dialog, const String& domain, const String& username, const String& password) { LOG_FUNCTION(UnjoinDomain); ASSERT(Win::IsWindow(dialog)); ASSERT(!domain.empty()); // username and password may be empty Win::CursorSetting cursor(IDC_WAIT); HRESULT hr = S_OK; do { hr = MyNetUnjoinDomain( username, password, NETSETUP_ACCT_DELETE); if (SUCCEEDED(hr)) { break; } // try again: not trying to delete the computer account. If the // user cancelled the credential dialog from the second attempt, then // this attempt will use the current context. LOG(L"Calling NetUnjoinDomain again, w/o account delete"); hr = MyNetUnjoinDomain( username, password, 0); BREAK_ON_FAILED_HRESULT(hr); // if we make it here, then the attempt to unjoin and remove the // account failed, but the attempt to unjoin and abandon the account // succeeded. So we tell the user about the abandonment, and hope // they feel really guilty about it. // Don't hassle them. They just panic. 95386 LOG( String::format( IDS_COMPUTER_ACCOUNT_ORPHANED, domain.c_str())); // Win::MessageBox( // dialog, // String::format( // IDS_COMPUTER_ACCOUNT_ORPHANED, // domain.c_str()), // String::load(IDS_APP_TITLE), // MB_OK | MB_ICONWARNING); } while (0); if (FAILED(hr)) { popup.Error( dialog, hr, String::format( IDS_UNJOIN_FAILED, domain.c_str())); } return hr; } // Return true if all changes were successful, false if not. Called when the // current domain membership is to be severed, or when changing from one // workgroup to another. // // domain -> workgroup // workgroup A -> workgroup B // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. bool State::DoSaveWorkgroupChange(HWND dialog) { LOG_FUNCTION(State::DoSaveWorkgroupChange); ASSERT(Win::IsWindow(dialog)); HRESULT hr = S_OK; bool result = true; bool unjoinFailed = false; bool changedSyncFlag = false; do { // update the sync dns suffix flag, if the user changed it. Do this // before calling NetUnjoinDomain, which will clear the dns suffix // for us. if (original.SyncDNSNames != current.SyncDNSNames) { hr = WriteSyncFlag(dialog, current.SyncDNSNames); if (FAILED(hr)) { result = false; } else { changedSyncFlag = true; } // we don't break on failure, as the flag is less consequential // than the joined state and computer name. } // only unjoin if we were previously joined to a domain if (!original.JoinedToWorkgroup and networkingInstalled) { // get credentials for removing the computer account String username; String password; if (!RetrieveCredentials(dialog, IDS_UNJOIN_CREDENTIALS, username, password)) { result = false; unjoinFailed = true; break; } // // JonN 6/27/01 26151 // Attempting to join domain with username "someone\" gives a cryptic error // hr = MassageUserName(original.DomainName, username); if (FAILED(hr)) { break; } hr = UnjoinDomain( dialog, original.DomainName, username, password); // Don't try to change anything else, especially the hostname. If // the unjoin failed, and we change the name locally, this will // prevent the user from logging in, as the new computer name will // not match the computer account name in the domain. if (FAILED(hr)) { result = false; unjoinFailed = true; break; } } // join the workgroup hr = MyNetJoinDomain(current.DomainName, String(), String(), 0); if (FAILED(hr)) { // this is extremely unlikely to fail, and if it did, the // workgroup would simply be "WORKGROUP" result = false; popup.Error( dialog, hr, String::format( IDS_JOIN_WORKGROUP_FAILED, current.DomainName.c_str())); break; } popup.Info( dialog, String::format( IDS_WORKGROUP_WELCOME, current.DomainName.c_str())); // change the host name, if the user has changed it. if ( original.ShortComputerName.icompare( current.ShortComputerName) != 0) { hr = SetShortName(dialog); if (FAILED(hr)) { result = false; } } // change the domain name, if the user changed it. if ( original.ComputerDomainDnsName.icompare( current.ComputerDomainDnsName) != 0 ) { hr = SetDomainDnsName(dialog); if (FAILED(hr)) { result = false; } } } while (0); if (unjoinFailed and changedSyncFlag) { // change the sync flag back to its original state. hr = WriteSyncFlag(dialog, original.SyncDNSNames); // if we can't restore the flag (unlikely), then that's just tough // potatos. ASSERT(SUCCEEDED(hr)); } return result; } // Return true if all changes succeeded, false otherwise. Called only // when domain membership is not to be changed. // // dialog - window handle to dialog window to be used as a parent window // for any child dialogs that may need to be raised. bool State::DoSaveNameChange(HWND dialog) { LOG_FUNCTION(State::DoSaveNameChange); ASSERT(Win::IsWindow(dialog)); bool result = true; HRESULT hr = S_OK; do { // change the hostname, if the user has made changes if ( original.ShortComputerName.icompare( current.ShortComputerName) != 0) { if (!original.JoinedToWorkgroup and networkingInstalled) { // machine is joined to a domain -- we need to rename the // machine's domain account String username; String password; if (!RetrieveCredentials(dialog, IDS_RENAME_CREDENTIALS, username, password)) { result = false; break; } // // JonN 6/27/01 26151 // Attempting to join domain with username "someone\" gives a cryptic error // hr = MassageUserName(current.DomainName, username); if (FAILED(hr)) { break; } hr = MyNetRenameMachineInDomain( // We need to pass the full hostname instead of just the // netbios name here in order to get the correct DNS // hostname and SPN set on the computer object. See ntraid // (ntbug9) #128204 current.ShortComputerName, username, password, NETSETUP_ACCT_CREATE); if (FAILED(hr)) { result = false; // JonN 1/03/01 106601 // When the dns suffix checkbox is unchecked, // domain join fails with a confusing message if (hr == Win32ToHresult(ERROR_DS_COULDNT_UPDATE_SPNS)) // 106601 { bool fWarnDnsSuffix = WarnDnsSuffix(current.DomainName); popup.Error( dialog, String::format( (fWarnDnsSuffix) ? IDS_RENAME_COULDNT_UPDATE_SPNS_SUFFIX : IDS_RENAME_COULDNT_UPDATE_SPNS, current.ShortComputerName.c_str(), current.DomainName.c_str(), current.ComputerDomainDnsName.c_str())); } else { popup.Error( dialog, hr, String::format( IDS_RENAME_FAILED, current.ShortComputerName.c_str())); } } // Don't try to change anything else, especially the netbios name. // If the rename failed, and we change the name locally, this will // prevent the user from logging in, as the new computer name will // not match the computer account name in the domain. BREAK_ON_FAILED_HRESULT(hr); } // Set the dns hostname and the netbios name. If we called // NetRenameMachineInDomain, this may redundantly set netbios name // (as NetRenameMachineInDomain calls SetComputerNameEx with the // netbios name). hr = SetShortName(dialog); // Since NetRenameMachineInDomain calls SetComputerNameEx, if that // failed, the rename would also have failed. So our 2nd call to // SetComputerNameEx in SetShortName is almost certain to succeed. // If it does fail, we're not going to attempt to roll back the // rename. if (FAILED(hr)) { result = false; break; } } // update the sync dns suffix flag, if the user changed it if (original.SyncDNSNames != current.SyncDNSNames) { hr = WriteSyncFlag(dialog, current.SyncDNSNames); if (FAILED(hr)) { result = false; } } // change the domain name, if the user changed it. if ( original.ComputerDomainDnsName.icompare( current.ComputerDomainDnsName) != 0 ) { hr = SetDomainDnsName(dialog); if (FAILED(hr)) { result = false; } } } while (0); return result; } bool State::SaveChanges(HWND dialog) { LOG_FUNCTION(State::SaveChanges); ASSERT(Win::IsWindow(dialog)); // Changes to domain membership are made first, then to the computer name. // // workgroup -> domain // domain A -> domain B // if ( (original.JoinedToWorkgroup && !current.JoinedToWorkgroup) || ( !original.JoinedToWorkgroup && !current.JoinedToWorkgroup && original.DomainName.icompare(current.DomainName) != 0) ) { return DoSaveDomainChange(dialog); } // // domain -> workgroup // workgroup A -> workgroup B // else if ( !original.JoinedToWorkgroup && current.JoinedToWorkgroup || original.JoinedToWorkgroup && current.JoinedToWorkgroup && original.DomainName.icompare(current.DomainName) != 0) { return DoSaveWorkgroupChange(dialog); } // // name change only // ASSERT(original.JoinedToWorkgroup == current.JoinedToWorkgroup); ASSERT(original.DomainName == original.DomainName); return DoSaveNameChange(dialog); } void State::SetChangesMadeThisSession(bool yesNo) { LOG_FUNCTION2( State::SetChangesMadeThisSession, yesNo ? L"true" : L"false"); mustReboot = yesNo; } bool State::ChangesMadeThisSession() const { LOG_FUNCTION2( State::ChangesMadeThisSession, mustReboot ? L"true" : L"false"); return mustReboot; } String State::GetNetbiosComputerName() const { return current.NetbiosComputerName; } String State::GetOriginalShortComputerName() const { return original.ShortComputerName; } DSROLE_OPERATION_STATE GetDsRoleChangeState() { LOG_FUNCTION(GetDsRoleChangeState); DSROLE_OPERATION_STATE result = ::DsRoleOperationIdle; DSROLE_OPERATION_STATE_INFO* info = 0; HRESULT hr = MyDsRoleGetPrimaryDomainInformation(0, info); if (SUCCEEDED(hr)) { if (info) { result = info->OperationState; ::DsRoleFreeMemory(info); } } return result; } bool IsUpgradingDc() { LOG_FUNCTION(IsUpgradingDc); bool isUpgrade = false; DSROLE_UPGRADE_STATUS_INFO* info = 0; HRESULT hr = MyDsRoleGetPrimaryDomainInformation(0, info); if (SUCCEEDED(hr)) { isUpgrade = ( (info->OperationState & DSROLE_UPGRADE_IN_PROGRESS) ? true : false ); ::DsRoleFreeMemory(info); } return isUpgrade; } // Evaluate a list of preconditions that must be met before a name change can // be committed. Return a string describing the first unmet condition, or // an empty string if all conditions are met. // // These preconditions are a subset of those checked before enabling the // button to allow the user to enter changes. The conditions not checked here // are those that cannot be changed while the ui is running (logged on as // local admin, machine is DC) // // 389646 String CheckPreconditions() { LOG_FUNCTION(CheckPreconditions); String result; do { // could have started dcpromo after opening netid if (IsDcpromoRunning()) { result = String::load(IDS_PRECHK_DCPROMO_RUNNING); break; } else { // this test is redundant if dcpromo is running, so only perform // it when dcpromo is not running. if (IsUpgradingDc()) { result = String::load(IDS_PRECHK_MUST_COMPLETE_DCPROMO); } } // could have installed cert svc after opening netid NTService certsvc(L"CertSvc"); if (certsvc.IsInstalled()) { // sorry- renaming cert issuers invalidates their certs. result = String::load(IDS_PRECHK_CANT_RENAME_CERT_SVC); } // could have completed dcpromo after opening netid switch (GetDsRoleChangeState()) { case ::DsRoleOperationIdle: { // do nothing break; } case ::DsRoleOperationActive: { // a role change operation is underway result = String::load(IDS_PRECHK_ROLE_CHANGE_IN_PROGRESS); break; } case ::DsRoleOperationNeedReboot: { // a role change has already taken place, need to reboot before // attempting another. result = String::load(IDS_PRECHK_ROLE_CHANGE_NEEDS_REBOOT); break; } default: { ASSERT(false); break; } } if (!result.empty()) { break; } // could have installed/uninstalled networking after opening // netid // re-evaluate this here again, which will be globally visible. networkingInstalled = IsNetworkingInstalled(); State& state = State::GetInstance(); if (!state.IsNetworkingInstalled() && !state.IsMemberOfWorkgroup()) { // domain members need to be able to reach a dc result = String::load(IDS_PRECHK_NETWORKING_NEEDED); } } while (0); return result; }