Leaked source code of windows server 2003
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.

667 lines
25 KiB

  1. //+---------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1999-2002.
  5. //
  6. // File: Schema.cpp
  7. //
  8. // Contents: DoSchemaDiagnosis and support methods
  9. //
  10. //
  11. //----------------------------------------------------------------------------
  12. #include "stdafx.h"
  13. #include <security.h>
  14. #include <seopaque.h>
  15. #include <sddl.h>
  16. #include "ADUtils.h"
  17. #include "Schema.h"
  18. #include "SecDesc.h"
  19. // Function prototypes
  20. bool FindInGlobalList (const ACE_SAMNAME* pAceSAMNameToFind, const ACE_SAMNAME_LIST& defACLList);
  21. HRESULT GetSchemaDefaultSecurityDescriptor (
  22. const wstring& strObjectDN,
  23. PADS_ATTR_INFO* ppAttrs,
  24. PSECURITY_DESCRIPTOR* ppSecurityDescriptor,
  25. wstring &objectClass);
  26. HRESULT GetObjectClass (const wstring& strObjectDN, wstring& ldapClassName);
  27. HRESULT GetClassSecurityDescriptor (
  28. PADS_ATTR_INFO* ppAttrs,
  29. PSECURITY_DESCRIPTOR* ppSecurityDescriptor,
  30. CComPtr<IADsPathname>& spPathname,
  31. const wstring& ldapClassName);
  32. HRESULT GetADClassName (
  33. CComPtr<IADsPathname>& spPathname,
  34. const wstring& ldapClassName,
  35. wstring& adClassName);
  36. // Functions
  37. HRESULT DoSchemaDiagnosis ()
  38. {
  39. _TRACE (1, L"Entering DoSchemaDiagnosis\n");
  40. HRESULT hr = S_OK;
  41. wstring str;
  42. size_t nDACLAcesFound = 0;
  43. bool bAllExplicit = true;
  44. bool bAllInherited = true;
  45. ACE_SAMNAME_LIST defDACLList;
  46. ACE_SAMNAME_LIST defSACLList;
  47. if ( !_Module.DoTabDelimitedOutput () )
  48. {
  49. LoadFromResource (str, IDS_SCHEMA_DEFAULTS_DIAGNOSIS);
  50. MyWprintf (str.c_str ());
  51. }
  52. PSECURITY_DESCRIPTOR pSecurityDescriptor = 0;
  53. PADS_ATTR_INFO pAttrs = NULL;
  54. wstring ldapClassName;
  55. hr = GetSchemaDefaultSecurityDescriptor (_Module.GetObjectDN (), &pAttrs,
  56. &pSecurityDescriptor, ldapClassName);
  57. if ( SUCCEEDED (hr) && pSecurityDescriptor )
  58. {
  59. hr = EnumerateDacl (pSecurityDescriptor, defDACLList, false);
  60. if ( SUCCEEDED (hr) )
  61. {
  62. ACE_SAMNAME* pAceSAMName = 0;
  63. // Compare the DACL
  64. for (ACE_SAMNAME_LIST::iterator itr = defDACLList.begin();
  65. itr != defDACLList.end();
  66. itr++)
  67. {
  68. pAceSAMName = *itr;
  69. if ( FindInGlobalList (pAceSAMName, _Module.m_DACLList) )
  70. {
  71. if ( pAceSAMName->m_pAllowedAce->Header.AceFlags & INHERITED_ACE )
  72. {
  73. switch ( pAceSAMName->m_AceType )
  74. {
  75. case ACCESS_ALLOWED_OBJECT_ACE_TYPE:
  76. {
  77. wstring inheritedObjectClass;
  78. if ( SUCCEEDED (_Module.GetClassFromGUID (
  79. pAceSAMName->m_pAllowedObjectAce->InheritedObjectType,
  80. inheritedObjectClass) ) )
  81. {
  82. if ( !ldapClassName.compare (inheritedObjectClass) )
  83. break; // matches
  84. }
  85. }
  86. continue; // not a match
  87. case ACCESS_DENIED_OBJECT_ACE_TYPE:
  88. {
  89. wstring inheritedObjectClass;
  90. if ( SUCCEEDED (_Module.GetClassFromGUID (
  91. pAceSAMName->m_pDeniedObjectAce->InheritedObjectType,
  92. inheritedObjectClass) ) )
  93. {
  94. if ( !ldapClassName.compare (inheritedObjectClass) )
  95. break; // matches
  96. }
  97. }
  98. continue; // not a match
  99. default:
  100. break; // matches
  101. }
  102. bAllExplicit = false;
  103. }
  104. else
  105. bAllInherited = false;
  106. nDACLAcesFound++;
  107. }
  108. }
  109. }
  110. // Compare the SACL
  111. hr = EnumerateSacl (pSecurityDescriptor, defSACLList);
  112. if ( SUCCEEDED (hr) )
  113. {
  114. ACE_SAMNAME* pAceSAMName = 0;
  115. for (ACE_SAMNAME_LIST::iterator itr = defSACLList.begin();
  116. itr != defSACLList.end();
  117. itr++)
  118. {
  119. pAceSAMName = *itr;
  120. if ( FindInGlobalList (pAceSAMName, _Module.m_SACLList) )
  121. {
  122. if ( pAceSAMName->m_pAllowedAce->Header.AceFlags & INHERITED_ACE )
  123. {
  124. switch ( pAceSAMName->m_AceType )
  125. {
  126. case SYSTEM_AUDIT_OBJECT_ACE_TYPE:
  127. {
  128. wstring inheritedObjectClass;
  129. if ( SUCCEEDED (_Module.GetClassFromGUID (
  130. pAceSAMName->m_pSystemAuditObjectAce->InheritedObjectType,
  131. inheritedObjectClass) ) )
  132. {
  133. if ( !ldapClassName.compare (inheritedObjectClass) )
  134. break; // matches
  135. }
  136. }
  137. continue; // not a match
  138. case SYSTEM_AUDIT_ACE_TYPE:
  139. default:
  140. break; // matches
  141. }
  142. bAllExplicit = false;
  143. }
  144. else
  145. bAllInherited = false;
  146. nDACLAcesFound++;
  147. }
  148. }
  149. }
  150. }
  151. wstring strDefaultState;
  152. bool bPresent = false;
  153. if ( !nDACLAcesFound )
  154. {
  155. // absent
  156. LoadFromResource (strDefaultState, IDS_ABSENT);
  157. }
  158. else
  159. {
  160. if ( nDACLAcesFound == defDACLList.size () )
  161. {
  162. if ( bAllExplicit | bAllInherited )
  163. {
  164. // present
  165. LoadFromResource (strDefaultState, IDS_PRESENT);
  166. bPresent = true;
  167. }
  168. else
  169. {
  170. // partial
  171. LoadFromResource (strDefaultState, IDS_PARTIAL);
  172. }
  173. }
  174. else
  175. {
  176. // partial
  177. LoadFromResource (strDefaultState, IDS_PARTIAL);
  178. }
  179. }
  180. if ( _Module.DoTabDelimitedOutput () )
  181. FormatMessage (str, IDS_SCHEMA_DEFAULTS_CDO, strDefaultState.c_str ());
  182. else
  183. FormatMessage (str, IDS_SCHEMA_DEFAULTS, strDefaultState.c_str ());
  184. MyWprintf (str.c_str ());
  185. if ( bPresent )
  186. {
  187. if ( bAllExplicit )
  188. LoadFromResource (strDefaultState, IDS_AT_CREATION);
  189. else
  190. LoadFromResource (strDefaultState, IDS_BY_INHERITANCE);
  191. if ( _Module.DoTabDelimitedOutput () )
  192. FormatMessage (str, IDS_OBTAINED_CDO, strDefaultState.c_str ());
  193. else
  194. FormatMessage (str, IDS_OBTAINED, strDefaultState.c_str ());
  195. MyWprintf (str.c_str ());
  196. }
  197. else if ( _Module.DoTabDelimitedOutput () )
  198. MyWprintf (L"\n\n");
  199. _TRACE (-1, L"Leaving DoSchemaDiagnosis: 0x%x\n", hr);
  200. return hr;
  201. }
  202. HRESULT GetSchemaDefaultSecurityDescriptor (
  203. const wstring& strObjectDN,
  204. PADS_ATTR_INFO* ppAttrs,
  205. PSECURITY_DESCRIPTOR* ppSecurityDescriptor,
  206. wstring &ldapClassName)
  207. {
  208. _TRACE (1, L"Entering GetSchemaDefaultSecurityDescriptor\n");
  209. HRESULT hr = S_OK;
  210. if ( ppAttrs && ppSecurityDescriptor )
  211. {
  212. hr = GetObjectClass (strObjectDN, ldapClassName);
  213. if ( SUCCEEDED (hr) )
  214. {
  215. wstring strDC;
  216. size_t pos = strObjectDN.find (L"DC=", 0);
  217. if ( strObjectDN.npos != pos )
  218. {
  219. strDC = strObjectDN.substr (pos);
  220. CComPtr<IADsPathname> spPathname;
  221. //
  222. // Constructing the directory paths
  223. //
  224. hr = CoCreateInstance(
  225. CLSID_Pathname,
  226. NULL,
  227. CLSCTX_ALL,
  228. IID_PPV_ARG (IADsPathname, &spPathname));
  229. if ( SUCCEEDED (hr) )
  230. {
  231. ASSERT (!!spPathname);
  232. hr = spPathname->Set (CComBSTR (ACLDIAG_LDAP), ADS_SETTYPE_PROVIDER);
  233. if ( SUCCEEDED (hr) )
  234. {
  235. hr = spPathname->Set (CComBSTR (strDC.c_str ()), ADS_SETTYPE_DN);
  236. if ( SUCCEEDED (hr) )
  237. {
  238. hr = spPathname->AddLeafElement (CComBSTR (L"CN=Configuration"));
  239. if ( SUCCEEDED (hr) )
  240. {
  241. hr = spPathname->AddLeafElement (CComBSTR (L"CN=Schema"));
  242. if ( SUCCEEDED (hr) )
  243. {
  244. hr = GetClassSecurityDescriptor (
  245. ppAttrs,
  246. ppSecurityDescriptor,
  247. spPathname,
  248. ldapClassName);
  249. }
  250. }
  251. }
  252. else
  253. {
  254. _TRACE (0, L"IADsPathname->Set (%s): 0x%x\n",
  255. strDC.c_str (), hr);
  256. }
  257. }
  258. else
  259. {
  260. _TRACE (0, L"IADsPathname->Set (%s): 0x%x\n", ACLDIAG_LDAP, hr);
  261. }
  262. }
  263. else
  264. {
  265. _TRACE (0, L"CoCreateInstance(CLSID_Pathname): 0x%x\n", hr);
  266. }
  267. }
  268. }
  269. }
  270. else
  271. hr = E_POINTER;
  272. _TRACE (-1, L"Leaving GetSchemaDefaultSecurityDescriptor: 0x%x\n", hr);
  273. return hr;
  274. }
  275. HRESULT GetObjectClass (const wstring& strObjectDN, wstring& ldapClassName)
  276. {
  277. _TRACE (1, L"Entering GetObjectClass\n");
  278. HRESULT hr = S_OK;
  279. CComPtr<IADsPathname> spPathname;
  280. //
  281. // Constructing the directory paths
  282. //
  283. hr = CoCreateInstance(
  284. CLSID_Pathname,
  285. NULL,
  286. CLSCTX_ALL,
  287. IID_PPV_ARG (IADsPathname, &spPathname));
  288. if ( SUCCEEDED (hr) )
  289. {
  290. ASSERT (!!spPathname);
  291. hr = spPathname->Set (CComBSTR (ACLDIAG_LDAP), ADS_SETTYPE_PROVIDER);
  292. if ( SUCCEEDED (hr) )
  293. {
  294. hr = spPathname->Set (CComBSTR (strObjectDN.c_str ()), ADS_SETTYPE_DN);
  295. if ( SUCCEEDED (hr) )
  296. {
  297. BSTR bstrFullPath = 0;
  298. hr = spPathname->Retrieve(ADS_FORMAT_X500, &bstrFullPath);
  299. if ( SUCCEEDED (hr) )
  300. {
  301. CComPtr<IDirectoryObject> spDirObj;
  302. hr = ADsOpenObjectHelper (bstrFullPath,
  303. IID_IDirectoryObject,
  304. 0,
  305. (void**)&spDirObj);
  306. if ( SUCCEEDED (hr) )
  307. {
  308. //
  309. // Get this object's object class.
  310. //
  311. const PWSTR wzObjectClass = L"objectClass";
  312. DWORD cAttrs = 0;
  313. LPWSTR rgpwzAttrNames[] = {wzObjectClass};
  314. PADS_ATTR_INFO pAttrs = NULL;
  315. hr = spDirObj->GetObjectAttributes(rgpwzAttrNames, 1,
  316. &pAttrs, &cAttrs);
  317. if ( SUCCEEDED (hr) )
  318. {
  319. if ( 1 <= cAttrs && pAttrs && pAttrs->pADsValues )
  320. {
  321. if (!(pAttrs->pADsValues[pAttrs->dwNumValues-1].CaseIgnoreString) )
  322. {
  323. _TRACE (0, L"IADS return bogus object class!\n");
  324. hr = E_UNEXPECTED;
  325. }
  326. else
  327. {
  328. ldapClassName =
  329. pAttrs->pADsValues[pAttrs->dwNumValues-1].CaseIgnoreString;
  330. }
  331. FreeADsMem (pAttrs);
  332. }
  333. else
  334. hr = E_UNEXPECTED;
  335. }
  336. else
  337. {
  338. _TRACE (0, L"IDirectoryObject->GetObjectAttributes (): 0x%x\n", hr);
  339. }
  340. }
  341. else
  342. {
  343. _TRACE (0, L"ADsOpenObjectHelper (%s): 0x%x\n", bstrFullPath, hr);
  344. wstring strErr;
  345. FormatMessage (strErr, IDS_INVALID_OBJECT,
  346. _Module.GetObjectDN ().c_str (),
  347. GetSystemMessage (hr).c_str ());
  348. MyWprintf (strErr.c_str ());
  349. }
  350. }
  351. else
  352. {
  353. _TRACE (0, L"IADsPathname->Retrieve (): 0x%x\n", hr);
  354. }
  355. }
  356. else
  357. {
  358. _TRACE (0, L"IADsPathname->Set (%s): 0x%x\n",
  359. _Module.GetObjectDN ().c_str (), hr);
  360. }
  361. }
  362. else
  363. {
  364. _TRACE (0, L"IADsPathname->Set (%s): 0x%x\n", ACLDIAG_LDAP, hr);
  365. }
  366. }
  367. else
  368. {
  369. _TRACE (0, L"CoCreateInstance(CLSID_Pathname): 0x%x\n", hr);
  370. }
  371. _TRACE (-1, L"Leaving GetObjectClass: 0x%x\n", hr);
  372. return hr;
  373. }
  374. bool FindInGlobalList (const ACE_SAMNAME* pAceSAMNameToFind, const ACE_SAMNAME_LIST& defDACLList)
  375. {
  376. _TRACE (1, L"Entering FindInGlobalList\n");
  377. bool bFound = false;
  378. ACE_SAMNAME* pAceSAMName = 0;
  379. for (ACE_SAMNAME_LIST::iterator itr = defDACLList.begin();
  380. itr != defDACLList.end();
  381. itr++)
  382. {
  383. pAceSAMName = *itr;
  384. // pAceSAMNameToFind must be on the left
  385. if ( *pAceSAMNameToFind == *pAceSAMName )
  386. {
  387. bFound = true;
  388. break;
  389. }
  390. }
  391. _TRACE (-1, L"Leaving FindInGlobalList: %s\n", bFound ? L"found" : L"not found");
  392. return bFound;
  393. }
  394. HRESULT GetClassSecurityDescriptor (PADS_ATTR_INFO* ppAttrs,
  395. PSECURITY_DESCRIPTOR* ppSecurityDescriptor,
  396. CComPtr<IADsPathname>& spPathname,
  397. const wstring& ldapClassName)
  398. {
  399. _TRACE (1, L"Entering GetClassSecurityDescriptor\n");
  400. // ldapClassName must be converted from LDAP class to AD class
  401. wstring adClassName;
  402. HRESULT hr = GetADClassName (spPathname, ldapClassName, adClassName);
  403. if ( SUCCEEDED (hr) )
  404. {
  405. wstring cnClassName (L"CN=");
  406. cnClassName += adClassName;
  407. hr = spPathname->AddLeafElement (
  408. CComBSTR (cnClassName.c_str ()));
  409. if ( SUCCEEDED (hr) )
  410. {
  411. BSTR bstrFullPath = 0;
  412. hr = spPathname->Retrieve(ADS_FORMAT_X500, &bstrFullPath);
  413. if ( SUCCEEDED (hr) )
  414. {
  415. CComPtr<IDirectoryObject> spDirObj;
  416. hr = ADsOpenObjectHelper (bstrFullPath,
  417. IID_IDirectoryObject,
  418. 0,
  419. (void**)&spDirObj);
  420. if ( SUCCEEDED (hr) )
  421. {
  422. hr = SetSecurityInfoMask (spDirObj,
  423. OWNER_SECURITY_INFORMATION |
  424. GROUP_SECURITY_INFORMATION |
  425. DACL_SECURITY_INFORMATION |
  426. SACL_SECURITY_INFORMATION);
  427. //
  428. // Get this object's default Security Descriptor.
  429. //
  430. const PWSTR wzSecDescriptor = L"defaultSecurityDescriptor";
  431. DWORD cAttrs = 0;
  432. LPWSTR rgpwzAttrNames[] = {wzSecDescriptor};
  433. hr = spDirObj->GetObjectAttributes(rgpwzAttrNames, 1, ppAttrs, &cAttrs);
  434. if ( SUCCEEDED (hr) )
  435. {
  436. if ( 1 == cAttrs && *ppAttrs && (*ppAttrs)->pADsValues )
  437. {
  438. // Caller will delete the SD w/ LocalFree
  439. if ( !ConvertStringSecurityDescriptorToSecurityDescriptor(
  440. (*ppAttrs)->pADsValues->CaseIgnoreString,
  441. SDDL_REVISION,
  442. ppSecurityDescriptor,
  443. 0) )
  444. {
  445. hr = HRESULT_FROM_WIN32(::GetLastError());
  446. }
  447. if ( SUCCEEDED (hr) )
  448. {
  449. ASSERT (*ppSecurityDescriptor);
  450. if ( !*ppSecurityDescriptor )
  451. {
  452. _TRACE (0, L"Call to ConvertStringSecurityDescriptorToSecurityDescriptor () succeeded but returned null security descriptor.\n");
  453. hr = E_FAIL;
  454. }
  455. }
  456. }
  457. else
  458. hr = E_UNEXPECTED;
  459. }
  460. else
  461. {
  462. _TRACE (0, L"IDirectoryObject->GetObjectAttributes (): 0x%x\n", hr);
  463. }
  464. }
  465. else
  466. {
  467. _TRACE (0, L"ADsOpenObjectHelper (%s): 0x%x\n", bstrFullPath, hr);
  468. wstring strErr;
  469. FormatMessage (strErr, IDS_INVALID_OBJECT,
  470. _Module.GetObjectDN ().c_str (),
  471. GetSystemMessage (hr).c_str ());
  472. MyWprintf (strErr.c_str ());
  473. }
  474. }
  475. else
  476. {
  477. _TRACE (0, L"IADsPathname->Retrieve (): 0x%x\n", hr);
  478. }
  479. }
  480. }
  481. _TRACE (-1, L"Leaving GetClassSecurityDescriptor: 0x%x\n", hr);
  482. return hr;
  483. }
  484. HRESULT GetADClassName (
  485. CComPtr<IADsPathname>& spPathname,
  486. const wstring& ldapClassName,
  487. wstring& adClassName)
  488. {
  489. _TRACE (1, L"Entering GetADClassName\n");
  490. HRESULT hr = S_OK;
  491. // Get the "CN=Schema,CN=Configuration,DC=..." object
  492. // Search for a child whose LDAP-Display-Name matches ldapClassName
  493. BSTR bstrFullPath = 0;
  494. hr = spPathname->Retrieve(ADS_FORMAT_X500, &bstrFullPath);
  495. if ( SUCCEEDED (hr) )
  496. {
  497. CComPtr<IDirectoryObject> spDirObj;
  498. hr = ADsOpenObjectHelper (bstrFullPath,
  499. IID_IDirectoryObject,
  500. 0,
  501. (void**)&spDirObj);
  502. if ( SUCCEEDED (hr) )
  503. {
  504. CComPtr<IDirectorySearch> spDsSearch;
  505. hr = spDirObj->QueryInterface (IID_PPV_ARG(IDirectorySearch, &spDsSearch));
  506. if ( SUCCEEDED (hr) )
  507. {
  508. ASSERT (!!spDsSearch);
  509. ADS_SEARCHPREF_INFO pSearchPref[2];
  510. DWORD dwNumPref = 2;
  511. pSearchPref[0].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
  512. pSearchPref[0].vValue.dwType = ADSTYPE_INTEGER;
  513. pSearchPref[0].vValue.Integer = ADS_SCOPE_ONELEVEL;
  514. pSearchPref[1].dwSearchPref = ADS_SEARCHPREF_CHASE_REFERRALS;
  515. pSearchPref[1].vValue.dwType = ADSTYPE_INTEGER;
  516. pSearchPref[1].vValue.Integer = ADS_CHASE_REFERRALS_NEVER;
  517. hr = spDsSearch->SetSearchPreference(
  518. pSearchPref,
  519. dwNumPref
  520. );
  521. if ( SUCCEEDED (hr) )
  522. {
  523. PWSTR rgszAttrList[] = {L"cn"}; //Common-Name", NULL };
  524. ADS_SEARCH_HANDLE hSearchHandle = 0;
  525. DWORD dwNumAttributes = 1;
  526. wstring strQuery;
  527. ADS_SEARCH_COLUMN Column;
  528. ::ZeroMemory (&Column, sizeof (ADS_SEARCH_COLUMN));
  529. FormatMessage (strQuery,
  530. L"lDAPDisplayName=%1", //L"LDAP-Display-Name=%1",
  531. ldapClassName.c_str ());
  532. hr = spDsSearch->ExecuteSearch(
  533. const_cast <LPWSTR>(strQuery.c_str ()),
  534. rgszAttrList,
  535. dwNumAttributes,
  536. &hSearchHandle
  537. );
  538. if ( SUCCEEDED (hr) )
  539. {
  540. hr = spDsSearch->GetFirstRow (hSearchHandle);
  541. if ( SUCCEEDED (hr) )
  542. {
  543. while (hr != S_ADS_NOMORE_ROWS )
  544. {
  545. //
  546. // Getting current row's information
  547. //
  548. hr = spDsSearch->GetColumn(
  549. hSearchHandle,
  550. rgszAttrList[0],
  551. &Column
  552. );
  553. if ( SUCCEEDED (hr) )
  554. {
  555. adClassName = Column.pADsValues->CaseIgnoreString;
  556. spDsSearch->FreeColumn (&Column);
  557. Column.pszAttrName = NULL;
  558. break;
  559. }
  560. else if ( hr != E_ADS_COLUMN_NOT_SET )
  561. {
  562. break;
  563. }
  564. else
  565. {
  566. _TRACE (0, L"IDirectorySearch::GetColumn (): 0x%x\n", hr);
  567. }
  568. }
  569. }
  570. else
  571. {
  572. _TRACE (0, L"IDirectorySearch::GetFirstRow (): 0x%x\n", hr);
  573. }
  574. if (Column.pszAttrName)
  575. {
  576. spDsSearch->FreeColumn(&Column);
  577. }
  578. spDsSearch->CloseSearchHandle(hSearchHandle);
  579. }
  580. else
  581. {
  582. _TRACE (0, L"IDirectorySearch::ExecuteSearch (): 0x%x\n", hr);
  583. hr = S_OK;
  584. }
  585. }
  586. else
  587. {
  588. _TRACE (0, L"IDirectorySearch::SetSearchPreference (): 0x%x\n", hr);
  589. }
  590. }
  591. else
  592. {
  593. _TRACE (0, L"IDirectoryObject::QueryInterface (IDirectorySearch): 0x%x\n", hr);
  594. }
  595. }
  596. else
  597. {
  598. _TRACE (0, L"ADsOpenObjectHelper (%s): 0x%x\n", bstrFullPath, hr);
  599. }
  600. }
  601. else
  602. {
  603. _TRACE (0, L"IADsPathname->Retrieve (): 0x%x\n", hr);
  604. }
  605. _TRACE (-1, L"Leaving GetADClassName: 0x%x\n", hr);
  606. return hr;
  607. }