573 lines
12 KiB
C++
573 lines
12 KiB
C++
// Copyright (c) 1997-1999 Microsoft Corporation
|
|
//
|
|
// get credentials page
|
|
//
|
|
// 12-22-97 sburns
|
|
|
|
|
|
|
|
#include "headers.hxx"
|
|
#include "page.hpp"
|
|
#include "CredentialsPage.hpp"
|
|
#include "resource.h"
|
|
#include "state.hpp"
|
|
#include "ds.hpp"
|
|
#include "CredentialUiHelpers.hpp"
|
|
#include "common.hpp"
|
|
#include <DiagnoseDcNotFound.hpp>
|
|
|
|
|
|
|
|
CredentialsPage::CredentialsPage()
|
|
:
|
|
DCPromoWizardPage(
|
|
IDD_GET_CREDENTIALS,
|
|
IDS_CREDENTIALS_PAGE_TITLE,
|
|
IDS_CREDENTIALS_PAGE_SUBTITLE),
|
|
readAnswerfile(false)
|
|
{
|
|
LOG_CTOR(CredentialsPage);
|
|
|
|
CredUIInitControls();
|
|
}
|
|
|
|
|
|
|
|
CredentialsPage::~CredentialsPage()
|
|
{
|
|
LOG_DTOR(CredentialsPage);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CredentialsPage::OnInit()
|
|
{
|
|
LOG_FUNCTION(CredentialsPage::OnInit);
|
|
|
|
HWND hwndCred = Win::GetDlgItem(hwnd, IDC_CRED);
|
|
Credential_SetUserNameMaxChars(hwndCred, DS::MAX_USER_NAME_LENGTH);
|
|
Credential_SetPasswordMaxChars(hwndCred, DS::MAX_PASSWORD_LENGTH);
|
|
|
|
// Only use the smartcard flag when the machine is joined to a domain. On a
|
|
// standalone machine, the smartcard won't have access to any domain
|
|
// authority to authenticate it.
|
|
// NTRAID#NTBUG9-287538-2001/01/23-sburns
|
|
|
|
State& state = State::GetInstance();
|
|
Computer& computer = state.GetComputer();
|
|
|
|
DWORD flags = CRS_NORMAL | CRS_USERNAMES;
|
|
if (
|
|
computer.IsJoinedToDomain()
|
|
|
|
// can only use smartcards on replica promotions
|
|
// NTRAID#NTBUG9-311150-2001/02/19-sburns
|
|
|
|
&& state.GetOperation() == State::REPLICA)
|
|
{
|
|
flags |= CRS_SMARTCARDS;
|
|
}
|
|
Credential_InitStyle(hwndCred, flags);
|
|
|
|
Win::Edit_LimitText(
|
|
Win::GetDlgItem(hwnd, IDC_DOMAIN),
|
|
Dns::MAX_NAME_LENGTH);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
CredentialsPage::Enable()
|
|
{
|
|
// LOG_FUNCTION(CredentialsPage::Enable);
|
|
|
|
int next =
|
|
( !CredUi::GetUsername(Win::GetDlgItem(hwnd, IDC_CRED)).empty()
|
|
&& !Win::GetTrimmedDlgItemText(hwnd, IDC_DOMAIN).empty() )
|
|
? PSWIZB_NEXT : 0;
|
|
|
|
Win::PropSheet_SetWizButtons(
|
|
Win::GetParent(hwnd),
|
|
PSWIZB_BACK | next);
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
CredentialsPage::OnCommand(
|
|
HWND /* windowFrom */ ,
|
|
unsigned controlIDFrom,
|
|
unsigned code)
|
|
{
|
|
// LOG_FUNCTION(CredentialsPage::OnCommand);
|
|
|
|
switch (controlIDFrom)
|
|
{
|
|
case IDC_CRED:
|
|
{
|
|
if (code == CRN_USERNAMECHANGE)
|
|
{
|
|
SetChanged(controlIDFrom);
|
|
Enable();
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
case IDC_DOMAIN:
|
|
{
|
|
if (code == EN_CHANGE)
|
|
{
|
|
SetChanged(controlIDFrom);
|
|
|
|
Enable();
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
// do nothing
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
CredentialsPage::ShouldSkipPage()
|
|
{
|
|
LOG_FUNCTION(CredentialsPage::ShouldSkipPage);
|
|
|
|
State& state = State::GetInstance();
|
|
State::Operation oper = state.GetOperation();
|
|
|
|
bool result = false;
|
|
|
|
switch (oper)
|
|
{
|
|
case State::FOREST:
|
|
{
|
|
// never need credentials for new forest.
|
|
|
|
result = true;
|
|
break;
|
|
}
|
|
case State::DEMOTE:
|
|
{
|
|
// The demote page should circumvent this page if necessary, so if
|
|
// we make it here, don't skip the page.
|
|
|
|
break;
|
|
}
|
|
case State::ABORT_BDC_UPGRADE:
|
|
case State::REPLICA:
|
|
case State::TREE:
|
|
case State::CHILD:
|
|
{
|
|
break;
|
|
}
|
|
case State::NONE:
|
|
default:
|
|
{
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
CredentialsPage::DetermineNextPage()
|
|
{
|
|
LOG_FUNCTION(CredentialsPage::DetermineNextPage);
|
|
|
|
State& state = State::GetInstance();
|
|
|
|
int id = IDD_PATHS;
|
|
switch (state.GetOperation())
|
|
{
|
|
case State::DEMOTE:
|
|
case State::ABORT_BDC_UPGRADE:
|
|
{
|
|
id = IDD_ADMIN_PASSWORD;
|
|
break;
|
|
}
|
|
case State::FOREST:
|
|
{
|
|
id = IDD_NEW_FOREST;
|
|
break;
|
|
}
|
|
case State::REPLICA:
|
|
{
|
|
if (state.GetRunContext() == State::BDC_UPGRADE)
|
|
{
|
|
id = IDD_PATHS;
|
|
}
|
|
else
|
|
{
|
|
id = IDD_REPLICA;
|
|
}
|
|
break;
|
|
}
|
|
case State::TREE:
|
|
{
|
|
id = IDD_NEW_TREE;
|
|
break;
|
|
}
|
|
case State::CHILD:
|
|
{
|
|
id = IDD_NEW_CHILD;
|
|
break;
|
|
}
|
|
case State::NONE:
|
|
default:
|
|
{
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
|
|
|
|
String
|
|
GetMessage()
|
|
{
|
|
String message;
|
|
State& state = State::GetInstance();
|
|
|
|
switch (state.GetOperation())
|
|
{
|
|
case State::ABORT_BDC_UPGRADE:
|
|
{
|
|
message = String::load(IDS_ABORT_BDC_UPGRADE_CREDENTIALS);
|
|
break;
|
|
}
|
|
case State::TREE:
|
|
case State::CHILD:
|
|
case State::REPLICA:
|
|
{
|
|
message = String::load(IDS_PROMOTION_CREDENTIALS);
|
|
break;
|
|
}
|
|
case State::DEMOTE:
|
|
{
|
|
// 318736 demote requires enterprise admin credentials -- for
|
|
// root and child domains alike.
|
|
|
|
message =
|
|
String::format(
|
|
IDS_ROOT_DOMAIN_CREDENTIALS,
|
|
state.GetComputer().GetForestDnsName().c_str());
|
|
break;
|
|
}
|
|
case State::FOREST:
|
|
{
|
|
// do nothing, the page will be skipped.
|
|
|
|
break;
|
|
}
|
|
case State::NONE:
|
|
default:
|
|
{
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
|
|
|
|
String
|
|
DefaultUserDomainName()
|
|
{
|
|
String d;
|
|
State& state = State::GetInstance();
|
|
const Computer& computer = state.GetComputer();
|
|
|
|
switch (state.GetOperation())
|
|
{
|
|
case State::ABORT_BDC_UPGRADE:
|
|
case State::FOREST:
|
|
{
|
|
// do nothing
|
|
|
|
break;
|
|
}
|
|
case State::DEMOTE: // 301361
|
|
case State::TREE:
|
|
{
|
|
d = computer.GetForestDnsName();
|
|
break;
|
|
}
|
|
case State::CHILD:
|
|
{
|
|
d = computer.GetDomainDnsName();
|
|
break;
|
|
}
|
|
case State::REPLICA:
|
|
{
|
|
d = computer.GetDomainDnsName();
|
|
if (d.empty() && state.ReplicateFromMedia())
|
|
{
|
|
d = state.GetReplicaDomainDNSName();
|
|
}
|
|
break;
|
|
}
|
|
case State::NONE:
|
|
default:
|
|
{
|
|
ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return d;
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
CredentialsPage::OnSetActive()
|
|
{
|
|
LOG_FUNCTION(CredentialsPage::OnSetActive);
|
|
|
|
Win::WaitCursor cursor;
|
|
|
|
State& state = State::GetInstance();
|
|
Wizard& wiz = GetWizard();
|
|
HWND hwndCred = Win::GetDlgItem(hwnd, IDC_CRED);
|
|
|
|
if (ShouldSkipPage())
|
|
{
|
|
LOG(L"skipping CredentialsPage");
|
|
|
|
if (wiz.IsBacktracking())
|
|
{
|
|
// backup once again
|
|
|
|
wiz.Backtrack(hwnd);
|
|
}
|
|
else
|
|
{
|
|
wiz.SetNextPageID(hwnd, DetermineNextPage());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
if (!readAnswerfile && state.UsingAnswerFile())
|
|
{
|
|
CredUi::SetUsername(
|
|
hwndCred,
|
|
state.GetAnswerFileOption(State::OPTION_USERNAME));
|
|
CredUi::SetPassword(
|
|
hwndCred,
|
|
state.GetEncodedAnswerFileOption(State::OPTION_PASSWORD));
|
|
|
|
String domain = state.GetAnswerFileOption(State::OPTION_USER_DOMAIN);
|
|
if (domain.empty())
|
|
{
|
|
domain = DefaultUserDomainName();
|
|
}
|
|
Win::SetDlgItemText(
|
|
hwnd,
|
|
IDC_DOMAIN,
|
|
domain);
|
|
|
|
readAnswerfile = true;
|
|
}
|
|
|
|
if (state.RunHiddenUnattended())
|
|
{
|
|
int nextPage = CredentialsPage::Validate();
|
|
if (nextPage != -1)
|
|
{
|
|
wiz.SetNextPageID(hwnd, nextPage);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
state.ClearHiddenWhileUnattended();
|
|
}
|
|
}
|
|
|
|
// use the credentials last entered (for browsing, etc.)
|
|
|
|
CredUi::SetUsername(hwndCred, state.GetUsername());
|
|
CredUi::SetPassword(hwndCred, state.GetPassword());
|
|
|
|
Win::SetDlgItemText(hwnd, IDC_DOMAIN, state.GetUserDomainName());
|
|
|
|
Win::PropSheet_SetWizButtons(
|
|
Win::GetParent(hwnd),
|
|
PSWIZB_BACK);
|
|
Enable();
|
|
|
|
Win::SetDlgItemText(hwnd, IDC_MESSAGE, GetMessage());
|
|
|
|
if (Win::GetTrimmedDlgItemText(hwnd, IDC_DOMAIN).empty())
|
|
{
|
|
// supply a default domain if none already present
|
|
|
|
Win::SetDlgItemText(hwnd, IDC_DOMAIN, DefaultUserDomainName());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
int
|
|
CredentialsPage::Validate()
|
|
{
|
|
LOG_FUNCTION(CredentialsPage::Validate);
|
|
|
|
int nextPage = -1;
|
|
|
|
do
|
|
{
|
|
if (
|
|
!WasChanged(IDC_CRED)
|
|
&& !WasChanged(IDC_DOMAIN))
|
|
{
|
|
// nothing changed => nothing to validate
|
|
|
|
nextPage = DetermineNextPage();
|
|
break;
|
|
}
|
|
|
|
State& state = State::GetInstance();
|
|
|
|
HWND hwndCred = Win::GetDlgItem(hwnd, IDC_CRED);
|
|
String username = CredUi::GetUsername(hwndCred);
|
|
|
|
if (username.empty())
|
|
{
|
|
popup.Gripe(hwnd, IDC_CRED, IDS_MUST_ENTER_USERNAME);
|
|
break;
|
|
}
|
|
|
|
String domain = Win::GetTrimmedDlgItemText(hwnd, IDC_DOMAIN);
|
|
if (domain.empty())
|
|
{
|
|
popup.Gripe(hwnd, IDC_DOMAIN, IDS_MUST_ENTER_USER_DOMAIN);
|
|
break;
|
|
}
|
|
|
|
Win::WaitCursor cursor;
|
|
|
|
// the domain must be an NT 5 domain: no user of a downlevel domain
|
|
// could perform operations in an NT 5 forest. We get the forest name
|
|
// of the domain ('cause that may be useful for the new tree scenario)
|
|
// as a means of validating the domain name. If the domain does not
|
|
// exist, or is not an NT5 domain, then this call will fail.
|
|
|
|
String forest = GetForestName(domain);
|
|
if (forest.empty())
|
|
{
|
|
ShowDcNotFoundErrorDialog(
|
|
hwnd,
|
|
IDC_DOMAIN,
|
|
domain,
|
|
String::load(IDS_WIZARD_TITLE),
|
|
String::format(IDS_DC_NOT_FOUND, domain.c_str()),
|
|
false);
|
|
break;
|
|
}
|
|
|
|
if (state.GetOperation() == State::TREE)
|
|
{
|
|
// For the new tree case, we need to validate the forest name (a dns
|
|
// domain name) by ensuring that we can find a writable DS DC in that
|
|
// domain. The user may have supplied a netbios domain name, and it
|
|
// is possible that the domain's DNS registration is broken. Since
|
|
// we will use the forest name as the parent domain name in the call
|
|
// to DsRoleDcAsDc, we need to make sure we can find DCs with that
|
|
// name. 122886
|
|
|
|
DOMAIN_CONTROLLER_INFO* info = 0;
|
|
HRESULT hr =
|
|
MyDsGetDcName(
|
|
0,
|
|
forest,
|
|
|
|
// force discovery to ensure that we don't pick up a cached
|
|
// entry for a domain that may no longer exist, writeable
|
|
// and DS because we happen to know that's what the
|
|
// DsRoleDcAsDc API will require.
|
|
|
|
DS_FORCE_REDISCOVERY
|
|
| DS_WRITABLE_REQUIRED
|
|
| DS_DIRECTORY_SERVICE_REQUIRED,
|
|
info);
|
|
if (FAILED(hr) || !info)
|
|
{
|
|
ShowDcNotFoundErrorDialog(
|
|
hwnd,
|
|
IDC_DOMAIN,
|
|
forest,
|
|
String::load(IDS_WIZARD_TITLE),
|
|
String::format(IDS_DC_FOR_ROOT_NOT_FOUND, forest.c_str()),
|
|
|
|
// we know the name can't be netbios: forest names are always
|
|
// DNS names
|
|
|
|
true);
|
|
break;
|
|
}
|
|
|
|
::NetApiBufferFree(info);
|
|
}
|
|
|
|
state.SetUserForestName(forest);
|
|
|
|
// set these now so we can read the domain topology
|
|
|
|
state.SetUsername(username);
|
|
state.SetPassword(CredUi::GetPassword(hwndCred));
|
|
state.SetUserDomainName(domain);
|
|
|
|
// cache the domain topology: this is used to validate new tree,
|
|
// child, and replica domain names in later pages. It's also a
|
|
// pretty good validation of the credentials.
|
|
|
|
HRESULT hr = state.ReadDomains();
|
|
if (FAILED(hr))
|
|
{
|
|
popup.Gripe(
|
|
hwnd,
|
|
IDC_DOMAIN,
|
|
hr,
|
|
String::format(IDS_UNABLE_TO_READ_FOREST));
|
|
break;
|
|
}
|
|
|
|
// valid
|
|
|
|
ClearChanges();
|
|
|
|
nextPage = DetermineNextPage();
|
|
}
|
|
while (0);
|
|
|
|
return nextPage;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|