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