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.

797 lines
18 KiB

  1. // Copyright (C) 2000 Microsoft Corporation
  2. //
  3. // Non-domain Naming Context checking code
  4. //
  5. // 13 July 2000 sburns, from code supplied by jeffparh
  6. #include "headers.hxx"
  7. #include "state.hpp"
  8. #include "resource.h"
  9. #include "NonDomainNc.hpp"
  10. #ifdef LOGGING_BUILD
  11. #define LOG_LDAP(msg, ldap) LOG(msg); LOG(String::format(L"LDAP error %1!ld!", (ldap)))
  12. #else
  13. #define LOG_LDAP(msg, ldap)
  14. #endif
  15. HRESULT
  16. LdapToHresult(int ldapError)
  17. {
  18. // CODEWORK: I'm told that ldap_get_option for LDAP_OPT_SERVER_ERROR or
  19. // LDAP_OPT_SERVER_EXT_ERROR (or perhaps LDAP_OPT_ERROR_STRING?) will give
  20. // an error result with "higher fidelity"
  21. return Win32ToHresult(::LdapMapErrorToWin32(ldapError));
  22. }
  23. // provided by jeffparh
  24. DWORD
  25. IsLastReplicaOfNC(
  26. IN LDAP * hld,
  27. IN LPWSTR pszConfigNC,
  28. IN LPWSTR pszNC,
  29. IN LPWSTR pszNtdsDsaDN,
  30. OUT BOOL * pfIsLastReplica
  31. )
  32. /*++
  33. Routine Description:
  34. Determine whether any DSAs other than that with DN pszNtdsDsaDN hold
  35. replicas of a particular NC.
  36. Arguments:
  37. hld (IN) - LDAP handle to execute search with.
  38. pszConfigNC (IN) - DN of the config NC. Used as a base for the search.
  39. pszNC (IN) - NC for which to check for other replicas.
  40. pszNtdsDsaDN (IN) - DN of the DSA object known to currently have a replica
  41. of the NC. We are specifically looking for replicas *other than* this
  42. one.
  43. pfIsLastReplica (OUT) - On successful return, TRUE iff no DSAs hold replicas
  44. of pszNC other than that with DN pszNtdsDsaDN.
  45. Return Values:
  46. Win error.
  47. --*/
  48. {
  49. LOG_FUNCTION2(IsLastReplicaOfNC, pszNC ? pszNC : L"(null)");
  50. ASSERT(hld);
  51. ASSERT(pszConfigNC);
  52. ASSERT(pszNC);
  53. ASSERT(pszNtdsDsaDN);
  54. ASSERT(pfIsLastReplica);
  55. if (
  56. !hld
  57. || !pszConfigNC
  58. || !pszNC
  59. || !pszNtdsDsaDN
  60. || !pfIsLastReplica)
  61. {
  62. return ERROR_INVALID_PARAMETER;
  63. }
  64. // Just checking for existence -- don't really want any attributes
  65. // returned.
  66. static LPWSTR rgpszDsaAttrsToRead[] = {
  67. L"__invalid_attribute_name__",
  68. NULL
  69. };
  70. static WCHAR szFilterFormat[]
  71. = L"(&(objectCategory=ntdsDsa)(hasMasterNCs=%ls)(!(distinguishedName=%ls)))";
  72. *pfIsLastReplica = TRUE;
  73. int ldStatus = 0;
  74. DWORD err = 0;
  75. LDAPMessage * pDsaResults = NULL;
  76. LDAPMessage * pDsaEntry = NULL;
  77. size_t cchFilter;
  78. PWSTR pszFilter;
  79. LDAP_TIMEVAL lTimeout = {3*60, 0}; // three minutes
  80. do
  81. {
  82. cchFilter = sizeof(szFilterFormat) / sizeof(*szFilterFormat)
  83. + wcslen(pszNtdsDsaDN)
  84. + wcslen(pszNC);
  85. pszFilter = (PWSTR) new BYTE[sizeof(WCHAR) * cchFilter];
  86. swprintf(pszFilter, szFilterFormat, pszNC, pszNtdsDsaDN);
  87. // Search config NC for any ntdsDsa object that hosts this NC other
  88. // than that with dn pszNtdsDsaDN. Note that we cap the search at one
  89. // returned object -- we're not really trying to enumerate, just
  90. // checking for existence.
  91. ldStatus = ldap_search_ext_sW(hld, pszConfigNC, LDAP_SCOPE_SUBTREE,
  92. pszFilter, rgpszDsaAttrsToRead, 0,
  93. NULL, NULL, &lTimeout, 1, &pDsaResults);
  94. if (pDsaResults)
  95. {
  96. // Ignore any error (such as LDAP_SIZELIMIT_EXCEEDED) when the
  97. // search returns results.
  98. ldStatus = 0;
  99. pDsaEntry = ldap_first_entry(hld, pDsaResults);
  100. *pfIsLastReplica = (NULL == pDsaEntry);
  101. } else if (ldStatus)
  102. {
  103. // Search failed and returned no results.
  104. LOG_LDAP(L"Config NC search failed", ldStatus);
  105. break;
  106. } else
  107. {
  108. // No error, no results. This shouldn't happen.
  109. LOG("ldap_search_ext_sW returned no results and no error!");
  110. ASSERT(false);
  111. }
  112. }
  113. while (0);
  114. if (NULL != pDsaResults) {
  115. ldap_msgfree(pDsaResults);
  116. }
  117. if (pszFilter)
  118. {
  119. delete[] pszFilter;
  120. }
  121. if (!err && ldStatus) {
  122. err = LdapMapErrorToWin32(ldStatus);
  123. }
  124. return err;
  125. }
  126. // S_OK if this machine (the localhost) is the last replica of at least one
  127. // non-domain NC, S_FALSE if not, or error otherwise. If S_OK, then the
  128. // StringList will contain the DNs of the non domain NCs for which this
  129. // machine is the last replica.
  130. //
  131. // based on code from jeffparh
  132. //
  133. // hld (IN) - LDAP handle bound to DSA to evaluate.
  134. //
  135. // result (OUT) - string list to receive DNs of the non-domain NCs.
  136. HRESULT
  137. IsLastNdncReplica(LDAP* hld, StringList& result)
  138. {
  139. LOG_FUNCTION(IsLastNdncReplica);
  140. ASSERT(hld);
  141. ASSERT(result.empty());
  142. HRESULT hr = S_FALSE;
  143. LDAPMessage* rootResults = 0;
  144. PWSTR* configNc = 0;
  145. PWSTR* schemaNc = 0;
  146. PWSTR* domainNc = 0;
  147. PWSTR* masterNcs = 0;
  148. PWSTR* ntdsDsaDn = 0;
  149. do
  150. {
  151. // Gather basic rootDSE info.
  152. static PWSTR ROOT_ATTRS_TO_READ[] =
  153. {
  154. LDAP_OPATT_NAMING_CONTEXTS_W,
  155. LDAP_OPATT_DEFAULT_NAMING_CONTEXT_W,
  156. LDAP_OPATT_CONFIG_NAMING_CONTEXT_W,
  157. LDAP_OPATT_SCHEMA_NAMING_CONTEXT_W,
  158. LDAP_OPATT_DS_SERVICE_NAME_W,
  159. 0
  160. };
  161. LOG(L"Calling ldap_search_s");
  162. int ldStatus =
  163. ldap_search_sW(
  164. hld,
  165. 0,
  166. LDAP_SCOPE_BASE,
  167. L"(objectClass=*)",
  168. ROOT_ATTRS_TO_READ,
  169. 0,
  170. &rootResults);
  171. if (ldStatus)
  172. {
  173. LOG_LDAP(L"RootDSE search failed", ldStatus);
  174. hr = LdapToHresult(ldStatus);
  175. break;
  176. }
  177. configNc = ldap_get_valuesW(hld, rootResults, LDAP_OPATT_CONFIG_NAMING_CONTEXT_W);
  178. schemaNc = ldap_get_valuesW(hld, rootResults, LDAP_OPATT_SCHEMA_NAMING_CONTEXT_W);
  179. domainNc = ldap_get_valuesW(hld, rootResults, LDAP_OPATT_DEFAULT_NAMING_CONTEXT_W);
  180. masterNcs = ldap_get_valuesW(hld, rootResults, LDAP_OPATT_NAMING_CONTEXTS_W);
  181. ntdsDsaDn = ldap_get_valuesW(hld, rootResults, LDAP_OPATT_DS_SERVICE_NAME_W);
  182. if (
  183. (0 == configNc)
  184. || (0 == schemaNc)
  185. || (0 == domainNc)
  186. || (0 == masterNcs)
  187. || (0 == ntdsDsaDn))
  188. {
  189. LOG(L"Can't find key rootDSE attributes!");
  190. hr = Win32ToHresult(ERROR_DS_UNAVAILABLE);
  191. break;
  192. }
  193. // There is only one value for each of these attributes...
  194. ASSERT(1 == ldap_count_valuesW(configNc));
  195. ASSERT(1 == ldap_count_valuesW(schemaNc));
  196. ASSERT(1 == ldap_count_valuesW(domainNc));
  197. ASSERT(1 == ldap_count_valuesW(ntdsDsaDn));
  198. DWORD masterNcCount = ldap_count_valuesW(masterNcs);
  199. LOG(String::format(L"masterNcCount = %1!d!", masterNcCount));
  200. // '3' => 1 nc for config, 1 nc for schema, 1 nc for this DC's own
  201. // domain.
  202. if (masterNcCount <= 3)
  203. {
  204. // DSA holds no master NCs other than config, schema, and its own
  205. // domain. Thus, it is not the last replica of any NDNC.
  206. LOG(L"This dsa holds no master NCs other than config, schema, and domain");
  207. ASSERT(3 == masterNcCount);
  208. ASSERT(0 == ldStatus);
  209. ASSERT(hr == S_FALSE);
  210. break;
  211. }
  212. // Loop through non-config/schema/domain NCs to determine those for
  213. // which the DSA is the last replica.
  214. for (int i = 0; 0 != masterNcs[i]; ++i)
  215. {
  216. PWSTR nc = masterNcs[i];
  217. LOG(L"Evaluating " + String(nc));
  218. if (
  219. (0 != wcscmp(nc, *configNc))
  220. && (0 != wcscmp(nc, *schemaNc))
  221. && (0 != wcscmp(nc, *domainNc)))
  222. {
  223. // A non-config/schema/domain NC.
  224. LOG(L"Calling IsLastReplicaOfNC on " + String(nc));
  225. BOOL isLastReplica = FALSE;
  226. DWORD err =
  227. IsLastReplicaOfNC(
  228. hld,
  229. *configNc,
  230. nc,
  231. *ntdsDsaDn,
  232. &isLastReplica);
  233. if (err)
  234. {
  235. LOG(L"IsLastReplicaOfNC() failed");
  236. hr = Win32ToHresult(err);
  237. break;
  238. }
  239. if (isLastReplica)
  240. {
  241. // This DSA is indeed the last replica of this particular
  242. // NC. Return the DN of this NC to our caller.
  243. LOG(L"last replica of " + String(nc));
  244. result.push_back(nc);
  245. }
  246. else
  247. {
  248. LOG(L"not last replica of " + String(nc));
  249. }
  250. }
  251. }
  252. // If we broke out of the prior loop with an error, jump out to the
  253. // cleanup section.
  254. BREAK_ON_FAILED_HRESULT(hr);
  255. hr = result.size() > 0 ? S_OK : S_FALSE;
  256. }
  257. while (0);
  258. if (rootResults)
  259. {
  260. ldap_msgfree(rootResults);
  261. }
  262. if (0 != configNc)
  263. {
  264. ldap_value_freeW(configNc);
  265. }
  266. if (0 != schemaNc)
  267. {
  268. ldap_value_freeW(schemaNc);
  269. }
  270. if (0 != domainNc)
  271. {
  272. ldap_value_freeW(domainNc);
  273. }
  274. if (0 != masterNcs)
  275. {
  276. ldap_value_freeW(masterNcs);
  277. }
  278. if (0 != ntdsDsaDn)
  279. {
  280. ldap_value_freeW(ntdsDsaDn);
  281. }
  282. #ifdef LOGGING_BUILD
  283. LOG_HRESULT(hr);
  284. for (
  285. StringList::iterator i = result.begin();
  286. i != result.end();
  287. ++i)
  288. {
  289. LOG(*i);
  290. }
  291. #endif
  292. return hr;
  293. }
  294. // S_OK if this machine (the localhost) is the last replica of at least one
  295. // non-domain NC, S_FALSE if not, or error otherwise.
  296. //
  297. // result - If S_OK is returned, receives the DNs of the non domain NCs for
  298. // which this machine is the last replica. Should be empty on entry.
  299. HRESULT
  300. IsLastNonDomainNamingContextReplica(StringList& result)
  301. {
  302. LOG_FUNCTION(IsLastNonDomainNamingContextReplica);
  303. ASSERT(result.empty());
  304. result.clear();
  305. HRESULT hr = S_FALSE;
  306. LDAP* hld = 0;
  307. do
  308. {
  309. // Connect to target DSA.
  310. LOG(L"Calling ldap_open");
  311. hld = ldap_openW(L"localhost", LDAP_PORT);
  312. if (!hld)
  313. {
  314. LOG("Cannot open LDAP connection to localhost");
  315. hr = Win32ToHresult(ERROR_DS_UNAVAILABLE);
  316. break;
  317. }
  318. // Bind using logged-in user's credentials.
  319. int ldStatus = ldap_bind_s(hld, 0, 0, LDAP_AUTH_NEGOTIATE);
  320. if (ldStatus)
  321. {
  322. LOG_LDAP(L"LDAP bind failed", ldStatus);
  323. hr = LdapToHresult(ldStatus);
  324. break;
  325. }
  326. // go do the real work
  327. hr = IsLastNdncReplica(hld, result);
  328. }
  329. while (0);
  330. if (hld)
  331. {
  332. ldap_unbind(hld);
  333. }
  334. LOG_HRESULT(hr);
  335. return hr;
  336. }
  337. static const DWORD HELP_MAP[] =
  338. {
  339. 0, 0
  340. };
  341. NonDomainNcErrorDialog::NonDomainNcErrorDialog(StringList& ndncList_)
  342. :
  343. Dialog(IDD_NON_DOMAIN_NC_ERROR, HELP_MAP),
  344. ndncList(ndncList_),
  345. warnIcon(0)
  346. {
  347. LOG_CTOR(NonDomainNcErrorDialog);
  348. ASSERT(ndncList.size());
  349. }
  350. NonDomainNcErrorDialog::~NonDomainNcErrorDialog()
  351. {
  352. LOG_DTOR(NonDomainNcErrorDialog);
  353. if (warnIcon)
  354. {
  355. Win::DestroyIcon(warnIcon);
  356. }
  357. }
  358. void
  359. NonDomainNcErrorDialog::OnInit()
  360. {
  361. LOG_FUNCTION(NonDomainNcErrorDialog::OnInit);
  362. // set the warning icon NTRAID#NTBUG9-239678-2000/11/28-sburns
  363. HRESULT hr = Win::LoadImage(IDI_WARN, warnIcon);
  364. ASSERT(SUCCEEDED(hr));
  365. Win::SendMessage(
  366. Win::GetDlgItem(hwnd, IDC_WARNING_ICON),
  367. STM_SETICON,
  368. reinterpret_cast<WPARAM>(warnIcon),
  369. 0);
  370. PopulateListView();
  371. }
  372. // Unwraps the safearray of variants inside a variant, extracts the strings
  373. // inside them, and catenates the strings together with semicolons in between.
  374. // Return empty string on error.
  375. //
  376. // variant - in, the variant containing a safearray of variants of bstr.
  377. String
  378. GetNdncDescriptionHelper(VARIANT* variant)
  379. {
  380. LOG_FUNCTION(GetNdncDescriptionHelper);
  381. ASSERT(variant);
  382. ASSERT(V_VT(variant) == (VT_ARRAY | VT_VARIANT));
  383. String result;
  384. SAFEARRAY* psa = V_ARRAY(variant);
  385. do
  386. {
  387. ASSERT(psa);
  388. ASSERT(psa != (SAFEARRAY*)-1);
  389. if (!psa or psa == (SAFEARRAY*)-1)
  390. {
  391. LOG(L"variant not safe array");
  392. break;
  393. }
  394. if (::SafeArrayGetDim(psa) != 1)
  395. {
  396. LOG(L"safe array: wrong number of dimensions");
  397. break;
  398. }
  399. VARTYPE vt = VT_EMPTY;
  400. HRESULT hr = ::SafeArrayGetVartype(psa, &vt);
  401. if (FAILED(hr) || vt != VT_VARIANT)
  402. {
  403. LOG(L"safe array: wrong element type");
  404. break;
  405. }
  406. long lower = 0;
  407. long upper = 0;
  408. hr = ::SafeArrayGetLBound(psa, 1, &lower);
  409. BREAK_ON_FAILED_HRESULT2(hr, L"can't get lower bound");
  410. hr = ::SafeArrayGetUBound(psa, 1, &upper);
  411. BREAK_ON_FAILED_HRESULT2(hr, L"can't get upper bound");
  412. VARIANT varItem;
  413. ::VariantInit(&varItem);
  414. for (long i = lower; i <= upper; ++i)
  415. {
  416. hr = ::SafeArrayGetElement(psa, &i, &varItem);
  417. if (FAILED(hr))
  418. {
  419. LOG(String::format(L"index %1!d! failed", i));
  420. continue;
  421. }
  422. result += V_BSTR(&varItem);
  423. if (i < upper)
  424. {
  425. result += L";";
  426. }
  427. ::VariantClear(&varItem);
  428. }
  429. }
  430. while (0);
  431. LOG(result);
  432. return result;
  433. }
  434. // bind to an ndnc, read it's description(s), and return them catenated
  435. // together. Return empty string on error.
  436. //
  437. // ndncDn - in, DN of the ndnc
  438. String
  439. GetNdncDescription(const String& ndncDn)
  440. {
  441. LOG_FUNCTION2(GetNdncDescription, ndncDn);
  442. ASSERT(!ndncDn.empty());
  443. String result;
  444. do
  445. {
  446. String path = L"LDAP://" + ndncDn;
  447. SmartInterface<IADs> iads(0);
  448. IADs* dumb = 0;
  449. HRESULT hr =
  450. ::ADsGetObject(
  451. path.c_str(),
  452. __uuidof(iads),
  453. reinterpret_cast<void**>(&dumb));
  454. BREAK_ON_FAILED_HRESULT2(hr, L"ADsGetObject failed on " + path);
  455. iads.Acquire(dumb);
  456. // description is a multivalued attrbute for no apparent good reason.
  457. // so we need to walk an array of values.
  458. _variant_t variant;
  459. hr = iads->GetEx(AutoBstr(L"description"), &variant);
  460. BREAK_ON_FAILED_HRESULT2(hr, L"read description failed");
  461. result = GetNdncDescriptionHelper(&variant);
  462. }
  463. while (0);
  464. LOG(result);
  465. return result;
  466. }
  467. // Build a list view with two columns, one for the DN of the ndncs for which
  468. // this box is the last replica, another for the description(s) of those
  469. // ndncs.
  470. void
  471. NonDomainNcErrorDialog::PopulateListView()
  472. {
  473. LOG_FUNCTION(NonDomainNcErrorDialog::PopulateListView);
  474. HWND view = Win::GetDlgItem(hwnd, IDC_NDNC_LIST);
  475. // add a column to the list view for the DN
  476. LVCOLUMN column;
  477. ::ZeroMemory(&column, sizeof column);
  478. column.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
  479. column.fmt = LVCFMT_LEFT;
  480. int width = 0;
  481. String::load(IDS_NDNC_LIST_NAME_COLUMN_WIDTH).convert(width);
  482. column.cx = width;
  483. String label = String::load(IDS_NDNC_LIST_NAME_COLUMN);
  484. column.pszText = const_cast<wchar_t*>(label.c_str());
  485. Win::ListView_InsertColumn(view, 0, column);
  486. // add a column to the list view for description.
  487. String::load(IDS_NDNC_LIST_DESC_COLUMN_WIDTH).convert(width);
  488. column.cx = width;
  489. label = String::load(IDS_NDNC_LIST_DESC_COLUMN);
  490. column.pszText = const_cast<wchar_t*>(label.c_str());
  491. Win::ListView_InsertColumn(view, 1, column);
  492. // Load up the edit box with the DNs we aliased in the ctor.
  493. LVITEM item;
  494. ::ZeroMemory(&item, sizeof item);
  495. for (
  496. StringList::iterator i = ndncList.begin();
  497. i != ndncList.end();
  498. ++i)
  499. {
  500. item.mask = LVIF_TEXT;
  501. item.pszText = const_cast<wchar_t*>(i->c_str());
  502. item.iSubItem = 0;
  503. item.iItem = Win::ListView_InsertItem(view, item);
  504. // add the description sub-item to the list control
  505. String description = GetNdncDescription(*i);
  506. item.mask = LVIF_TEXT;
  507. item.pszText = const_cast<wchar_t*>(description.c_str());
  508. item.iSubItem = 1;
  509. Win::ListView_SetItem(view, item);
  510. }
  511. }
  512. bool
  513. NonDomainNcErrorDialog::OnCommand(
  514. HWND /* windowFrom */ ,
  515. unsigned controlIDFrom,
  516. unsigned code)
  517. {
  518. // LOG_FUNCTION(NonDomainNcErrorDialog::OnCommand);
  519. if (code == BN_CLICKED)
  520. {
  521. switch (controlIDFrom)
  522. {
  523. case IDOK:
  524. case IDCANCEL:
  525. {
  526. Win::EndDialog(hwnd, controlIDFrom);
  527. return true;
  528. }
  529. // NTRAID#NTBUG9-239678-2000/11/28-sburns
  530. case IDC_SHOW_HELP:
  531. {
  532. if (code == BN_CLICKED)
  533. {
  534. Win::HtmlHelp(
  535. hwnd,
  536. L"adconcepts.chm::/ADHelpDemoteWithNDNC.htm",
  537. HH_DISPLAY_TOPIC,
  538. 0);
  539. return true;
  540. }
  541. break;
  542. }
  543. default:
  544. {
  545. // do nothing
  546. }
  547. }
  548. }
  549. return false;
  550. }
  551. bool
  552. IsLastReplicaOfNonDomainNamingContexts()
  553. {
  554. LOG_FUNCTION(IsLastReplicaOfNonDomainNamingContexts);
  555. bool result = false;
  556. do
  557. {
  558. State::RunContext context = State::GetInstance().GetRunContext();
  559. if (context != State::NT5_DC)
  560. {
  561. // not a DC, so can't be replica of any NCs
  562. LOG(L"not a DC");
  563. break;
  564. }
  565. // Find the list of non-domain NCs that this DC is the last replica
  566. // (if any). If we find some, gripe at the user.
  567. StringList ndncList;
  568. HRESULT hr = IsLastNonDomainNamingContextReplica(ndncList);
  569. if (FAILED(hr))
  570. {
  571. popup.Error(
  572. Win::GetDesktopWindow(),
  573. hr,
  574. IDS_FAILED_TO_READ_NDNC_INFO);
  575. result = true;
  576. break;
  577. }
  578. if (hr == S_FALSE)
  579. {
  580. LOG(L"Not last replica of non-domain NCs");
  581. ASSERT(result == false);
  582. break;
  583. }
  584. result = true;
  585. // there should be at least one DN in the list.
  586. ASSERT(ndncList.size());
  587. NonDomainNcErrorDialog(ndncList).ModalExecute(Win::GetDesktopWindow());
  588. }
  589. while (0);
  590. LOG(result ? L"true" : L"false");
  591. return result;
  592. }