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.

1172 lines
41 KiB

  1. //+-------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 2000
  5. //
  6. // File: dsrm.cpp
  7. //
  8. // Contents: Defines the main function and parser tables for the dsrm
  9. // command line utility
  10. //
  11. // History: 07-Sep-2000 HiteshR Created dsmove
  12. // 13-Sep-2000 JonN Templated dsrm from dsmove
  13. // 26-Sep-2000 JonN Cleanup in several areas
  14. //
  15. //--------------------------------------------------------------------------
  16. /*
  17. Error message strategy:
  18. -- If errors occur before any particular directory object
  19. is contacted, they will be reported as "dsrm failed: <error>".
  20. -- For each target, either one or more errors will be reported,
  21. or (if "quiet" is not specified) one success will be reported.
  22. If "continue" is not specified, nothing will be reported on
  23. targets after the first one to experience an error.
  24. -- More than one error can be reported on a target, but only if
  25. the "subtree", "exclude" and "continue" flags are all specified.
  26. In this case, DSRM will continue to delete the other children
  27. of that specified target object.
  28. -- If a subtree is being deleted and the error actually relates to
  29. a child object, the error reported will reference the particular
  30. child object, rather than the specified target object.
  31. -- Failure to delete a system object will be reported as
  32. ERROR_DS_CANT_DELETE_DSA_OBJ or ERROR_DS_CANT_DELETE.
  33. -- Failure to delete the logged-in user will be reported as
  34. ERROR_DS_CANT_DELETE. This test will only be performed on the
  35. specified target object, not on any of its child objects.
  36. */
  37. #include "pch.h"
  38. #include "stdio.h"
  39. #include "cstrings.h"
  40. #include "usage.h"
  41. #include "rmtable.h"
  42. #include "resource.h" // IDS_DELETE_PROMPT[_EXCLUDE]
  43. #include <ntldap.h> // LDAP_MATCHING_RULE_BIT_AND_W
  44. #define SECURITY_WIN32
  45. #include <security.h> // GetUserNameEx
  46. //
  47. // Function Declarations
  48. //
  49. HRESULT ValidateSwitches();
  50. HRESULT DoRm( PWSTR pszDoubleNullObjectDN );
  51. HRESULT DoRmItem( CDSCmdCredentialObject& credentialObject,
  52. CDSCmdBasePathsInfo& basePathsInfo,
  53. PWSTR pszObjectDN,
  54. bool* pfErrorReported );
  55. HRESULT IsCriticalSystemObject( CDSCmdBasePathsInfo& basePathsInfo,
  56. IADs* pIADs,
  57. const BSTR pszClass,
  58. const BSTR pszObjectDN,
  59. bool* pfErrorReported );
  60. HRESULT RetrieveStringColumn( IDirectorySearch* pSearch,
  61. ADS_SEARCH_HANDLE SearchHandle,
  62. LPWSTR szColumnName,
  63. CComBSTR& sbstr );
  64. HRESULT SetSearchPreference(IDirectorySearch* piSearch, ADS_SCOPEENUM scope);
  65. HRESULT IsThisUserLoggedIn( const BSTR bstrUserDN );
  66. HRESULT DeleteChildren( CDSCmdCredentialObject& credentialObject,
  67. IADs* pIADs,
  68. bool* pfErrorReported );
  69. //
  70. // Global variables
  71. //
  72. BOOL fSubtree = false; // BOOL is used in parser structure
  73. BOOL fExclude = false;
  74. BOOL fContinue = false;
  75. BOOL fQuiet = false;
  76. BOOL fNoPrompt = false;
  77. LPWSTR g_lpszLoggedInUser = NULL;
  78. //
  79. //Usage Table
  80. //
  81. UINT USAGE_DSRM[] =
  82. {
  83. USAGE_DSRM_DESCRIPTION,
  84. USAGE_DSRM_SYNTAX,
  85. USAGE_DSRM_PARAMETERS,
  86. USAGE_DSRM_REMARKS,
  87. USAGE_DSRM_EXAMPLES,
  88. USAGE_END,
  89. };
  90. //+--------------------------------------------------------------------------
  91. //
  92. // Function: _tmain
  93. //
  94. // Synopsis: Main function for command-line app
  95. // beyond what parser can do.
  96. //
  97. // Returns: int : HRESULT to be returned from command-line app
  98. //
  99. // History: 07-Sep-2000 HiteshR Created dsmove
  100. // 13-Sep-2000 JonN Templated from dsmove
  101. // 26-Sep-2000 JonN Updated error reporting
  102. //
  103. //---------------------------------------------------------------------------
  104. int __cdecl _tmain( VOID )
  105. {
  106. int argc = 0;
  107. LPTOKEN pToken = NULL;
  108. HRESULT hr = S_OK;
  109. hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  110. if (FAILED(hr))
  111. {
  112. DisplayErrorMessage(g_pszDSCommandName, NULL, hr);
  113. goto exit_gracefully;
  114. }
  115. DWORD _dwErr = GetCommandInput(&argc,&pToken);
  116. hr = HRESULT_FROM_WIN32( _dwErr );
  117. if (FAILED(hr) || argc == 1)
  118. {
  119. if (FAILED(hr)) // JonN 3/27/01 344920
  120. DisplayErrorMessage( g_pszDSCommandName, NULL, hr );
  121. //if argc is 1, show usage in STDOUT
  122. DisplayMessage(USAGE_DSRM,(argc == 1));
  123. goto exit_gracefully;
  124. }
  125. PARSE_ERROR Error;
  126. //Security Review:Correct Bound is passed.
  127. ::ZeroMemory( &Error, sizeof(Error) );
  128. if(!ParseCmd(g_pszDSCommandName,
  129. DSRM_COMMON_COMMANDS,
  130. argc-1,
  131. pToken+1,
  132. USAGE_DSRM,
  133. &Error,
  134. TRUE))
  135. {
  136. //ParseCmd did not display any error. Error should
  137. //be handled here. Check DisplayParseError for the
  138. //cases where Error is not shown by ParseCmd
  139. if(!Error.MessageShown)
  140. {
  141. hr = E_INVALIDARG;
  142. DisplayErrorMessage(g_pszDSCommandName,
  143. NULL,
  144. hr);
  145. goto exit_gracefully;
  146. }
  147. if(Error.ErrorSource == ERROR_FROM_PARSER
  148. && Error.Error == PARSE_ERROR_HELP_SWITCH)
  149. {
  150. hr = S_OK;
  151. goto exit_gracefully;
  152. }
  153. hr = E_INVALIDARG;
  154. goto exit_gracefully;
  155. }
  156. hr = ValidateSwitches();
  157. if (FAILED(hr))
  158. {
  159. DisplayMessage(USAGE_DSRM);
  160. goto exit_gracefully;
  161. }
  162. //
  163. // Command line parsing succeeded
  164. //
  165. hr = DoRm( DSRM_COMMON_COMMANDS[eCommObjectDN].strValue );
  166. exit_gracefully:
  167. // Free Command Array
  168. FreeCmd(DSRM_COMMON_COMMANDS);
  169. // Free Token
  170. if(pToken)
  171. delete []pToken;
  172. if (NULL != g_lpszLoggedInUser)
  173. delete[] g_lpszLoggedInUser;
  174. //
  175. // Uninitialize COM
  176. //
  177. CoUninitialize();
  178. return hr;
  179. }
  180. //+--------------------------------------------------------------------------
  181. //
  182. // Function: ValidateSwitches
  183. //
  184. // Synopsis: Does advanced switch dependency validation
  185. // beyond what parser can do.
  186. //
  187. // Arguments:
  188. //
  189. // Returns: S_OK or E_INVALIDARG
  190. //
  191. // History: 07-Sep-2000 HiteshR Created dsmove
  192. // 13-Sep-2000 JonN Templated from dsmove
  193. // 26-Sep-2000 JonN Updated error reporting
  194. //
  195. //---------------------------------------------------------------------------
  196. HRESULT ValidateSwitches()
  197. {
  198. // Check to be sure the server and domain switches
  199. // are mutually exclusive
  200. if (DSRM_COMMON_COMMANDS[eCommServer].bDefined &&
  201. DSRM_COMMON_COMMANDS[eCommDomain].bDefined)
  202. {
  203. return E_INVALIDARG;
  204. }
  205. // read subtree parameters
  206. fSubtree = DSRM_COMMON_COMMANDS[eCommSubtree].bDefined;
  207. fExclude = DSRM_COMMON_COMMANDS[eCommExclude].bDefined;
  208. fContinue = DSRM_COMMON_COMMANDS[eCommContinue].bDefined;
  209. fQuiet = DSRM_COMMON_COMMANDS[eCommQuiet].bDefined;
  210. fNoPrompt = DSRM_COMMON_COMMANDS[eCommNoPrompt].bDefined;
  211. if ( NULL == DSRM_COMMON_COMMANDS[eCommObjectDN].strValue
  212. || L'\0' == DSRM_COMMON_COMMANDS[eCommObjectDN].strValue[0]
  213. || (fExclude && !fSubtree) )
  214. {
  215. DEBUG_OUTPUT(MINIMAL_LOGGING, L"ValidateSwitches: Invalid switches");
  216. return E_INVALIDARG;
  217. }
  218. return S_OK;
  219. }
  220. //+--------------------------------------------------------------------------
  221. //
  222. // Function: DoRm
  223. //
  224. // Synopsis: Deletes the appropriate object(s)
  225. // DoRm reports its own error and success messages
  226. //
  227. // Arguments: pszDoubleNullObjectDN: double-null-terminated stringlist
  228. //
  229. // Returns: HRESULT : error code to be returned from command-line app
  230. // Could be almost any ADSI error
  231. //
  232. // History: 13-Sep-2000 JonN templated from DoMove
  233. // 26-Sep-2000 JonN Updated error reporting
  234. //
  235. //---------------------------------------------------------------------------
  236. HRESULT DoRm( PWSTR pszDoubleNullObjectDN )
  237. {
  238. ASSERT( NULL != pszDoubleNullObjectDN
  239. && L'\0' != *pszDoubleNullObjectDN );
  240. //
  241. // Check to see if we are doing debug spew
  242. //
  243. #ifdef DBG
  244. bool bDebugging = DSRM_COMMON_COMMANDS[eCommDebug].bDefined &&
  245. DSRM_COMMON_COMMANDS[eCommDebug].nValue;
  246. if (bDebugging)
  247. {
  248. ENABLE_DEBUG_OUTPUT(DSRM_COMMON_COMMANDS[eCommDebug].nValue);
  249. }
  250. #else
  251. DISABLE_DEBUG_OUTPUT();
  252. #endif
  253. ENTER_FUNCTION(MINIMAL_LOGGING, DoRm);
  254. HRESULT hr = S_OK;
  255. CDSCmdCredentialObject credentialObject;
  256. if (DSRM_COMMON_COMMANDS[eCommUserName].bDefined &&
  257. DSRM_COMMON_COMMANDS[eCommUserName].strValue)
  258. {
  259. credentialObject.SetUsername(
  260. DSRM_COMMON_COMMANDS[eCommUserName].strValue);
  261. credentialObject.SetUsingCredentials(true);
  262. }
  263. if (DSRM_COMMON_COMMANDS[eCommPassword].bDefined &&
  264. DSRM_COMMON_COMMANDS[eCommPassword].strValue)
  265. {
  266. //Security Review:pCommandArgs[eCommPassword].strValue is encrypted.
  267. //Decrypt pCommandArgs[eCommPassword].strValue and then pass it to the
  268. //credentialObject.SetPassword.
  269. //See NTRAID#NTBUG9-571544-2000/11/13-hiteshr
  270. credentialObject.SetEncryptedPassword(&DSRM_COMMON_COMMANDS[eCommPassword].encryptedDataBlob);
  271. credentialObject.SetUsingCredentials(true);
  272. }
  273. //
  274. // Initialize the base paths info from the command line args
  275. //
  276. // CODEWORK should I just make this global?
  277. CDSCmdBasePathsInfo basePathsInfo;
  278. if (DSRM_COMMON_COMMANDS[eCommServer].bDefined &&
  279. DSRM_COMMON_COMMANDS[eCommServer].strValue)
  280. {
  281. hr = basePathsInfo.InitializeFromName(
  282. credentialObject,
  283. DSRM_COMMON_COMMANDS[eCommServer].strValue,
  284. true);
  285. }
  286. else if (DSRM_COMMON_COMMANDS[eCommDomain].bDefined &&
  287. DSRM_COMMON_COMMANDS[eCommDomain].strValue)
  288. {
  289. hr = basePathsInfo.InitializeFromName(
  290. credentialObject,
  291. DSRM_COMMON_COMMANDS[eCommDomain].strValue);
  292. }
  293. else
  294. {
  295. hr = basePathsInfo.InitializeFromName(credentialObject, NULL);
  296. }
  297. if (FAILED(hr))
  298. {
  299. //
  300. // Display error message and return
  301. //
  302. DEBUG_OUTPUT(MINIMAL_LOGGING,
  303. L"DoRm: InitializeFromName failure: 0x%08x",
  304. hr);
  305. DisplayErrorMessage(g_pszDSCommandName, NULL, hr);
  306. return hr;
  307. }
  308. // count through double-NULL-terminated string list
  309. //Security Review:pszDoubleNullObjectDN is series of string separated by '\0'
  310. //and last string is terminated by two '\0'.This code is fine.
  311. for ( ;
  312. L'\0' != *pszDoubleNullObjectDN;
  313. pszDoubleNullObjectDN += (wcslen(pszDoubleNullObjectDN)+1) )
  314. {
  315. bool fErrorReported = false;
  316. // return the error value for the first error encountered
  317. HRESULT hrThisItem = DoRmItem( credentialObject,
  318. basePathsInfo,
  319. pszDoubleNullObjectDN,
  320. &fErrorReported );
  321. if (FAILED(hrThisItem))
  322. {
  323. if (!FAILED(hr))
  324. hr = hrThisItem;
  325. if (!fErrorReported)
  326. DisplayErrorMessage(g_pszDSCommandName,
  327. pszDoubleNullObjectDN,
  328. hrThisItem);
  329. if (fContinue)
  330. continue;
  331. else
  332. break;
  333. }
  334. // display success message for each individual deletion
  335. if (!fQuiet && S_FALSE != hrThisItem)
  336. {
  337. DisplaySuccessMessage(g_pszDSCommandName,
  338. pszDoubleNullObjectDN);
  339. }
  340. }
  341. return hr;
  342. }
  343. //+--------------------------------------------------------------------------
  344. //
  345. // Function: DoRmItem
  346. //
  347. // Synopsis: Deletes a single target
  348. //
  349. // Arguments: credentialObject
  350. // basePathsInfo
  351. // pszObjectDN: X500 DN of object to delete
  352. // *pfErrorReported: Will be set to true if DoRmItem takes
  353. // care of reporting the error itself
  354. //
  355. // Returns: HRESULT : error code to be returned from command-line app
  356. // Could be almost any ADSI error
  357. // S_FALSE indicates the operation was cancelled
  358. //
  359. // History: 13-Sep-2000 JonN Created
  360. // 26-Sep-2000 JonN Updated error reporting
  361. //
  362. //---------------------------------------------------------------------------
  363. HRESULT DoRmItem( CDSCmdCredentialObject& credentialObject,
  364. CDSCmdBasePathsInfo& basePathsInfo,
  365. PWSTR pszObjectDN,
  366. bool* pfErrorReported )
  367. {
  368. ASSERT( NULL != pszObjectDN
  369. && L'\0' != *pszObjectDN
  370. && NULL != pfErrorReported );
  371. ENTER_FUNCTION(LEVEL3_LOGGING, DoRmItem);
  372. HRESULT hr = S_OK;
  373. CComBSTR sbstrADsPath;
  374. basePathsInfo.ComposePathFromDN(pszObjectDN,sbstrADsPath);
  375. CComPtr<IADs> spIADsItem;
  376. hr = DSCmdOpenObject(credentialObject,
  377. sbstrADsPath,
  378. IID_IADs,
  379. (void**)&spIADsItem,
  380. true);
  381. if (FAILED(hr))
  382. {
  383. DEBUG_OUTPUT(MINIMAL_LOGGING,
  384. L"DoRmItem(%s): DsCmdOpenObject failure: 0x%08x",
  385. sbstrADsPath, hr);
  386. return hr;
  387. }
  388. ASSERT( !!spIADsItem );
  389. // CODEWORK Is this a remote LDAP operation, or does the ADsOpenObject
  390. // already retrieve the class? I can bundle the class retrieval into
  391. // the IsCriticalSystemObject search if necessary.
  392. CComBSTR sbstrClass;
  393. hr = spIADsItem->get_Class( &sbstrClass );
  394. if (FAILED(hr))
  395. {
  396. DEBUG_OUTPUT(MINIMAL_LOGGING,
  397. L"DoRmItem(%s): get_class failure: 0x%08x",
  398. sbstrADsPath, hr);
  399. return hr;
  400. }
  401. ASSERT( !!sbstrClass && L'\0' != sbstrClass[0] );
  402. // Check whether this is a critical system object
  403. // This method will report its own errors
  404. hr = IsCriticalSystemObject( basePathsInfo,
  405. spIADsItem,
  406. sbstrClass,
  407. CComBSTR(pszObjectDN),
  408. pfErrorReported );
  409. if (FAILED(hr))
  410. return hr;
  411. if (!fNoPrompt)
  412. {
  413. while (true)
  414. {
  415. // display prompt
  416. // CODEWORK allow "a" for all?
  417. CComBSTR sbstrPrompt;
  418. if (!sbstrPrompt.LoadString(
  419. ::GetModuleHandle(NULL),
  420. (fExclude) ? IDS_DELETE_PROMPT_EXCLUDE
  421. : IDS_DELETE_PROMPT))
  422. {
  423. ASSERT(FALSE);
  424. sbstrPrompt = (fExclude)
  425. ? L"Are you sure you wish to delete %1 (Y/N)? "
  426. : L"Are you sure you wish to delete all children of %1 (Y/N)? ";
  427. }
  428. // 476225-2002/04/26-JonN escaped output
  429. CComBSTR sbstrOutputDN;
  430. hr = GetOutputDN( &sbstrOutputDN, pszObjectDN );
  431. PWSTR pszOutputDN = (SUCCEEDED(hr)) ? sbstrOutputDN : pszObjectDN;
  432. PTSTR ptzSysMsg = NULL;
  433. //Security Review:FormatMessage is called with FORMAT_MESSAGE_ALLOCATE_BUFFER
  434. //so the buffer is correctly allocated by the API.
  435. DWORD cch = ::FormatMessage(
  436. FORMAT_MESSAGE_ALLOCATE_BUFFER
  437. | FORMAT_MESSAGE_FROM_STRING
  438. | FORMAT_MESSAGE_ARGUMENT_ARRAY,
  439. sbstrPrompt,
  440. 0,
  441. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  442. (LPTSTR)&ptzSysMsg,
  443. 0,
  444. (va_list*)&pszOutputDN );
  445. if (0 == cch)
  446. {
  447. DWORD _dwErr = GetLastError();
  448. DEBUG_OUTPUT(MINIMAL_LOGGING,
  449. L"DoRmItem(%s): FormatMessage failure: %d",
  450. sbstrADsPath, _dwErr);
  451. return HRESULT_FROM_WIN32( _dwErr );
  452. }
  453. DisplayOutputNoNewline( ptzSysMsg );
  454. (void) ::LocalFree( ptzSysMsg );
  455. // read a line of console input
  456. WCHAR ach[129];
  457. //Security Review:ZermoMemory takes number of bytes. This is correct.
  458. ::ZeroMemory( ach, sizeof(ach) );
  459. DWORD cchRead = 0;
  460. //Security Review: Bound is fine here. User input is handled correctly.
  461. if (!ReadConsole(GetStdHandle(STD_INPUT_HANDLE),
  462. ach,
  463. 128,
  464. &cchRead,
  465. NULL))
  466. {
  467. DWORD dwErr = ::GetLastError();
  468. if (ERROR_INSUFFICIENT_BUFFER == dwErr)
  469. continue;
  470. DEBUG_OUTPUT(MINIMAL_LOGGING,
  471. L"DoRmItem(%s): ReadConsole failure: %d",
  472. sbstrADsPath, dwErr);
  473. return HRESULT_FROM_WIN32(dwErr);
  474. }
  475. if (cchRead < 1)
  476. continue;
  477. CComBSTR sbstrY;
  478. if (!sbstrY.LoadString(
  479. ::GetModuleHandle(NULL),
  480. IDS_DELETE_RESPONSE_Y ))
  481. {
  482. sbstrY = L"Y";
  483. }
  484. CComBSTR sbstrN;
  485. if (!sbstrN.LoadString(
  486. ::GetModuleHandle(NULL),
  487. IDS_DELETE_RESPONSE_N ))
  488. {
  489. sbstrN = L"N";
  490. }
  491. // return S_FALSE if user types 'n'
  492. WCHAR wchUpper = (WCHAR)::CharUpper( (LPTSTR)(ach[0]) );
  493. if (NULL != wcschr(sbstrN,wchUpper))
  494. return S_FALSE;
  495. else if (NULL != wcschr(sbstrY,wchUpper))
  496. break;
  497. // loop back to prompt
  498. }
  499. }
  500. if (fExclude)
  501. {
  502. return DeleteChildren( credentialObject, spIADsItem, pfErrorReported );
  503. }
  504. else if (fSubtree)
  505. {
  506. // delete the whole subtree
  507. CComQIPtr<IADsDeleteOps> spDeleteOps( spIADsItem );
  508. ASSERT( !!spDeleteOps );
  509. if ( !spDeleteOps )
  510. {
  511. DEBUG_OUTPUT(MINIMAL_LOGGING,
  512. L"DoRmItem(%s): IADsDeleteOps init failure",
  513. sbstrADsPath);
  514. return E_FAIL;
  515. }
  516. hr = spDeleteOps->DeleteObject( NULL );
  517. if (FAILED(hr))
  518. {
  519. DEBUG_OUTPUT(MINIMAL_LOGGING,
  520. L"DoRmItem(%s): DeleteObject failure: 0x%08x",
  521. sbstrADsPath, hr);
  522. }
  523. else
  524. {
  525. DEBUG_OUTPUT(FULL_LOGGING,
  526. L"DoRmItem(%s): DeleteObject succeeds: 0x%08x",
  527. sbstrADsPath, hr);
  528. }
  529. return hr;
  530. }
  531. // Single-object deletion
  532. // get IADsContainer for parent object
  533. CComBSTR sbstrParentObjectPath;
  534. hr = spIADsItem->get_Parent( &sbstrParentObjectPath );
  535. if (FAILED(hr))
  536. {
  537. DEBUG_OUTPUT(MINIMAL_LOGGING,
  538. L"DoRmItem(%s): get_Parent failure: 0x%08x",
  539. sbstrADsPath, hr);
  540. return hr;
  541. }
  542. ASSERT( !!sbstrParentObjectPath
  543. && L'\0' != sbstrParentObjectPath[0] );
  544. CComPtr<IADsContainer> spDsContainer;
  545. hr = DSCmdOpenObject(credentialObject,
  546. sbstrParentObjectPath,
  547. IID_IADsContainer,
  548. (void**)&spDsContainer,
  549. true);
  550. if (FAILED(hr))
  551. {
  552. DEBUG_OUTPUT(MINIMAL_LOGGING,
  553. L"DoRmItem(%s): DSCmdOpenObject failure: 0x%08x",
  554. sbstrParentObjectPath, hr);
  555. return hr;
  556. }
  557. ASSERT( !!spDsContainer );
  558. // get leaf name
  559. CComBSTR sbstrLeafWithDecoration; // will contain "CN="
  560. CPathCracker pathCracker;
  561. hr = pathCracker.Set(CComBSTR(pszObjectDN), ADS_SETTYPE_DN);
  562. ASSERT(!FAILED(hr));
  563. if (FAILED(hr))
  564. return hr;
  565. hr = pathCracker.GetElement(0, &sbstrLeafWithDecoration);
  566. ASSERT(!FAILED(hr));
  567. if (FAILED(hr))
  568. return hr;
  569. ASSERT( !!sbstrLeafWithDecoration
  570. && L'\0' != sbstrLeafWithDecoration[0] );
  571. // delete just this object
  572. hr = spDsContainer->Delete( sbstrClass, sbstrLeafWithDecoration );
  573. DEBUG_OUTPUT((FAILED(hr)) ? MINIMAL_LOGGING : FULL_LOGGING,
  574. L"DoRmItem(%s): Delete(%s, %s) returns 0x%08x",
  575. sbstrADsPath, sbstrClass, sbstrLeafWithDecoration, hr);
  576. return hr;
  577. }
  578. //+--------------------------------------------------------------------------
  579. //
  580. // Function: IsCriticalSystemObject
  581. //
  582. // Synopsis: Checks whether a single target is a critical system
  583. // object or whether the subtree contains any such objects.
  584. // The root object is tested on these criteria (if "exclude"
  585. // is not specified):
  586. // (1) is of class user and represents the logged-in user
  587. // (2) is of class nTDSDSA
  588. // (3) is of class trustedDomain
  589. // (3.5) is of class interSiteTransport (212232 JonN 10/27/00)
  590. // (4) is of class computer and userAccountControl indicates
  591. // that this is a DC
  592. // The entire subtree is tested on these criteria (if "subtree"
  593. // is specified, excepting the root object if "exclude"
  594. // is specified):
  595. // (1) isCriticalSystemObject is true
  596. // (2) is of class nTDSDSA
  597. // (3) is of class trustedDomain
  598. // (3.5) is of class interSiteTransport (212232 JonN 10/27/00)
  599. // (4) is of class computer and userAccountControl indicates
  600. // that this is a DC
  601. //
  602. // Arguments: credentialObject
  603. // basePathsInfo
  604. // pszObjectDN: X500 DN of object to delete
  605. // *pfErrorReported: Will be set to true if DoRmItem takes
  606. // care of reporting the error itself
  607. //
  608. // Returns: HRESULT : error code to be returned from command-line app
  609. // Could be almost any ADSI error, although likely codes are
  610. // ERROR_DS_CANT_DELETE
  611. // ERROR_DS_CANT_DELETE_DSA_OBJ
  612. // ERROR_DS_CANT_FIND_DSA_OBJ
  613. //
  614. // History: 13-Sep-2000 JonN Created
  615. // 26-Sep-2000 JonN Updated error reporting
  616. //
  617. //---------------------------------------------------------------------------
  618. // CODEWORK This could use the pszMessage parameter to ReportErrorMessage
  619. // to provide additional details on why the object is protected.
  620. HRESULT IsCriticalSystemObject( CDSCmdBasePathsInfo& basePathsInfo,
  621. IADs* pIADs,
  622. const BSTR pszClass,
  623. const BSTR pszObjectDN,
  624. bool* pfErrorReported )
  625. {
  626. ASSERT( pIADs && pszClass && pszObjectDN && pfErrorReported );
  627. if ( !pIADs || !pszClass || !pszObjectDN || !pfErrorReported )
  628. return E_INVALIDARG;
  629. ENTER_FUNCTION(LEVEL5_LOGGING, IsCriticalSystemObject);
  630. HRESULT hr = S_OK;
  631. // Class-specific checks
  632. // Let the parent report errors on the root object
  633. if (fExclude)
  634. {
  635. // skip tests on root object, it won't be deleted anyhow
  636. }
  637. //Security Review:One of the string is constant in all the
  638. //_wcsicmp below. They are all fine.
  639. else if (!_wcsicmp(L"user",pszClass))
  640. {
  641. // CODEWORK we could do this check for the entire subtree
  642. hr = IsThisUserLoggedIn(pszObjectDN);
  643. if (FAILED(hr))
  644. {
  645. DEBUG_OUTPUT(MINIMAL_LOGGING,
  646. L"IsCriticalSystemObject(%s): User is logged in: 0x%08x",
  647. pszObjectDN, hr);
  648. return hr;
  649. }
  650. }
  651. else if (!_wcsicmp(L"nTDSDSA",pszClass))
  652. {
  653. DEBUG_OUTPUT(MINIMAL_LOGGING,
  654. L"IsCriticalSystemObject(%s): Object is an nTDSDSA",
  655. pszObjectDN);
  656. return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE_DSA_OBJ);
  657. }
  658. else if (!_wcsicmp(L"trustedDomain",pszClass))
  659. {
  660. DEBUG_OUTPUT(MINIMAL_LOGGING,
  661. L"IsCriticalSystemObject(%s): Object is a trustedDomain",
  662. pszObjectDN);
  663. return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
  664. }
  665. else if (!_wcsicmp(L"interSiteTransport",pszClass))
  666. {
  667. // 212232 JonN 10/27/00 Protect interSiteTransport objects
  668. DEBUG_OUTPUT(MINIMAL_LOGGING,
  669. L"IsCriticalSystemObject(%s): Object is an interSiteTransport",
  670. pszObjectDN);
  671. return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
  672. }
  673. else if (!_wcsicmp(L"computer",pszClass))
  674. {
  675. // Figure out if the account is a DC
  676. CComVariant Var;
  677. hr = pIADs->Get(CComBSTR(L"userAccountControl"), &Var);
  678. if ( SUCCEEDED(hr) && (Var.lVal & ADS_UF_SERVER_TRUST_ACCOUNT))
  679. {
  680. DEBUG_OUTPUT(MINIMAL_LOGGING,
  681. L"IsCriticalSystemObject(%s): Object is a DC computer object",
  682. pszObjectDN);
  683. return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE_DSA_OBJ);
  684. }
  685. }
  686. if (!fSubtree)
  687. return S_OK;
  688. // The user passed the "subtree" flag. Search the entire subtree.
  689. // Note that the checks are not identical to the single-object
  690. // checks, they generally conform to what DSADMIN/SITEREPL does.
  691. CComQIPtr<IDirectorySearch,&IID_IDirectorySearch> spSearch( pIADs );
  692. ASSERT( !!spSearch );
  693. if ( !spSearch )
  694. {
  695. DEBUG_OUTPUT(MINIMAL_LOGGING,
  696. L"IsCriticalSystemObject(%s): Failed to load IDirectorySearch",
  697. pszObjectDN);
  698. return E_FAIL;
  699. }
  700. hr = SetSearchPreference(spSearch, ADS_SCOPE_SUBTREE);
  701. ASSERT( !FAILED(hr) );
  702. if (FAILED(hr))
  703. return hr;
  704. CComBSTR sbstrDSAObjectCategory = L"CN=NTDS-DSA,";
  705. sbstrDSAObjectCategory += basePathsInfo.GetSchemaNamingContext();
  706. CComBSTR sbstrComputerObjectCategory = L"CN=Computer,";
  707. sbstrComputerObjectCategory += basePathsInfo.GetSchemaNamingContext();
  708. CComBSTR sbstrFilter;
  709. sbstrFilter = L"(|(isCriticalSystemObject=TRUE)(objectCategory=";
  710. sbstrFilter += sbstrDSAObjectCategory;
  711. sbstrFilter += L")(objectCategory=CN=Trusted-Domain,";
  712. sbstrFilter += basePathsInfo.GetSchemaNamingContext();
  713. // 212232 JonN 10/27/00 Protect interSiteTransport objects
  714. sbstrFilter += L")(objectCategory=CN=Inter-Site-Transport,";
  715. sbstrFilter += basePathsInfo.GetSchemaNamingContext();
  716. sbstrFilter += L")(&(objectCategory=";
  717. sbstrFilter += sbstrComputerObjectCategory;
  718. sbstrFilter += L")(userAccountControl:";
  719. sbstrFilter += LDAP_MATCHING_RULE_BIT_AND_W L":=8192)))";
  720. LPWSTR pAttrs[2] = { L"aDSPath",
  721. L"objectCategory"};
  722. ADS_SEARCH_HANDLE SearchHandle = NULL;
  723. hr = spSearch->ExecuteSearch (sbstrFilter,
  724. pAttrs,
  725. 2,
  726. &SearchHandle);
  727. if (FAILED(hr))
  728. {
  729. DEBUG_OUTPUT(MINIMAL_LOGGING,
  730. L"IsCriticalSystemObject(%s): Search with filter %s fails: 0x%08x",
  731. pszObjectDN, sbstrFilter, hr);
  732. return hr;
  733. }
  734. DEBUG_OUTPUT(LEVEL6_LOGGING,
  735. L"IsCriticalSystemObject(%s): Search with filter %s succeeds: 0x%08x",
  736. pszObjectDN, sbstrFilter, hr);
  737. while ( hr = spSearch->GetNextRow( SearchHandle ),
  738. SUCCEEDED(hr) && hr != S_ADS_NOMORE_ROWS )
  739. {
  740. CComBSTR sbstrADsPathThisItem;
  741. hr = RetrieveStringColumn( spSearch,
  742. SearchHandle,
  743. pAttrs[0],
  744. sbstrADsPathThisItem );
  745. ASSERT( !FAILED(hr) );
  746. if (FAILED(hr))
  747. return hr;
  748. // only compare DNs
  749. CPathCracker pathcracker;
  750. hr = pathcracker.Set( sbstrADsPathThisItem, ADS_SETTYPE_FULL );
  751. ASSERT( !FAILED(hr) ); // 571371-JonN-2002/04/08
  752. if (FAILED(hr))
  753. return hr;
  754. CComBSTR sbstrDN;
  755. hr = pathcracker.Retrieve( ADS_FORMAT_X500_DN, &sbstrDN );
  756. ASSERT( !FAILED(hr) ); // 571371-JonN-2002/04/08
  757. if (FAILED(hr))
  758. return hr;
  759. // ignore matches on root object if fExclude, it won't
  760. // be deleted anyway
  761. //Security Review:Check for the return value of pathcracker.Retrieve
  762. //if it fails we may AV. 571371-2000/11/13-hiteshr
  763. if (fExclude && !!sbstrDN && !_wcsicmp( pszObjectDN, sbstrDN ))
  764. continue;
  765. CComBSTR sbstrObjectCategory;
  766. hr = RetrieveStringColumn( spSearch,
  767. SearchHandle,
  768. pAttrs[1],
  769. sbstrObjectCategory );
  770. ASSERT( !FAILED(hr) );
  771. if (FAILED(hr))
  772. return hr;
  773. //Security Review:sbstrObjectCategory is coming from ADSI and is fine.
  774. //sbstrDSAObjectCategory are formed by adding string from ADSI to hardcoded string.
  775. //This is fine.
  776. hr = ( !_wcsicmp(sbstrObjectCategory,sbstrDSAObjectCategory)
  777. || !_wcsicmp(sbstrObjectCategory,sbstrComputerObjectCategory) )
  778. ? HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE_DSA_OBJ)
  779. : HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
  780. DisplayErrorMessage(g_pszDSCommandName,
  781. sbstrDN,
  782. hr);
  783. *pfErrorReported = TRUE;
  784. return hr; // do not permit deletion
  785. }
  786. return S_OK;
  787. }
  788. //+--------------------------------------------------------------------------
  789. //
  790. // Function: RetrieveStringColumn
  791. //
  792. // Synopsis: Extracts a string value from a SearchHandle
  793. // beyond what parser can do.
  794. //
  795. // Arguments: IDirectorySearch*
  796. // SearchHandle: must be current on an active record
  797. // szColumnName: as passed to ExecuteSearch
  798. // sbstr: returns contents of string value
  799. //
  800. // Returns: HRESULT : error code to be returned from command-line app
  801. // errors should not occur here
  802. //
  803. // History: 26-Sep-2000 JonN Created
  804. //
  805. //---------------------------------------------------------------------------
  806. HRESULT RetrieveStringColumn( IDirectorySearch* pSearch,
  807. ADS_SEARCH_HANDLE SearchHandle,
  808. LPWSTR szColumnName,
  809. CComBSTR& sbstr )
  810. {
  811. ASSERT( pSearch && szColumnName );
  812. ADS_SEARCH_COLUMN col;
  813. //Security Review:Correct size in byte is passed.
  814. ::ZeroMemory( &col, sizeof(col) );
  815. HRESULT hr = pSearch->GetColumn( SearchHandle, szColumnName, &col );
  816. ASSERT( !FAILED(hr) );
  817. if (FAILED(hr))
  818. return hr;
  819. ASSERT( col.dwNumValues == 1 );
  820. if ( col.dwNumValues != 1 )
  821. {
  822. (void) pSearch->FreeColumn( &col );
  823. return E_FAIL;
  824. }
  825. switch (col.dwADsType)
  826. {
  827. case ADSTYPE_CASE_IGNORE_STRING:
  828. sbstr = col.pADsValues[0].CaseIgnoreString;
  829. break;
  830. case ADSTYPE_DN_STRING:
  831. sbstr = col.pADsValues[0].DNString;
  832. break;
  833. default:
  834. ASSERT(FALSE);
  835. hr = E_FAIL;
  836. break;
  837. }
  838. (void) pSearch->FreeColumn( &col );
  839. return hr;
  840. }
  841. #define QUERY_PAGESIZE 50
  842. //+--------------------------------------------------------------------------
  843. //
  844. // Function: SetSearchPreference
  845. //
  846. // Synopsis: Sets default search parameters
  847. //
  848. // Arguments: IDirectorySearch*
  849. // ADS_SCOPEENUM: scope of search
  850. //
  851. // Returns: HRESULT : error code to be returned from command-line app
  852. // errors should not occur here
  853. //
  854. // History: 26-Sep-2000 JonN Created
  855. //
  856. //---------------------------------------------------------------------------
  857. HRESULT SetSearchPreference(IDirectorySearch* piSearch, ADS_SCOPEENUM scope)
  858. {
  859. if (NULL == piSearch)
  860. {
  861. ASSERT(FALSE);
  862. return E_INVALIDARG;
  863. }
  864. ADS_SEARCHPREF_INFO aSearchPref[4];
  865. aSearchPref[0].dwSearchPref = ADS_SEARCHPREF_CHASE_REFERRALS;
  866. aSearchPref[0].vValue.dwType = ADSTYPE_INTEGER;
  867. aSearchPref[0].vValue.Integer = ADS_CHASE_REFERRALS_EXTERNAL;
  868. aSearchPref[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
  869. aSearchPref[1].vValue.dwType = ADSTYPE_INTEGER;
  870. aSearchPref[1].vValue.Integer = QUERY_PAGESIZE;
  871. aSearchPref[2].dwSearchPref = ADS_SEARCHPREF_CACHE_RESULTS;
  872. aSearchPref[2].vValue.dwType = ADSTYPE_BOOLEAN;
  873. aSearchPref[2].vValue.Integer = FALSE;
  874. aSearchPref[3].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
  875. aSearchPref[3].vValue.dwType = ADSTYPE_INTEGER;
  876. aSearchPref[3].vValue.Integer = scope;
  877. return piSearch->SetSearchPreference (aSearchPref, 4);
  878. }
  879. //+--------------------------------------------------------------------------
  880. //
  881. // Function: IsThisUserLoggedIn
  882. //
  883. // Synopsis: Checks whether the object with this DN represents
  884. // the currently logged-in user
  885. //
  886. // Arguments: bstrUserDN: DN of object to check
  887. //
  888. // Returns: HRESULT : error code to be returned from command-line app
  889. // ERROR_DS_CANT_DELETE indicates that this user is logged in
  890. //
  891. // History: 26-Sep-2000 JonN Created
  892. //
  893. //---------------------------------------------------------------------------
  894. HRESULT IsThisUserLoggedIn( const BSTR bstrUserDN )
  895. {
  896. ENTER_FUNCTION(LEVEL7_LOGGING, IsThisUserLoggedIn);
  897. if (g_lpszLoggedInUser == NULL) {
  898. // get the size passing null pointer
  899. DWORD nSize = 0;
  900. // this is expected to fail
  901. if (GetUserNameEx(NameFullyQualifiedDN , NULL, &nSize))
  902. {
  903. DEBUG_OUTPUT(MINIMAL_LOGGING,
  904. L"IsThisUserLoggedIn(%s): GetUserNameEx unexpected success",
  905. bstrUserDN);
  906. return E_FAIL;
  907. }
  908. if( nSize == 0 )
  909. {
  910. // JonN 3/16/01 344862
  911. // dsrm from workgroup computer cannot remotely delete users from domain
  912. // This probably failed because the local computer is in a workgroup
  913. DEBUG_OUTPUT(MINIMAL_LOGGING,
  914. L"IsThisUserLoggedIn(%s): GetUserNameEx nSize==0",
  915. bstrUserDN);
  916. return S_OK; // allow user deletion
  917. }
  918. g_lpszLoggedInUser = new WCHAR[ nSize ];
  919. if( g_lpszLoggedInUser == NULL )
  920. {
  921. DEBUG_OUTPUT(MINIMAL_LOGGING,
  922. L"IsThisUserLoggedIn(%s): out of memory",
  923. bstrUserDN);
  924. return E_OUTOFMEMORY;
  925. }
  926. //Security Review:Correct size in byte is passed.
  927. ::ZeroMemory( g_lpszLoggedInUser, nSize*sizeof(WCHAR) );
  928. // this is expected to succeed
  929. if (!GetUserNameEx(NameFullyQualifiedDN, g_lpszLoggedInUser, &nSize ))
  930. {
  931. // JonN 3/16/01 344862
  932. // dsrm from workgroup computer cannot remotely delete users from domain
  933. // This probably failed because the local computer is in a workgroup
  934. DWORD dwErr = ::GetLastError();
  935. DEBUG_OUTPUT(MINIMAL_LOGGING,
  936. L"IsThisUserLoggedIn(%s): GetUserNameEx unexpected failure: %d",
  937. bstrUserDN, dwErr);
  938. return S_OK; // allow user deletion
  939. }
  940. }
  941. //Security Review:GetUserNameEx returns Null terminated user name.
  942. if (!_wcsicmp (g_lpszLoggedInUser, bstrUserDN))
  943. return HRESULT_FROM_WIN32(ERROR_DS_CANT_DELETE);
  944. return S_OK;
  945. }
  946. //+--------------------------------------------------------------------------
  947. //
  948. // Function: DeleteChildren
  949. //
  950. // Synopsis: Deletes only the children of a single target
  951. //
  952. // Arguments: credentialObject
  953. // basePathsInfo
  954. // pIADs: IADs pointer to the object
  955. // *pfErrorReported: Will be set to true if DeleteChildren
  956. // takes care of reporting the error itself
  957. //
  958. // Returns: HRESULT : error code to be returned from command-line app
  959. // Could be almost any ADSI error
  960. // Returns S_OK if there are no children
  961. //
  962. // History: 26-Sep-2000 JonN Created
  963. //
  964. //---------------------------------------------------------------------------
  965. HRESULT DeleteChildren( CDSCmdCredentialObject& credentialObject,
  966. IADs* pIADs,
  967. bool* pfErrorReported )
  968. {
  969. ENTER_FUNCTION(LEVEL5_LOGGING, DeleteChildren);
  970. ASSERT( pIADs && pfErrorReported );
  971. if ( !pIADs || !pfErrorReported )
  972. return E_POINTER;
  973. CComQIPtr<IDirectorySearch,&IID_IDirectorySearch> spSearch( pIADs );
  974. ASSERT( !!spSearch );
  975. if ( !spSearch )
  976. return E_FAIL;
  977. HRESULT hr = SetSearchPreference(spSearch, ADS_SCOPE_ONELEVEL);
  978. ASSERT( !FAILED(hr) );
  979. if (FAILED(hr))
  980. return hr;
  981. LPWSTR pAttrs[1] = { L"aDSPath" };
  982. ADS_SEARCH_HANDLE SearchHandle = NULL;
  983. hr = spSearch->ExecuteSearch (L"(objectClass=*)",
  984. pAttrs,
  985. 1,
  986. &SearchHandle);
  987. if (FAILED(hr))
  988. {
  989. DEBUG_OUTPUT(MINIMAL_LOGGING,
  990. L"DeleteChildren: ExecuteSearch failure: 0x%08x",
  991. hr);
  992. return hr;
  993. }
  994. while ( hr = spSearch->GetNextRow( SearchHandle ),
  995. SUCCEEDED(hr) && hr != S_ADS_NOMORE_ROWS )
  996. {
  997. CComBSTR sbstrADsPathThisItem;
  998. hr = RetrieveStringColumn( spSearch,
  999. SearchHandle,
  1000. pAttrs[0],
  1001. sbstrADsPathThisItem );
  1002. ASSERT( !FAILED(hr) );
  1003. if (FAILED(hr))
  1004. break;
  1005. CComPtr<IADsDeleteOps> spDeleteOps;
  1006. // return the error value for the first error encountered
  1007. HRESULT hrThisItem = DSCmdOpenObject(credentialObject,
  1008. sbstrADsPathThisItem,
  1009. IID_IADsDeleteOps,
  1010. (void**)&spDeleteOps,
  1011. true);
  1012. if (FAILED(hrThisItem))
  1013. {
  1014. DEBUG_OUTPUT(
  1015. MINIMAL_LOGGING,
  1016. L"DeleteChildren: DsCmdOpenObject(%s) failure: 0x%08x",
  1017. sbstrADsPathThisItem, hrThisItem);
  1018. }
  1019. else
  1020. {
  1021. ASSERT( !!spDeleteOps );
  1022. hrThisItem = spDeleteOps->DeleteObject( NULL );
  1023. if (FAILED(hrThisItem))
  1024. {
  1025. DEBUG_OUTPUT(
  1026. MINIMAL_LOGGING,
  1027. L"DeleteChildren: DeleteObject(%s) failure: 0x%08x",
  1028. sbstrADsPathThisItem, hrThisItem);
  1029. }
  1030. }
  1031. if (!FAILED(hrThisItem))
  1032. continue;
  1033. // an error occurred
  1034. if (!FAILED(hr))
  1035. hr = hrThisItem;
  1036. CComBSTR sbstrDN;
  1037. CPathCracker pathcracker;
  1038. HRESULT hr2 = pathcracker.Set( sbstrADsPathThisItem, ADS_SETTYPE_FULL );
  1039. ASSERT( !FAILED(hr2) );
  1040. if (FAILED(hr2))
  1041. break;
  1042. hr2 = pathcracker.Retrieve( ADS_FORMAT_X500_DN, &sbstrDN );
  1043. ASSERT( !FAILED(hr2) );
  1044. if (FAILED(hr2))
  1045. break;
  1046. // Report error message for the child which could not be deleted
  1047. DisplayErrorMessage(g_pszDSCommandName,
  1048. sbstrDN,
  1049. hrThisItem);
  1050. *pfErrorReported = true;
  1051. if (!fContinue)
  1052. break;
  1053. }
  1054. if (hr != S_ADS_NOMORE_ROWS)
  1055. {
  1056. DEBUG_OUTPUT(FULL_LOGGING,
  1057. L"DeleteChildren: abandoning search");
  1058. (void) spSearch->AbandonSearch( SearchHandle );
  1059. }
  1060. return (hr == S_ADS_NOMORE_ROWS) ? S_OK : hr;
  1061. }