Windows-Server-2003/admin/netid/state.cpp

1887 lines
47 KiB
C++

// 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: 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 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;
}