|
|
// 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; 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: [email protected]. 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 EncryptedString& 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; EncryptedString 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 EncryptedString& 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; EncryptedString 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(), EncryptedString(), 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; EncryptedString 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; }
|