|
|
// Copyright (C) 2000 Microsoft Corporation
//
// Dynamic DNS detection/diagnostic page
//
// 22 Aug 2000 sburns
#include "headers.hxx"
#include "page.hpp"
#include "DynamicDnsPage.hpp"
#include "DynamicDnsDetailsDialog.hpp"
#include "resource.h"
#include "state.hpp"
DynamicDnsPage::DynamicDnsPage() : DCPromoWizardPage( IDD_DYNAMIC_DNS, IDS_DYNAMIC_DNS_PAGE_TITLE, IDS_DYNAMIC_DNS_PAGE_SUBTITLE), testPassCount(0), diagnosticResultCode(UNEXPECTED_FINDING_SERVER) { LOG_CTOR(DynamicDnsPage);
WSADATA data; DWORD err = ::WSAStartup(MAKEWORD(2,0), &data);
// if winsock startup fails, that's a shame. The gethostbyname will
// not work, but there's not much we can do about that.
ASSERT(!err); }
DynamicDnsPage::~DynamicDnsPage() { LOG_DTOR(DynamicDnsPage);
::WSACleanup(); }
void DynamicDnsPage::ShowButtons(bool shown) { LOG_FUNCTION(DynamicDnsPage::ShowButtons);
int state = shown ? SW_SHOW : SW_HIDE;
Win::ShowWindow(Win::GetDlgItem(hwnd, IDC_RETRY), state); Win::ShowWindow(Win::GetDlgItem(hwnd, IDC_INSTALL_DNS), state); Win::ShowWindow(Win::GetDlgItem(hwnd, IDC_IGNORE), state); }
void DynamicDnsPage::SelectRadioButton(int buttonResId) { // If the order of the buttons changes, then this must be changed. The
// buttons also need to have consecutively numbered res IDs in the tab
// order.
Win::CheckRadioButton(hwnd, IDC_RETRY, IDC_IGNORE, buttonResId); }
void DynamicDnsPage::OnInit() { LOG_FUNCTION(DynamicDnsPage::OnInit);
SelectRadioButton(IDC_IGNORE);
// Hide the radio buttons initially
ShowButtons(false); }
// Adds a trailing '.' to the supplied name if one is not already present.
//
// name - in, name to add a trailing '.' to, if it doesn't already have one.
// If this value is the empty string, then '.' is returned.
String FullyQualifyDnsName(const String& name) { LOG_FUNCTION2(FullyQualifyDnsName, name);
if (name.empty()) { return L"."; }
// needs a trailing dot
if (name[name.length() - 1] != L'.') { return name + L"."; }
// already has a trailing dot
return name; }
// Scans a linked list of DNS_RECORDs, returning a pointer to the first record
// of type SOA, or 0 if no record of that type is in the list.
//
// recordList - in, linked list of DNS_RECORDs, as returned from DnsQuery
DNS_RECORD* FindSoaRecord(DNS_RECORD* recordList) { LOG_FUNCTION(FindSoaRecord); ASSERT(recordList);
DNS_RECORD* result = recordList; while (result) { if (result->wType == DNS_TYPE_SOA) { LOG(L"SOA record found");
break; }
result = result->pNext; }
return result; }
// Returns the textual representation of the IP address for the given server
// name, in the form "xxx.xxx.xxx.xxx", or the empty string if not IP address
// can be determined.
//
// serverName - in, the host name of the server for which to find the IP
// address. If the value is the empty string, then the empty string is
// returned from the function.
String GetIpAddress(const String& serverName) { LOG_FUNCTION2(GetIpAddress, serverName); ASSERT(!serverName.empty());
String result;
do { if (serverName.empty()) { break; }
LOG(L"Calling gethostbyname");
AnsiString ansi; serverName.convert(ansi);
HOSTENT* host = gethostbyname(ansi.c_str()); if (host) { struct in_addr a; memcpy(&a.S_un.S_addr, host->h_addr_list[0], sizeof(a.S_un.S_addr)); result = inet_ntoa(a);
break; }
LOG(String::format(L"WSAGetLastError = 0x%1!0X", WSAGetLastError())); } while (0);
LOG(result);
return result; }
// Find the DNS server that is authoritative for registering the given server
// name, i.e. what server would register the name. Returns NO_ERROR on
// success, or a DNS status code (a win32 error) on failure. On failure, the
// out parameters are all empty strings.
//
// serverName - in, candidate name for registration. This value should not be the
// empty string.
//
// authZone - out, the zone the name would be registered in.
//
// authServer - out, the name of the DNS server that would have the
// registration.
//
// authServerIpAddress - out, textual representation of the IP address of the
// server named by authServer.
DNS_STATUS FindAuthoritativeServer( const String& serverName, String& authZone, String& authServer, String& authServerIpAddress) { LOG_FUNCTION2(FindAuthoritativeServer, serverName); ASSERT(!serverName.empty());
authZone.erase(); authServer.erase(); authServerIpAddress.erase();
// ensure that the server name ends with a "." so that we have a stop
// point for our loop
String currentName = FullyQualifyDnsName(serverName);
DNS_STATUS result = NO_ERROR; DNS_RECORD* queryResults = 0;
while (!currentName.empty()) { result = MyDnsQuery( currentName, DNS_TYPE_SOA, DNS_QUERY_BYPASS_CACHE, queryResults); if ( result == ERROR_TIMEOUT || result == DNS_ERROR_RCODE_SERVER_FAILURE) { // we bail out entirely
LOG(L"failed to find autoritative server.");
break; }
// search for an SOA RR
DNS_RECORD* soaRecord = queryResults ? FindSoaRecord(queryResults) : 0; if (soaRecord) { // collect return values, and we're done.
LOG(L"autoritative server found");
authZone = soaRecord->pName; authServer = soaRecord->Data.SOA.pNamePrimaryServer; authServerIpAddress = GetIpAddress(authServer);
break; }
// no SOA record found.
if (currentName == L".") { // We've run out of names to query. This situation is so unlikely
// that the DNS server would have to be seriously broken to put
// us in this state. So this is almost an assert case.
LOG(L"Root zone reached without finding SOA record!"); result = DNS_ERROR_ZONE_HAS_NO_SOA_RECORD; break; }
// whack off the leftmost label, and iterate again on the parent
// zone.
currentName = Dns::GetParentDomainName(currentName);
MyDnsRecordListFree(queryResults); queryResults = 0; }
MyDnsRecordListFree(queryResults);
LOG(String::format(L"result = %1!08X!", result)); LOG(L"authZone = " + authZone); LOG(L"authServer = " + authServer); LOG(L"authServerIpAddress = " + authServerIpAddress);
return result; }
DNS_STATUS MyDnsUpdateTest(const String& name) { LOG_FUNCTION2(MyDnsUpdateTest, name); ASSERT(!name.empty());
LOG(L"Calling DnsUpdateTest"); LOG( L"hContextHandle : 0"); LOG(String::format(L"pszName : %1", name.c_str())); LOG( L"fOptions : 0"); LOG( L"aipServers : 0");
DNS_STATUS status = ::DnsUpdateTest( 0, const_cast<wchar_t*>(name.c_str()), 0, 0);
LOG(String::format(L"status = %1!08X!", status)); LOG(MyDnsStatusString(status));
return status; }
// Returns result code that corresponds to what messages to be displayed and
// what radio buttons to make available as a result of the diagnostic.
//
// Also returns thru out parameters information to be included in the
// messages.
//
// serverName - in, the name of the domain contoller to be registered.
//
// errorCode - out, the DNS error code (a win32 error) encountered when
// running the diagnostic.
//
// authZone - out, the zone the name would be registered in.
//
// authServer - out, the name of the DNS server that would have the
// registration.
//
// authServerIpAddress - out, textual representation of the IP address of the
// server named by authServer.
DynamicDnsPage::DiagnosticCode DynamicDnsPage::DiagnoseDnsRegistration( const String& serverName, DNS_STATUS& errorCode, String& authZone, String& authServer, String& authServerIpAddress) { LOG_FUNCTION(DynamicDnsPage::DiagnoseDnsRegistration); ASSERT(!serverName.empty());
DiagnosticCode result = UNEXPECTED_FINDING_SERVER; errorCode = FindAuthoritativeServer( serverName, authZone, authServer, authServerIpAddress);
switch (errorCode) { case NO_ERROR: { if (authZone == L".") { // Message 8
LOG(L"authZone is root");
result = ZONE_IS_ROOT; } else { errorCode = MyDnsUpdateTest(serverName);
switch (errorCode) { case DNS_ERROR_RCODE_NO_ERROR: case DNS_ERROR_RCODE_YXDOMAIN: { // Message 1
LOG(L"DNS registration support verified.");
result = SUCCESS; break; } case DNS_ERROR_RCODE_NOT_IMPLEMENTED: case DNS_ERROR_RCODE_REFUSED: { // Message 2
LOG(L"Server does not support update");
result = SERVER_CANT_UPDATE; break; } default: { // Message 3
result = ERROR_TESTING_SERVER; break; } } }
break; } case DNS_ERROR_RCODE_SERVER_FAILURE: { // Message 6
result = ERROR_FINDING_SERVER; break; } case ERROR_TIMEOUT: { // Message 11
result = TIMEOUT; break; } default: { // Message 4
LOG(L"Unexpected error");
result = UNEXPECTED_FINDING_SERVER; break; } }
LOG(String::format(L"DiagnosticCode = %1!x!", result));
return result; }
// do the test, update the text on the page, update the radio buttons
// enabled state, choose a radio button default if neccessary
void DynamicDnsPage::DoDnsTestAndUpdatePage() { LOG_FUNCTION(DynamicDnsPage::DoDnsTestAndUpdatePage);
// this might take a while.
Win::WaitCursor cursor;
State& state = State::GetInstance(); String domain = state.GetNewDomainDNSName();
DNS_STATUS errorCode = 0; String authZone; String authServer; String authServerIpAddress; String serverName = L"_ldap._tcp.dc._msdcs." + domain;
diagnosticResultCode = DiagnoseDnsRegistration( serverName, errorCode, authZone, authServer, authServerIpAddress); ++testPassCount;
String message; int defaultButton = IDC_IGNORE;
switch (diagnosticResultCode) { // Message 1
case SUCCESS: { message = String::load(IDS_DYN_DNS_MESSAGE_SUCCESS); details = String::format( IDS_DYN_DNS_DETAIL_FULL, testPassCount, authServer.c_str(), authServerIpAddress.c_str(), authZone.c_str(), GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str()); helpTopicLink = L""; defaultButton = IDC_IGNORE; ShowButtons(false);
break; }
// Message 2
case SERVER_CANT_UPDATE: { message = String::load(IDS_DYN_DNS_MESSAGE_SERVER_CANT_UPDATE); details = String::format( IDS_DYN_DNS_DETAIL_FULL, testPassCount, authServer.c_str(), authServerIpAddress.c_str(), authZone.c_str(), GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str());
if (Dns::CompareNames(authZone, domain) == DnsNameCompareEqual) { helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message2a.htm"; } else { helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message2b.htm"; } defaultButton = IDC_RETRY; ShowButtons(true);
break; }
// Message 3
case ERROR_TESTING_SERVER: { message = String::load(IDS_DYN_DNS_MESSAGE_ERROR_TESTING_SERVER); details = String::format( IDS_DYN_DNS_DETAIL_FULL, testPassCount, authServer.c_str(), authServerIpAddress.c_str(), authZone.c_str(), GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str()); helpTopicLink = "DNSConcepts.chm::/sag_DNS_tro_dynamic_message3.htm"; defaultButton = IDC_RETRY; ShowButtons(true); break; }
// Message 6
case ERROR_FINDING_SERVER: { ASSERT(authServer.empty()); ASSERT(authZone.empty()); ASSERT(authServerIpAddress.empty());
message = String::load(IDS_DYN_DNS_MESSAGE_ERROR_FINDING_SERVER); details = String::format( IDS_DYN_DNS_DETAIL_SCANT, testPassCount, serverName.c_str(), GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str()); helpTopicLink = "DNSConcepts.chm::/sag_DNS_tro_dynamic_message6.htm"; defaultButton = IDC_INSTALL_DNS; ShowButtons(true); break; }
// Message 8
case ZONE_IS_ROOT: { message = String::load(IDS_DYN_DNS_MESSAGE_ZONE_IS_ROOT); details = String::format( IDS_DYN_DNS_DETAIL_ROOT_ZONE, testPassCount, authServer.c_str(), authServerIpAddress.c_str()); helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message8.htm"; defaultButton = IDC_INSTALL_DNS; ShowButtons(true); break; }
// Message 11
case TIMEOUT: { message = String::load(IDS_DYN_DNS_MESSAGE_TIMEOUT); details = String::format( IDS_DYN_DNS_DETAIL_SCANT, testPassCount, serverName.c_str(), GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str()); helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message11.htm"; defaultButton = IDC_INSTALL_DNS; ShowButtons(true); break; }
// Message 4
case UNEXPECTED_FINDING_SERVER:
// Anything else
default: { #ifdef DBG
ASSERT(authServer.empty()); ASSERT(authZone.empty()); ASSERT(authServerIpAddress.empty());
if (diagnosticResultCode != UNEXPECTED_FINDING_SERVER) { ASSERT(false); } #endif
message = String::load(IDS_DYN_DNS_MESSAGE_UNEXPECTED);
details = String::format( IDS_DYN_DNS_DETAIL_SCANT, testPassCount, serverName.c_str(), GetErrorMessage(Win32ToHresult(errorCode)).c_str(), errorCode, MyDnsStatusString(errorCode).c_str()); helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message4.htm"; defaultButton = IDC_RETRY; ShowButtons(true); break; }
}
Win::SetDlgItemText(hwnd, IDC_MESSAGE, message); Win::SetDlgItemText( hwnd, IDC_TEST_PASS, String::format(IDS_TEST_PASS_COUNT, testPassCount));
// success always forces the ignore option
if (diagnosticResultCode == SUCCESS) { SelectRadioButton(IDC_IGNORE); } else { // On the first pass only, decide what radio button to set. On
// subsequent passes, the user will have had the chance to change the
// button selection, so we don't change his selections.
if (testPassCount == 1) { int button = defaultButton;
ASSERT(diagnosticResultCode != SUCCESS);
// if the test failed, and the wizard is running unattended, then
// consult the answer file for the user's preference in dealing
// with the failure.
if (state.UsingAnswerFile()) { String option = state.GetAnswerFileOption(State::OPTION_AUTO_CONFIG_DNS);
if (option.icompare(State::VALUE_YES) == 0) { button = IDC_INSTALL_DNS; } else { button = IDC_IGNORE; } }
SelectRadioButton(button); } } }
bool DynamicDnsPage::OnSetActive() { LOG_FUNCTION(DynamicDnsPage::OnSetActive);
State& state = State::GetInstance(); State::Operation oper = state.GetOperation();
// these are the only operations for which this page is valid; i.e.
// new domain scenarios
if ( oper == State::FOREST || oper == State::CHILD || oper == State::TREE) { DoDnsTestAndUpdatePage(); }
if ( ( oper != State::FOREST && oper != State::CHILD && oper != State::TREE) || state.RunHiddenUnattended() ) { LOG(L"Planning to Skip DynamicDnsPage");
Wizard& wizard = GetWizard();
if (wizard.IsBacktracking()) { // backup once again
wizard.Backtrack(hwnd); return true; }
int nextPage = Validate(); if (nextPage != -1) { LOG(L"skipping DynamicDnsPage"); wizard.SetNextPageID(hwnd, nextPage); return true; }
state.ClearHiddenWhileUnattended(); }
Win::PropSheet_SetWizButtons( Win::GetParent(hwnd), PSWIZB_BACK | PSWIZB_NEXT);
return true; }
void DumpButtons(HWND dialog) { LOG(String::format(L"retry : (%1)", Win::IsDlgButtonChecked(dialog, IDC_RETRY) ? L"*" : L" ")); LOG(String::format(L"ignore : (%1)", Win::IsDlgButtonChecked(dialog, IDC_IGNORE) ? L"*" : L" ")); LOG(String::format(L"install: (%1)", Win::IsDlgButtonChecked(dialog, IDC_INSTALL_DNS) ? L"*" : L" ")); }
int DynamicDnsPage::Validate() { LOG_FUNCTION(DynamicDnsPage::Validate);
int nextPage = -1;
do { State& state = State::GetInstance(); State::Operation oper = state.GetOperation(); DumpButtons(hwnd);
if ( oper != State::FOREST && oper != State::CHILD && oper != State::TREE) { // by definition valid, as the page does not apply
State::GetInstance().SetAutoConfigureDNS(false); nextPage = IDD_RAS_FIXUP; break; } if ( diagnosticResultCode == SUCCESS || Win::IsDlgButtonChecked(hwnd, IDC_IGNORE)) { // You can go about your business. Move along, move long.
// Force ignore, even if the user previously had encountered a
// failure and chose retry or install DNS. We do this in case the
// user backed up in the wizard and corrected the domain name.
State::GetInstance().SetAutoConfigureDNS(false); nextPage = IDD_RAS_FIXUP; break; }
// if the radio button selection = retry, then do the test over again,
// and stick to this page.
if (Win::IsDlgButtonChecked(hwnd, IDC_RETRY)) { DoDnsTestAndUpdatePage(); break; }
ASSERT(Win::IsDlgButtonChecked(hwnd, IDC_INSTALL_DNS));
State::GetInstance().SetAutoConfigureDNS(true); nextPage = IDD_RAS_FIXUP; break; } while (0);
LOG(String::format(L"nextPage = %1!d!", nextPage));
return nextPage; }
bool DynamicDnsPage::OnWizBack() { LOG_FUNCTION(DynamicDnsPage::OnWizBack);
// make sure we reset the auto-config flag => the only way it gets set
// it on the 'next' button.
State::GetInstance().SetAutoConfigureDNS(false);
return DCPromoWizardPage::OnWizBack(); }
bool DynamicDnsPage::OnCommand( HWND /* windowFrom */ , unsigned controlIdFrom, unsigned code) { bool result = false; switch (controlIdFrom) { case IDC_DETAILS: { if (code == BN_CLICKED) { // bring up the diagnostics details window
DynamicDnsDetailsDialog(details, helpTopicLink).ModalExecute(hwnd); result = true; } break; } default: { // do nothing
break; } }
return result; }
|