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.

956 lines
21 KiB

  1. // PolicyMM.cpp: implementation for the WMI class Nsp_MMPolicySettings
  2. //
  3. // Copyright (c)1997-2001 Microsoft Corporation
  4. //
  5. //////////////////////////////////////////////////////////////////////
  6. #include "precomp.h"
  7. #include "PolicyMM.h"
  8. #include "NetSecProv.h"
  9. /*
  10. Routine Description:
  11. Name:
  12. CMMPolicy::QueryInstance
  13. Functionality:
  14. Given the query, it returns to WMI (using pSink) all the instances that satisfy the query.
  15. Actually, what we give back to WMI may contain extra instances. WMI will do the final filtering.
  16. Virtual:
  17. Yes (part of IIPSecObjectImpl)
  18. Arguments:
  19. None.
  20. Return Value:
  21. Success:
  22. (1) WBEM_NO_ERROR if instances are returned;
  23. (2) WBEM_S_NO_MORE_DATA if no instances are returned.
  24. Failure:
  25. Various errors may occur. We return various error code to indicate such errors.
  26. Notes:
  27. */
  28. STDMETHODIMP
  29. CMMPolicy::QueryInstance (
  30. IN LPCWSTR pszQuery,
  31. IN IWbemContext * pCtx,
  32. IN IWbemObjectSink * pSink
  33. )
  34. {
  35. //
  36. // get the policy name from the query
  37. // the given key chain doesn't know anything about where clause property should be policy name
  38. // so make another one ourselves.
  39. //
  40. m_srpKeyChain.Release();
  41. HRESULT hr = CNetSecProv::GetKeyChainFromQuery(pszQuery, g_pszPolicyName, &m_srpKeyChain);
  42. if (FAILED(hr))
  43. {
  44. return hr;
  45. }
  46. CComVariant varPolicyName;
  47. //
  48. // GetKeyPropertyValue will return WBEM_S_FALSE in case the requested key property
  49. // is not found. That is fine because we are querying.
  50. //
  51. hr = m_srpKeyChain->GetKeyPropertyValue(g_pszPolicyName, &varPolicyName);
  52. LPCWSTR pszPolicyName = (varPolicyName.vt == VT_BSTR) ? varPolicyName.bstrVal : NULL;
  53. //
  54. // let's enumerate all fitting policies
  55. //
  56. DWORD dwResumeHandle = 0;
  57. PIPSEC_MM_POLICY pMMPolicy = NULL;
  58. hr = FindPolicyByName(pszPolicyName, &pMMPolicy, &dwResumeHandle);
  59. while (SUCCEEDED(hr))
  60. {
  61. CComPtr<IWbemClassObject> srpObj;
  62. hr = CreateWbemObjFromMMPolicy(pMMPolicy, &srpObj);
  63. //
  64. // we have successfully created a policy, give it to WMI.
  65. //
  66. if (SUCCEEDED(hr))
  67. {
  68. pSink->Indicate(1, &srpObj);
  69. }
  70. ::SPDApiBufferFree(pMMPolicy);
  71. pMMPolicy = NULL;
  72. hr = FindPolicyByName(pszPolicyName, &pMMPolicy, &dwResumeHandle);
  73. }
  74. //
  75. // since we are querying, it's ok to return not found
  76. //
  77. if (WBEM_E_NOT_FOUND == hr)
  78. {
  79. hr = WBEM_S_NO_MORE_DATA;
  80. }
  81. else if (SUCCEEDED(hr))
  82. {
  83. hr = WBEM_NO_ERROR;
  84. }
  85. return hr;
  86. }
  87. /*
  88. Routine Description:
  89. Name:
  90. CMMPolicy::DeleteInstance
  91. Functionality:
  92. Will delete the wbem object, which will cause the main mode policy to be deleted.
  93. Virtual:
  94. Yes (part of IIPSecObjectImpl)
  95. Arguments:
  96. pCtx - COM interface pointer given by WMI and needed for various WMI APIs.
  97. pSink - COM interface pointer to notify WMI of any created objects.
  98. Return Value:
  99. Success:
  100. WBEM_NO_ERROR;
  101. Failure:
  102. (1) WBEM_E_NOT_FOUND. Whether or not this should be considered an error
  103. depends on context.
  104. (2) Other errors indicating the cause.
  105. Notes:
  106. */
  107. STDMETHODIMP
  108. CMMPolicy::DeleteInstance (
  109. IN IWbemContext * pCtx,
  110. IN IWbemObjectSink * pSink
  111. )
  112. {
  113. CComVariant varPolicyName;
  114. HRESULT hr = m_srpKeyChain->GetKeyPropertyValue(g_pszPolicyName, &varPolicyName);
  115. if (FAILED(hr))
  116. {
  117. return hr;
  118. }
  119. else if (varPolicyName.vt != VT_BSTR || varPolicyName.bstrVal == NULL || varPolicyName.bstrVal[0] == L'\0')
  120. {
  121. return WBEM_E_NOT_FOUND;
  122. }
  123. return DeletePolicy(varPolicyName.bstrVal);
  124. }
  125. /*
  126. Routine Description:
  127. Name:
  128. CMMPolicy::PutInstance
  129. Functionality:
  130. Put a main mode policy into SPD whose properties are represented by the
  131. wbem object.
  132. Virtual:
  133. Yes (part of IIPSecObjectImpl)
  134. Arguments:
  135. pInst - The wbem object.
  136. pCtx - COM interface pointer given by WMI and needed for various WMI APIs.
  137. pSink - COM interface pointer to notify WMI of results.
  138. Return Value:
  139. Success:
  140. WBEM_NO_ERROR
  141. Failure:
  142. Various error codes specifying the error.
  143. Notes:
  144. */
  145. STDMETHODIMP
  146. CMMPolicy::PutInstance (
  147. IN IWbemClassObject * pInst,
  148. IN IWbemContext * pCtx,
  149. IN IWbemObjectSink * pSink
  150. )
  151. {
  152. if (pInst == NULL || pSink == NULL)
  153. {
  154. return WBEM_E_INVALID_PARAMETER;
  155. }
  156. bool bPreExist = false;
  157. //
  158. // for those policies that are created by ourselves (bPreExist == true)
  159. // we have our own way of allocating the buffer, need to free it in our corresponding way
  160. //
  161. PIPSEC_MM_POLICY pPolicy = NULL;
  162. HRESULT hr = GetMMPolicyFromWbemObj(pInst, &pPolicy, &bPreExist);
  163. //
  164. // if policy is successfully returned, then use it
  165. //
  166. if (SUCCEEDED(hr) && pPolicy)
  167. {
  168. hr = AddPolicy(bPreExist, pPolicy);
  169. //
  170. // deposit info about this action so that we can rollback
  171. //
  172. if (SUCCEEDED(hr))
  173. {
  174. hr = OnAfterAddPolicy(pPolicy->pszPolicyName, MainMode_Policy);
  175. }
  176. FreePolicy(&pPolicy, bPreExist);
  177. }
  178. return hr;
  179. }
  180. /*
  181. Routine Description:
  182. Name:
  183. CMMPolicy::GetInstance
  184. Functionality:
  185. Create a wbem object by the given key properties (already captured by our key chain object)..
  186. Virtual:
  187. Yes (part of IIPSecObjectImpl)
  188. Arguments:
  189. pCtx - COM interface pointer given by WMI and needed for various WMI APIs.
  190. pSink - COM interface pointer to notify WMI of any created objects.
  191. Return Value:
  192. Success:
  193. Success code. Use SUCCEEDED(hr) to test.
  194. Failure:
  195. (1) WBEM_E_NOT_FOUND if the auth method can't be found. Depending on
  196. the context, this may not be an error
  197. (2) Other various errors indicated by the returned error codes.
  198. Notes:
  199. */
  200. STDMETHODIMP
  201. CMMPolicy::GetInstance (
  202. IN IWbemContext * pCtx,
  203. IN IWbemObjectSink * pSink
  204. )
  205. {
  206. CComVariant varPolicyName;
  207. //
  208. // since policy name is a key property, it must have policy name property in the path and thus in the key chain
  209. //
  210. HRESULT hr = m_srpKeyChain->GetKeyPropertyValue(g_pszPolicyName, &varPolicyName);
  211. if (FAILED(hr))
  212. {
  213. return hr;
  214. }
  215. else if (varPolicyName.vt != VT_BSTR || varPolicyName.bstrVal == NULL || varPolicyName.bstrVal[0] == L'\0')
  216. {
  217. return WBEM_E_NOT_FOUND;
  218. }
  219. PIPSEC_MM_POLICY pMMPolicy = NULL;
  220. DWORD dwResumeHandle = 0;
  221. hr = FindPolicyByName(varPolicyName.bstrVal, &pMMPolicy, &dwResumeHandle);
  222. if (SUCCEEDED(hr))
  223. {
  224. CComPtr<IWbemClassObject> srpObj;
  225. hr = CreateWbemObjFromMMPolicy(pMMPolicy, &srpObj);
  226. if (SUCCEEDED(hr))
  227. {
  228. hr = pSink->Indicate(1, &srpObj);
  229. }
  230. ::SPDApiBufferFree(pMMPolicy);
  231. }
  232. return hr;
  233. }
  234. /*
  235. Routine Description:
  236. Name:
  237. CMMPolicy::CreateWbemObjFromMMPolicy
  238. Functionality:
  239. Given a SPD's main mode policy, we will create a wbem object representing it.
  240. Virtual:
  241. No.
  242. Arguments:
  243. pPolicy - The SPD's main mode policy object.
  244. ppObj - Receives the wbem object.
  245. Return Value:
  246. Success:
  247. WBEM_NO_ERROR
  248. Failure:
  249. (1) various errors indicated by the returned error codes.
  250. Notes:
  251. */
  252. HRESULT
  253. CMMPolicy::CreateWbemObjFromMMPolicy (
  254. IN PIPSEC_MM_POLICY pPolicy,
  255. OUT IWbemClassObject ** ppObj
  256. )
  257. {
  258. if (pPolicy == NULL || ppObj == NULL)
  259. {
  260. return WBEM_E_INVALID_PARAMETER;
  261. }
  262. *ppObj = NULL;
  263. //
  264. // create a wbem object of this class that can be used to fill in properties
  265. //
  266. HRESULT hr = SpawnObjectInstance(ppObj);
  267. if (SUCCEEDED(hr))
  268. {
  269. //
  270. // fill in base class members (CIPSecPolicy)
  271. //
  272. hr = CreateWbemObjFromPolicy(pPolicy, *ppObj);
  273. CComVariant var;
  274. //
  275. // put MM policy specific members
  276. //
  277. //
  278. // SoftSAExpTime
  279. //
  280. if (SUCCEEDED(hr))
  281. {
  282. var.Clear();
  283. var.vt = VT_I4;
  284. var.lVal = pPolicy->uSoftSAExpirationTime;
  285. hr = (*ppObj)->Put(g_pszSoftSAExpTime, 0, &var, CIM_EMPTY);
  286. }
  287. //
  288. // now, all those array data
  289. //
  290. if (SUCCEEDED(hr))
  291. {
  292. var.vt = VT_ARRAY | VT_I4;
  293. SAFEARRAYBOUND rgsabound[1];
  294. rgsabound[0].lLbound = 0;
  295. rgsabound[0].cElements = pPolicy->dwOfferCount;
  296. var.parray = ::SafeArrayCreate(VT_I4, 1, rgsabound);
  297. if (var.parray == NULL)
  298. {
  299. hr = WBEM_E_OUT_OF_MEMORY;
  300. }
  301. else
  302. {
  303. long lIndecies[1];
  304. //
  305. //$undone:shawnwu, we need to write some generic routine to do this repeatitive array
  306. // put (and get). That routine can only be based on memory offset!
  307. //
  308. //
  309. // put dwQuickModeLimit
  310. //
  311. for (DWORD dwIndex = 0; SUCCEEDED(hr) && dwIndex < pPolicy->dwOfferCount; dwIndex++)
  312. {
  313. lIndecies[0] = dwIndex;
  314. hr = ::SafeArrayPutElement(var.parray, lIndecies, &(pPolicy->pOffers[dwIndex].dwQuickModeLimit) );
  315. }
  316. if (SUCCEEDED(hr))
  317. {
  318. hr = (*ppObj)->Put(g_pszQMLimit, 0, &var, CIM_EMPTY);
  319. }
  320. //
  321. // put dwDHGroup
  322. //
  323. for (dwIndex = 0; SUCCEEDED(hr) && dwIndex < pPolicy->dwOfferCount; dwIndex++)
  324. {
  325. lIndecies[0] = dwIndex;
  326. hr = ::SafeArrayPutElement(var.parray, lIndecies, &(pPolicy->pOffers[dwIndex].dwDHGroup) );
  327. }
  328. if (SUCCEEDED(hr))
  329. {
  330. hr = (*ppObj)->Put(g_pszDHGroup, 0, &var, CIM_EMPTY);
  331. }
  332. //
  333. // put EncryptionAlgorithm.uAlgoIdentifier
  334. //
  335. for (dwIndex = 0; SUCCEEDED(hr) && dwIndex < pPolicy->dwOfferCount; dwIndex++)
  336. {
  337. lIndecies[0] = dwIndex;
  338. hr = ::SafeArrayPutElement(var.parray, lIndecies, &(pPolicy->pOffers[dwIndex].EncryptionAlgorithm.uAlgoIdentifier) );
  339. }
  340. if (SUCCEEDED(hr))
  341. {
  342. hr = (*ppObj)->Put(g_pszEncryptID, 0, &var, CIM_EMPTY);
  343. }
  344. //
  345. // put HashingAlgorithm.uAlgoIdentifier
  346. //
  347. for (dwIndex = 0; SUCCEEDED(hr) && dwIndex < pPolicy->dwOfferCount; dwIndex++)
  348. {
  349. lIndecies[0] = dwIndex;
  350. hr = ::SafeArrayPutElement(var.parray, lIndecies, &(pPolicy->pOffers[dwIndex].HashingAlgorithm.uAlgoIdentifier) );
  351. }
  352. if (SUCCEEDED(hr))
  353. {
  354. hr = (*ppObj)->Put(g_pszHashID, 0, &var, CIM_EMPTY);
  355. }
  356. }
  357. }
  358. }
  359. //
  360. // we may have created the object, but some mid steps have failed,
  361. // so let's release the object.
  362. //
  363. if (FAILED(hr) && *ppObj != NULL)
  364. {
  365. (*ppObj)->Release();
  366. *ppObj = NULL;
  367. }
  368. return SUCCEEDED(hr) ? WBEM_NO_ERROR : hr;
  369. }
  370. /*
  371. Routine Description:
  372. Name:
  373. CMMPolicy::GetMMPolicyFromWbemObj
  374. Functionality:
  375. Will try to get the main mode policy if such policy already exists.
  376. Otherwise, we will create a new one.
  377. Virtual:
  378. No.
  379. Arguments:
  380. pInst - The wbem object object.
  381. ppPolicy - Receives the quick mode policy.
  382. pbPreExist - Receives the information whether this object memory is allocated by SPD or not.
  383. Return Value:
  384. Success:
  385. WBEM_NO_ERROR
  386. Failure:
  387. (1) various errors indicated by the returned error codes.
  388. Notes:
  389. if some property is missing from the wbem object, we will supply the default value.
  390. */
  391. HRESULT
  392. CMMPolicy::GetMMPolicyFromWbemObj (
  393. IN IWbemClassObject * pInst,
  394. OUT PIPSEC_MM_POLICY * ppPolicy,
  395. OUT bool * pbPreExist
  396. )
  397. {
  398. if (pInst == NULL || ppPolicy == NULL || pbPreExist == NULL)
  399. {
  400. return WBEM_E_INVALID_PARAMETER;
  401. }
  402. *ppPolicy = NULL;
  403. *pbPreExist = false;
  404. HRESULT hr = GetPolicyFromWbemObj(pInst, ppPolicy, pbPreExist);
  405. //
  406. // we don't support modification on existing policies
  407. //
  408. if (SUCCEEDED(hr) && *pbPreExist == false)
  409. {
  410. CComVariant var;
  411. //
  412. // set uSoftSAExpirationTime
  413. //
  414. if (SUCCEEDED(pInst->Get(g_pszSoftSAExpTime, 0, &var, NULL, NULL)) && var.vt == VT_I4)
  415. {
  416. (*ppPolicy)->uSoftSAExpirationTime = var.lVal;
  417. }
  418. else
  419. {
  420. (*ppPolicy)->uSoftSAExpirationTime = DefaultaultSoftSAExpirationTime;
  421. }
  422. //
  423. // now, fill in each offer's contents
  424. //
  425. //
  426. // wbem object may not have all the properties, we will use default when a property is missing.
  427. // as a result, we don't keep the HRESULT and return it the caller
  428. //
  429. //
  430. // for readability
  431. //
  432. DWORD dwOfferCount = (*ppPolicy)->dwOfferCount;
  433. //
  434. // need to delete the memory
  435. //
  436. DWORD* pdwValues = new DWORD[dwOfferCount];
  437. long l;
  438. if (pdwValues == NULL)
  439. {
  440. hr = WBEM_E_OUT_OF_MEMORY;
  441. }
  442. else
  443. {
  444. var.Clear();
  445. //
  446. // If the property is missing from wbem object, we will initialize them to default values.
  447. //
  448. bool bInitialized = false;
  449. //
  450. // set dwQuickModeLimit
  451. //
  452. if ( SUCCEEDED(pInst->Get(g_pszQMLimit, 0, &var, NULL, NULL)) && (var.vt & VT_ARRAY) == VT_ARRAY )
  453. {
  454. if (SUCCEEDED(::GetDWORDSafeArrayElements(&var, dwOfferCount, pdwValues)))
  455. {
  456. //
  457. // we are guaranteed to get values after this point.
  458. //
  459. bInitialized = true;
  460. for (l = 0; l < dwOfferCount; l++)
  461. {
  462. (*ppPolicy)->pOffers[l].dwQuickModeLimit = pdwValues[l];
  463. }
  464. }
  465. }
  466. if (!bInitialized)
  467. {
  468. //
  469. // set to default values
  470. //
  471. for (l = 0; l < dwOfferCount; l++)
  472. {
  473. (*ppPolicy)->pOffers[l].dwQuickModeLimit = DefaultQMModeLimit;
  474. }
  475. }
  476. //
  477. // ready for next property
  478. //
  479. bInitialized = false;
  480. var.Clear();
  481. //
  482. // set dwDHGroup;
  483. //
  484. if ( SUCCEEDED(pInst->Get(g_pszDHGroup, 0, &var, NULL, NULL)) && (var.vt & VT_ARRAY) == VT_ARRAY )
  485. {
  486. if (SUCCEEDED(::GetDWORDSafeArrayElements(&var, dwOfferCount, pdwValues)))
  487. {
  488. //
  489. // we are guaranteed to get values after this point.
  490. //
  491. bInitialized = true;
  492. for (l = 0; l < dwOfferCount; l++)
  493. {
  494. (*ppPolicy)->pOffers[l].dwDHGroup = pdwValues[l];
  495. }
  496. }
  497. }
  498. if (!bInitialized)
  499. {
  500. //
  501. // set to default values
  502. //
  503. for (l = 0; l < dwOfferCount; l++)
  504. {
  505. (*ppPolicy)->pOffers[l].dwDHGroup = DefaultDHGroup;
  506. }
  507. }
  508. //
  509. // ready for next property
  510. //
  511. bInitialized = false;
  512. var.Clear();
  513. //
  514. // IPSEC_MM_ALGO EncryptionAlgorithm;
  515. //
  516. //
  517. // set EncriptionAlogirthm's uAlgoIdentifier
  518. //
  519. if ( SUCCEEDED(pInst->Get(g_pszEncryptID, 0, &var, NULL, NULL)) && (var.vt & VT_ARRAY) == VT_ARRAY )
  520. {
  521. if (SUCCEEDED(::GetDWORDSafeArrayElements(&var, dwOfferCount, pdwValues)))
  522. {
  523. //
  524. // we are guaranteed to get values after this point.
  525. //
  526. bInitialized = true;
  527. for (l = 0; l < dwOfferCount; l++)
  528. {
  529. (*ppPolicy)->pOffers[l].EncryptionAlgorithm.uAlgoIdentifier = pdwValues[l];
  530. }
  531. }
  532. }
  533. if (!bInitialized)
  534. {
  535. //
  536. // set to default values
  537. //
  538. for (l = 0; l < dwOfferCount; l++)
  539. {
  540. (*ppPolicy)->pOffers[l].EncryptionAlgorithm.uAlgoIdentifier = DefaultEncryptAlgoID;
  541. }
  542. }
  543. //
  544. // ready for next property
  545. //
  546. bInitialized = false;
  547. var.Clear();
  548. //
  549. // IPSEC_MM_ALGO HashingAlgorithm;
  550. //
  551. //
  552. // set EncriptionAlogirthm's uAlgoIdentifier
  553. //
  554. if ( SUCCEEDED(pInst->Get(g_pszEncryptID, 0, &var, NULL, NULL)) && (var.vt & VT_ARRAY) == VT_ARRAY )
  555. {
  556. if (SUCCEEDED(::GetDWORDSafeArrayElements(&var, dwOfferCount, pdwValues)))
  557. {
  558. //
  559. // we are guaranteed to get values after this point.
  560. //
  561. bInitialized = true;
  562. for (l = 0; l < dwOfferCount; l++)
  563. {
  564. (*ppPolicy)->pOffers[l].HashingAlgorithm.uAlgoIdentifier = pdwValues[l];
  565. }
  566. }
  567. }
  568. if (!bInitialized)
  569. {
  570. //
  571. // set to default values
  572. //
  573. for (l = 0; l < dwOfferCount; l++)
  574. {
  575. (*ppPolicy)->pOffers[l].HashingAlgorithm.uAlgoIdentifier = DefaultHashAlgoID;
  576. }
  577. }
  578. //
  579. // done with the array
  580. //
  581. delete [] pdwValues;
  582. }
  583. }
  584. if (FAILED(hr) && *ppPolicy != NULL)
  585. {
  586. FreePolicy(ppPolicy, *pbPreExist);
  587. }
  588. return SUCCEEDED(hr) ? WBEM_NO_ERROR : hr;
  589. }
  590. /*
  591. Routine Description:
  592. Name:
  593. CMMPolicy::AddPolicy
  594. Functionality:
  595. Add the main quick mode policy to SPD.
  596. Virtual:
  597. No.
  598. Arguments:
  599. bPreExist - Flag whether the main mode auth method already exists in SPD
  600. pQMPolicy - The quick mode policy to add.
  601. Return Value:
  602. Success:
  603. WBEM_NO_ERROR.
  604. Failure:
  605. WBEM_E_FAILED.
  606. Notes:
  607. */
  608. HRESULT
  609. CMMPolicy::AddPolicy (
  610. IN bool bPreExist,
  611. IN PIPSEC_MM_POLICY pMMPolicy
  612. )
  613. {
  614. HANDLE hFilter = NULL;
  615. DWORD dwResult = ERROR_SUCCESS;
  616. HRESULT hr = WBEM_NO_ERROR;
  617. if (bPreExist)
  618. {
  619. dwResult = ::SetMMPolicy(NULL, pMMPolicy->pszPolicyName, pMMPolicy);
  620. }
  621. else
  622. {
  623. dwResult = ::AddMMPolicy(NULL, 1, pMMPolicy);
  624. }
  625. if (dwResult != ERROR_SUCCESS)
  626. {
  627. hr = ::IPSecErrorToWbemError(dwResult);
  628. }
  629. return hr;
  630. }
  631. /*
  632. Routine Description:
  633. Name:
  634. CMMPolicy::DeletePolicy
  635. Functionality:
  636. Delete given main mode policy from SPD.
  637. Virtual:
  638. No.
  639. Arguments:
  640. pszPolicyName - The name of the policy to delete.
  641. Return Value:
  642. Success:
  643. WBEM_NO_ERROR.
  644. Failure:
  645. (1) WBEM_E_INVALID_PARAMETER: if pszPolicyName == NULL or *pszPolicyName == L'\0'.
  646. (2) WBEM_E_VETO_DELETE: if we are not allowed to delete the policy by SPD.
  647. Notes:
  648. */
  649. HRESULT
  650. CMMPolicy::DeletePolicy (
  651. IN LPCWSTR pszPolicyName
  652. )
  653. {
  654. if (pszPolicyName == NULL || *pszPolicyName == L'\0')
  655. {
  656. return WBEM_E_INVALID_PARAMETER;
  657. }
  658. HRESULT hr = WBEM_NO_ERROR;
  659. //
  660. // casting to LPWSTR is due to IPSec API's mistake
  661. //
  662. if (ERROR_SUCCESS != ::DeleteMMPolicy(NULL, (LPWSTR)pszPolicyName))
  663. {
  664. hr = WBEM_E_VETO_DELETE;
  665. }
  666. return hr;
  667. }