Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

890 lines
21 KiB

  1. // Copyright (C) 2000 Microsoft Corporation
  2. //
  3. // Dynamic DNS detection/diagnostic page
  4. //
  5. // 22 Aug 2000 sburns
  6. #include "headers.hxx"
  7. #include "page.hpp"
  8. #include "DynamicDnsPage.hpp"
  9. #include "DynamicDnsDetailsDialog.hpp"
  10. #include "resource.h"
  11. #include "state.hpp"
  12. DynamicDnsPage::DynamicDnsPage()
  13. :
  14. DCPromoWizardPage(
  15. IDD_DYNAMIC_DNS,
  16. IDS_DYNAMIC_DNS_PAGE_TITLE,
  17. IDS_DYNAMIC_DNS_PAGE_SUBTITLE),
  18. testPassCount(0),
  19. diagnosticResultCode(UNEXPECTED_FINDING_SERVER)
  20. {
  21. LOG_CTOR(DynamicDnsPage);
  22. WSADATA data;
  23. DWORD err = ::WSAStartup(MAKEWORD(2,0), &data);
  24. // if winsock startup fails, that's a shame. The gethostbyname will
  25. // not work, but there's not much we can do about that.
  26. ASSERT(!err);
  27. }
  28. DynamicDnsPage::~DynamicDnsPage()
  29. {
  30. LOG_DTOR(DynamicDnsPage);
  31. ::WSACleanup();
  32. }
  33. void
  34. DynamicDnsPage::ShowButtons(bool shown)
  35. {
  36. LOG_FUNCTION(DynamicDnsPage::ShowButtons);
  37. int state = shown ? SW_SHOW : SW_HIDE;
  38. Win::ShowWindow(Win::GetDlgItem(hwnd, IDC_RETRY), state);
  39. Win::ShowWindow(Win::GetDlgItem(hwnd, IDC_INSTALL_DNS), state);
  40. Win::ShowWindow(Win::GetDlgItem(hwnd, IDC_IGNORE), state);
  41. }
  42. void
  43. DynamicDnsPage::SelectRadioButton(int buttonResId)
  44. {
  45. // If the order of the buttons changes, then this must be changed. The
  46. // buttons also need to have consecutively numbered res IDs in the tab
  47. // order.
  48. Win::CheckRadioButton(hwnd, IDC_RETRY, IDC_IGNORE, buttonResId);
  49. }
  50. void
  51. DynamicDnsPage::OnInit()
  52. {
  53. LOG_FUNCTION(DynamicDnsPage::OnInit);
  54. SelectRadioButton(IDC_IGNORE);
  55. // Hide the radio buttons initially
  56. ShowButtons(false);
  57. }
  58. // Adds a trailing '.' to the supplied name if one is not already present.
  59. //
  60. // name - in, name to add a trailing '.' to, if it doesn't already have one.
  61. // If this value is the empty string, then '.' is returned.
  62. String
  63. FullyQualifyDnsName(const String& name)
  64. {
  65. LOG_FUNCTION2(FullyQualifyDnsName, name);
  66. if (name.empty())
  67. {
  68. return L".";
  69. }
  70. // needs a trailing dot
  71. if (name[name.length() - 1] != L'.')
  72. {
  73. return name + L".";
  74. }
  75. // already has a trailing dot
  76. return name;
  77. }
  78. // Scans a linked list of DNS_RECORDs, returning a pointer to the first record
  79. // of type SOA, or 0 if no record of that type is in the list.
  80. //
  81. // recordList - in, linked list of DNS_RECORDs, as returned from DnsQuery
  82. DNS_RECORD*
  83. FindSoaRecord(DNS_RECORD* recordList)
  84. {
  85. LOG_FUNCTION(FindSoaRecord);
  86. ASSERT(recordList);
  87. DNS_RECORD* result = recordList;
  88. while (result)
  89. {
  90. if (result->wType == DNS_TYPE_SOA)
  91. {
  92. LOG(L"SOA record found");
  93. break;
  94. }
  95. result = result->pNext;
  96. }
  97. return result;
  98. }
  99. // Returns the textual representation of the IP address for the given server
  100. // name, in the form "xxx.xxx.xxx.xxx", or the empty string if not IP address
  101. // can be determined.
  102. //
  103. // serverName - in, the host name of the server for which to find the IP
  104. // address. If the value is the empty string, then the empty string is
  105. // returned from the function.
  106. String
  107. GetIpAddress(const String& serverName)
  108. {
  109. LOG_FUNCTION2(GetIpAddress, serverName);
  110. ASSERT(!serverName.empty());
  111. String result;
  112. do
  113. {
  114. if (serverName.empty())
  115. {
  116. break;
  117. }
  118. LOG(L"Calling gethostbyname");
  119. AnsiString ansi;
  120. serverName.convert(ansi);
  121. HOSTENT* host = gethostbyname(ansi.c_str());
  122. if (host)
  123. {
  124. struct in_addr a;
  125. memcpy(&a.S_un.S_addr, host->h_addr_list[0], sizeof(a.S_un.S_addr));
  126. result = inet_ntoa(a);
  127. break;
  128. }
  129. LOG(String::format(L"WSAGetLastError = 0x%1!0X", WSAGetLastError()));
  130. }
  131. while (0);
  132. LOG(result);
  133. return result;
  134. }
  135. // Find the DNS server that is authoritative for registering the given server
  136. // name, i.e. what server would register the name. Returns NO_ERROR on
  137. // success, or a DNS status code (a win32 error) on failure. On failure, the
  138. // out parameters are all empty strings.
  139. //
  140. // serverName - in, candidate name for registration. This value should not be the
  141. // empty string.
  142. //
  143. // authZone - out, the zone the name would be registered in.
  144. //
  145. // authServer - out, the name of the DNS server that would have the
  146. // registration.
  147. //
  148. // authServerIpAddress - out, textual representation of the IP address of the
  149. // server named by authServer.
  150. DNS_STATUS
  151. FindAuthoritativeServer(
  152. const String& serverName,
  153. String& authZone,
  154. String& authServer,
  155. String& authServerIpAddress)
  156. {
  157. LOG_FUNCTION2(FindAuthoritativeServer, serverName);
  158. ASSERT(!serverName.empty());
  159. authZone.erase();
  160. authServer.erase();
  161. authServerIpAddress.erase();
  162. // ensure that the server name ends with a "." so that we have a stop
  163. // point for our loop
  164. String currentName = FullyQualifyDnsName(serverName);
  165. DNS_STATUS result = NO_ERROR;
  166. DNS_RECORD* queryResults = 0;
  167. while (!currentName.empty())
  168. {
  169. result =
  170. MyDnsQuery(
  171. currentName,
  172. DNS_TYPE_SOA,
  173. DNS_QUERY_BYPASS_CACHE,
  174. queryResults);
  175. if (
  176. result == ERROR_TIMEOUT
  177. || result == DNS_ERROR_RCODE_SERVER_FAILURE)
  178. {
  179. // we bail out entirely
  180. LOG(L"failed to find autoritative server.");
  181. break;
  182. }
  183. // search for an SOA RR
  184. DNS_RECORD* soaRecord =
  185. queryResults ? FindSoaRecord(queryResults) : 0;
  186. if (soaRecord)
  187. {
  188. // collect return values, and we're done.
  189. LOG(L"autoritative server found");
  190. authZone = soaRecord->pName;
  191. authServer = soaRecord->Data.SOA.pNamePrimaryServer;
  192. authServerIpAddress = GetIpAddress(authServer);
  193. break;
  194. }
  195. // no SOA record found.
  196. if (currentName == L".")
  197. {
  198. // We've run out of names to query. This situation is so unlikely
  199. // that the DNS server would have to be seriously broken to put
  200. // us in this state. So this is almost an assert case.
  201. LOG(L"Root zone reached without finding SOA record!");
  202. result = DNS_ERROR_ZONE_HAS_NO_SOA_RECORD;
  203. break;
  204. }
  205. // whack off the leftmost label, and iterate again on the parent
  206. // zone.
  207. currentName = Dns::GetParentDomainName(currentName);
  208. MyDnsRecordListFree(queryResults);
  209. queryResults = 0;
  210. }
  211. MyDnsRecordListFree(queryResults);
  212. LOG(String::format(L"result = %1!08X!", result));
  213. LOG(L"authZone = " + authZone);
  214. LOG(L"authServer = " + authServer);
  215. LOG(L"authServerIpAddress = " + authServerIpAddress);
  216. return result;
  217. }
  218. DNS_STATUS
  219. MyDnsUpdateTest(const String& name)
  220. {
  221. LOG_FUNCTION2(MyDnsUpdateTest, name);
  222. ASSERT(!name.empty());
  223. LOG(L"Calling DnsUpdateTest");
  224. LOG( L"hContextHandle : 0");
  225. LOG(String::format(L"pszName : %1", name.c_str()));
  226. LOG( L"fOptions : 0");
  227. LOG( L"aipServers : 0");
  228. DNS_STATUS status =
  229. ::DnsUpdateTest(
  230. 0,
  231. const_cast<wchar_t*>(name.c_str()),
  232. 0,
  233. 0);
  234. LOG(String::format(L"status = %1!08X!", status));
  235. LOG(MyDnsStatusString(status));
  236. return status;
  237. }
  238. // Returns result code that corresponds to what messages to be displayed and
  239. // what radio buttons to make available as a result of the diagnostic.
  240. //
  241. // Also returns thru out parameters information to be included in the
  242. // messages.
  243. //
  244. // serverName - in, the name of the domain contoller to be registered.
  245. //
  246. // errorCode - out, the DNS error code (a win32 error) encountered when
  247. // running the diagnostic.
  248. //
  249. // authZone - out, the zone the name would be registered in.
  250. //
  251. // authServer - out, the name of the DNS server that would have the
  252. // registration.
  253. //
  254. // authServerIpAddress - out, textual representation of the IP address of the
  255. // server named by authServer.
  256. DynamicDnsPage::DiagnosticCode
  257. DynamicDnsPage::DiagnoseDnsRegistration(
  258. const String& serverName,
  259. DNS_STATUS& errorCode,
  260. String& authZone,
  261. String& authServer,
  262. String& authServerIpAddress)
  263. {
  264. LOG_FUNCTION(DynamicDnsPage::DiagnoseDnsRegistration);
  265. ASSERT(!serverName.empty());
  266. DiagnosticCode result = UNEXPECTED_FINDING_SERVER;
  267. errorCode =
  268. FindAuthoritativeServer(
  269. serverName,
  270. authZone,
  271. authServer,
  272. authServerIpAddress);
  273. switch (errorCode)
  274. {
  275. case NO_ERROR:
  276. {
  277. if (authZone == L".")
  278. {
  279. // Message 8
  280. LOG(L"authZone is root");
  281. result = ZONE_IS_ROOT;
  282. }
  283. else
  284. {
  285. errorCode = MyDnsUpdateTest(serverName);
  286. switch (errorCode)
  287. {
  288. case DNS_ERROR_RCODE_NO_ERROR:
  289. case DNS_ERROR_RCODE_YXDOMAIN:
  290. {
  291. // Message 1
  292. LOG(L"DNS registration support verified.");
  293. result = SUCCESS;
  294. break;
  295. }
  296. case DNS_ERROR_RCODE_NOT_IMPLEMENTED:
  297. case DNS_ERROR_RCODE_REFUSED:
  298. {
  299. // Message 2
  300. LOG(L"Server does not support update");
  301. result = SERVER_CANT_UPDATE;
  302. break;
  303. }
  304. default:
  305. {
  306. // Message 3
  307. result = ERROR_TESTING_SERVER;
  308. break;
  309. }
  310. }
  311. }
  312. break;
  313. }
  314. case DNS_ERROR_RCODE_SERVER_FAILURE:
  315. {
  316. // Message 6
  317. result = ERROR_FINDING_SERVER;
  318. break;
  319. }
  320. case ERROR_TIMEOUT:
  321. {
  322. // Message 11
  323. result = TIMEOUT;
  324. break;
  325. }
  326. default:
  327. {
  328. // Message 4
  329. LOG(L"Unexpected error");
  330. result = UNEXPECTED_FINDING_SERVER;
  331. break;
  332. }
  333. }
  334. LOG(String::format(L"DiagnosticCode = %1!x!", result));
  335. return result;
  336. }
  337. // do the test, update the text on the page, update the radio buttons
  338. // enabled state, choose a radio button default if neccessary
  339. void
  340. DynamicDnsPage::DoDnsTestAndUpdatePage()
  341. {
  342. LOG_FUNCTION(DynamicDnsPage::DoDnsTestAndUpdatePage);
  343. // this might take a while.
  344. Win::WaitCursor cursor;
  345. State& state = State::GetInstance();
  346. String domain = state.GetNewDomainDNSName();
  347. DNS_STATUS errorCode = 0;
  348. String authZone;
  349. String authServer;
  350. String authServerIpAddress;
  351. String serverName = L"_ldap._tcp.dc._msdcs." + domain;
  352. diagnosticResultCode =
  353. DiagnoseDnsRegistration(
  354. serverName,
  355. errorCode,
  356. authZone,
  357. authServer,
  358. authServerIpAddress);
  359. ++testPassCount;
  360. String message;
  361. int defaultButton = IDC_IGNORE;
  362. switch (diagnosticResultCode)
  363. {
  364. // Message 1
  365. case SUCCESS:
  366. {
  367. message = String::load(IDS_DYN_DNS_MESSAGE_SUCCESS);
  368. details =
  369. String::format(
  370. IDS_DYN_DNS_DETAIL_FULL,
  371. testPassCount,
  372. authServer.c_str(),
  373. authServerIpAddress.c_str(),
  374. authZone.c_str(),
  375. GetErrorMessage(Win32ToHresult(errorCode)).c_str(),
  376. errorCode,
  377. MyDnsStatusString(errorCode).c_str());
  378. helpTopicLink = L"";
  379. defaultButton = IDC_IGNORE;
  380. ShowButtons(false);
  381. break;
  382. }
  383. // Message 2
  384. case SERVER_CANT_UPDATE:
  385. {
  386. message = String::load(IDS_DYN_DNS_MESSAGE_SERVER_CANT_UPDATE);
  387. details =
  388. String::format(
  389. IDS_DYN_DNS_DETAIL_FULL,
  390. testPassCount,
  391. authServer.c_str(),
  392. authServerIpAddress.c_str(),
  393. authZone.c_str(),
  394. GetErrorMessage(Win32ToHresult(errorCode)).c_str(),
  395. errorCode,
  396. MyDnsStatusString(errorCode).c_str());
  397. if (Dns::CompareNames(authZone, domain) == DnsNameCompareEqual)
  398. {
  399. helpTopicLink =
  400. L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message2a.htm";
  401. }
  402. else
  403. {
  404. helpTopicLink =
  405. L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message2b.htm";
  406. }
  407. defaultButton = IDC_RETRY;
  408. ShowButtons(true);
  409. break;
  410. }
  411. // Message 3
  412. case ERROR_TESTING_SERVER:
  413. {
  414. message = String::load(IDS_DYN_DNS_MESSAGE_ERROR_TESTING_SERVER);
  415. details =
  416. String::format(
  417. IDS_DYN_DNS_DETAIL_FULL,
  418. testPassCount,
  419. authServer.c_str(),
  420. authServerIpAddress.c_str(),
  421. authZone.c_str(),
  422. GetErrorMessage(Win32ToHresult(errorCode)).c_str(),
  423. errorCode,
  424. MyDnsStatusString(errorCode).c_str());
  425. helpTopicLink = "DNSConcepts.chm::/sag_DNS_tro_dynamic_message3.htm";
  426. defaultButton = IDC_RETRY;
  427. ShowButtons(true);
  428. break;
  429. }
  430. // Message 6
  431. case ERROR_FINDING_SERVER:
  432. {
  433. ASSERT(authServer.empty());
  434. ASSERT(authZone.empty());
  435. ASSERT(authServerIpAddress.empty());
  436. message = String::load(IDS_DYN_DNS_MESSAGE_ERROR_FINDING_SERVER);
  437. details =
  438. String::format(
  439. IDS_DYN_DNS_DETAIL_SCANT,
  440. testPassCount,
  441. serverName.c_str(),
  442. GetErrorMessage(Win32ToHresult(errorCode)).c_str(),
  443. errorCode,
  444. MyDnsStatusString(errorCode).c_str());
  445. helpTopicLink = "DNSConcepts.chm::/sag_DNS_tro_dynamic_message6.htm";
  446. defaultButton = IDC_INSTALL_DNS;
  447. ShowButtons(true);
  448. break;
  449. }
  450. // Message 8
  451. case ZONE_IS_ROOT:
  452. {
  453. message = String::load(IDS_DYN_DNS_MESSAGE_ZONE_IS_ROOT);
  454. details =
  455. String::format(
  456. IDS_DYN_DNS_DETAIL_ROOT_ZONE,
  457. testPassCount,
  458. authServer.c_str(),
  459. authServerIpAddress.c_str());
  460. helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message8.htm";
  461. defaultButton = IDC_INSTALL_DNS;
  462. ShowButtons(true);
  463. break;
  464. }
  465. // Message 11
  466. case TIMEOUT:
  467. {
  468. message = String::load(IDS_DYN_DNS_MESSAGE_TIMEOUT);
  469. details =
  470. String::format(
  471. IDS_DYN_DNS_DETAIL_SCANT,
  472. testPassCount,
  473. serverName.c_str(),
  474. GetErrorMessage(Win32ToHresult(errorCode)).c_str(),
  475. errorCode,
  476. MyDnsStatusString(errorCode).c_str());
  477. helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message11.htm";
  478. defaultButton = IDC_INSTALL_DNS;
  479. ShowButtons(true);
  480. break;
  481. }
  482. // Message 4
  483. case UNEXPECTED_FINDING_SERVER:
  484. // Anything else
  485. default:
  486. {
  487. #ifdef DBG
  488. ASSERT(authServer.empty());
  489. ASSERT(authZone.empty());
  490. ASSERT(authServerIpAddress.empty());
  491. if (diagnosticResultCode != UNEXPECTED_FINDING_SERVER)
  492. {
  493. ASSERT(false);
  494. }
  495. #endif
  496. message = String::load(IDS_DYN_DNS_MESSAGE_UNEXPECTED);
  497. details =
  498. String::format(
  499. IDS_DYN_DNS_DETAIL_SCANT,
  500. testPassCount,
  501. serverName.c_str(),
  502. GetErrorMessage(Win32ToHresult(errorCode)).c_str(),
  503. errorCode,
  504. MyDnsStatusString(errorCode).c_str());
  505. helpTopicLink = L"DNSConcepts.chm::/sag_DNS_tro_dynamic_message4.htm";
  506. defaultButton = IDC_RETRY;
  507. ShowButtons(true);
  508. break;
  509. }
  510. }
  511. Win::SetDlgItemText(hwnd, IDC_MESSAGE, message);
  512. Win::SetDlgItemText(
  513. hwnd,
  514. IDC_TEST_PASS,
  515. String::format(IDS_TEST_PASS_COUNT, testPassCount));
  516. // success always forces the ignore option
  517. if (diagnosticResultCode == SUCCESS)
  518. {
  519. SelectRadioButton(IDC_IGNORE);
  520. }
  521. else
  522. {
  523. // On the first pass only, decide what radio button to set. On
  524. // subsequent passes, the user will have had the chance to change the
  525. // button selection, so we don't change his selections.
  526. if (testPassCount == 1)
  527. {
  528. int button = defaultButton;
  529. ASSERT(diagnosticResultCode != SUCCESS);
  530. // if the test failed, and the wizard is running unattended, then
  531. // consult the answer file for the user's preference in dealing
  532. // with the failure.
  533. if (state.UsingAnswerFile())
  534. {
  535. String option =
  536. state.GetAnswerFileOption(State::OPTION_AUTO_CONFIG_DNS);
  537. if (option.icompare(State::VALUE_YES) == 0)
  538. {
  539. button = IDC_INSTALL_DNS;
  540. }
  541. else
  542. {
  543. button = IDC_IGNORE;
  544. }
  545. }
  546. SelectRadioButton(button);
  547. }
  548. }
  549. }
  550. bool
  551. DynamicDnsPage::OnSetActive()
  552. {
  553. LOG_FUNCTION(DynamicDnsPage::OnSetActive);
  554. State& state = State::GetInstance();
  555. State::Operation oper = state.GetOperation();
  556. // these are the only operations for which this page is valid; i.e.
  557. // new domain scenarios
  558. if (
  559. oper == State::FOREST
  560. || oper == State::CHILD
  561. || oper == State::TREE)
  562. {
  563. DoDnsTestAndUpdatePage();
  564. }
  565. if (
  566. ( oper != State::FOREST
  567. && oper != State::CHILD
  568. && oper != State::TREE)
  569. || state.RunHiddenUnattended() )
  570. {
  571. LOG(L"Planning to Skip DynamicDnsPage");
  572. Wizard& wizard = GetWizard();
  573. if (wizard.IsBacktracking())
  574. {
  575. // backup once again
  576. wizard.Backtrack(hwnd);
  577. return true;
  578. }
  579. int nextPage = Validate();
  580. if (nextPage != -1)
  581. {
  582. LOG(L"skipping DynamicDnsPage");
  583. wizard.SetNextPageID(hwnd, nextPage);
  584. return true;
  585. }
  586. state.ClearHiddenWhileUnattended();
  587. }
  588. Win::PropSheet_SetWizButtons(
  589. Win::GetParent(hwnd),
  590. PSWIZB_BACK | PSWIZB_NEXT);
  591. return true;
  592. }
  593. void
  594. DumpButtons(HWND dialog)
  595. {
  596. LOG(String::format(L"retry : (%1)", Win::IsDlgButtonChecked(dialog, IDC_RETRY) ? L"*" : L" "));
  597. LOG(String::format(L"ignore : (%1)", Win::IsDlgButtonChecked(dialog, IDC_IGNORE) ? L"*" : L" "));
  598. LOG(String::format(L"install: (%1)", Win::IsDlgButtonChecked(dialog, IDC_INSTALL_DNS) ? L"*" : L" "));
  599. }
  600. int
  601. DynamicDnsPage::Validate()
  602. {
  603. LOG_FUNCTION(DynamicDnsPage::Validate);
  604. int nextPage = -1;
  605. do
  606. {
  607. State& state = State::GetInstance();
  608. State::Operation oper = state.GetOperation();
  609. DumpButtons(hwnd);
  610. if (
  611. oper != State::FOREST
  612. && oper != State::CHILD
  613. && oper != State::TREE)
  614. {
  615. // by definition valid, as the page does not apply
  616. State::GetInstance().SetAutoConfigureDNS(false);
  617. nextPage = IDD_RAS_FIXUP;
  618. break;
  619. }
  620. if (
  621. diagnosticResultCode == SUCCESS
  622. || Win::IsDlgButtonChecked(hwnd, IDC_IGNORE))
  623. {
  624. // You can go about your business. Move along, move long.
  625. // Force ignore, even if the user previously had encountered a
  626. // failure and chose retry or install DNS. We do this in case the
  627. // user backed up in the wizard and corrected the domain name.
  628. State::GetInstance().SetAutoConfigureDNS(false);
  629. nextPage = IDD_RAS_FIXUP;
  630. break;
  631. }
  632. // if the radio button selection = retry, then do the test over again,
  633. // and stick to this page.
  634. if (Win::IsDlgButtonChecked(hwnd, IDC_RETRY))
  635. {
  636. DoDnsTestAndUpdatePage();
  637. break;
  638. }
  639. ASSERT(Win::IsDlgButtonChecked(hwnd, IDC_INSTALL_DNS));
  640. State::GetInstance().SetAutoConfigureDNS(true);
  641. nextPage = IDD_RAS_FIXUP;
  642. break;
  643. }
  644. while (0);
  645. LOG(String::format(L"nextPage = %1!d!", nextPage));
  646. return nextPage;
  647. }
  648. bool
  649. DynamicDnsPage::OnWizBack()
  650. {
  651. LOG_FUNCTION(DynamicDnsPage::OnWizBack);
  652. // make sure we reset the auto-config flag => the only way it gets set
  653. // it on the 'next' button.
  654. State::GetInstance().SetAutoConfigureDNS(false);
  655. return DCPromoWizardPage::OnWizBack();
  656. }
  657. bool
  658. DynamicDnsPage::OnCommand(
  659. HWND /* windowFrom */ ,
  660. unsigned controlIdFrom,
  661. unsigned code)
  662. {
  663. bool result = false;
  664. switch (controlIdFrom)
  665. {
  666. case IDC_DETAILS:
  667. {
  668. if (code == BN_CLICKED)
  669. {
  670. // bring up the diagnostics details window
  671. DynamicDnsDetailsDialog(details, helpTopicLink).ModalExecute(hwnd);
  672. result = true;
  673. }
  674. break;
  675. }
  676. default:
  677. {
  678. // do nothing
  679. break;
  680. }
  681. }
  682. return result;
  683. }