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.

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