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.

916 lines
23 KiB

  1. /*++
  2. Copyright (c) 1998 - 1998 Microsoft Corporation
  3. Module Name:
  4. refresh.c
  5. Abstract:
  6. This Module implements the delegation tool, which allows for the management
  7. of access to DS objects
  8. Author:
  9. Mac McLain (MacM) 10-15-96
  10. Environment:
  11. User Mode
  12. Revision History:
  13. --*/
  14. #include "stdafx.h"
  15. #include "utils.h"
  16. #include "dsace.h"
  17. #include "dsacls.h"
  18. typedef struct _DEFAULT_SD_NODE {
  19. PWSTR ObjectClass;
  20. PSECURITY_DESCRIPTOR DefaultSd;
  21. struct _DEFAULT_SD_NODE *Next;
  22. } DEFAULT_SD_NODE, *PDEFAULT_SD_NODE;
  23. typedef struct _DEFAULT_SD_INFO {
  24. LDAP *Ldap;
  25. PWSTR SchemaPath;
  26. PSID DomainSid;
  27. PDEFAULT_SD_NODE SdList;
  28. } DEFAULT_SD_INFO, *PDEFAULT_SD_INFO;
  29. #define DSACL_ALL_FILTER L"(ObjectClass=*)"
  30. #define DSACL_SCHEMA_NC L"schemaNamingContext"
  31. #define DSACL_OBJECT_CLASS L"objectClass"
  32. #define DSACL_LDAP_DN L"(ldapDisplayName="
  33. #define DSACL_LDAP_DN_CLOSE L")"
  34. #define DSACL_DEFAULT_SD L"defaultSecurityDescriptor"
  35. DWORD
  36. FindDefaultSdForClass(
  37. IN PWSTR ClassId,
  38. IN PDEFAULT_SD_INFO SdInfo,
  39. IN OUT PDEFAULT_SD_NODE *DefaultSdNode
  40. )
  41. /*++
  42. Routine Description:
  43. This routine will search the SD_INFO list for an existing entry that matches the current
  44. class type. If no such entry is found, one will be created from information from the schema
  45. Arguments:
  46. ClassId - ClassId to find the default SD node for
  47. SdInfo - Current list of default SDs and associated information
  48. DefaultSdNode - Where the locted node is returned
  49. Returns:
  50. ERROR_SUCCESS - Success
  51. ERROR_NOT_ENOUGH_MEMORY - A memory allocation failed
  52. --*/
  53. {
  54. DWORD Win32Err = ERROR_SUCCESS;
  55. PWSTR Attributes[] = {
  56. NULL,
  57. NULL
  58. };
  59. LDAPMessage *Message = NULL, *Entry;
  60. PWSTR Filter = NULL, SchemaObjectDn = NULL, DefaultSd = NULL, *DefaultSdList = NULL;
  61. PDEFAULT_SD_NODE Node;
  62. *DefaultSdNode = NULL;
  63. Node = SdInfo->SdList;
  64. while ( Node ) {
  65. if ( !_wcsicmp( Node->ObjectClass, ClassId ) ) {
  66. *DefaultSdNode = Node;
  67. break;
  68. }
  69. Node = Node->Next;
  70. }
  71. //
  72. // If it wasn't found, we'll have to go out and load it out of the Ds.
  73. //
  74. if ( !Node ) {
  75. Filter = (LPWSTR)LocalAlloc( LMEM_FIXED,
  76. sizeof( DSACL_LDAP_DN ) - sizeof( WCHAR ) +
  77. ( wcslen( ClassId ) * sizeof( WCHAR ) ) +
  78. sizeof( DSACL_LDAP_DN_CLOSE ) );
  79. if ( !Filter ) {
  80. Win32Err = ERROR_NOT_ENOUGH_MEMORY;
  81. goto FindDefaultExit;
  82. }
  83. swprintf( Filter,
  84. L"%ws%ws%ws",
  85. DSACL_LDAP_DN,
  86. ClassId,
  87. DSACL_LDAP_DN_CLOSE );
  88. //
  89. // Now, do the search
  90. //
  91. Win32Err = LdapMapErrorToWin32( ldap_search_s( SdInfo->Ldap,
  92. SdInfo->SchemaPath,
  93. LDAP_SCOPE_SUBTREE,
  94. Filter,
  95. Attributes,
  96. 0,
  97. &Message ) );
  98. if ( Win32Err != ERROR_SUCCESS ) {
  99. goto FindDefaultExit;
  100. }
  101. Entry = ldap_first_entry( SdInfo->Ldap, Message );
  102. if ( Entry ) {
  103. SchemaObjectDn = ldap_get_dn( SdInfo->Ldap, Entry );
  104. if ( !SchemaObjectDn ) {
  105. Win32Err = ERROR_NOT_ENOUGH_MEMORY;
  106. goto FindDefaultExit;
  107. }
  108. } else {
  109. Win32Err = LdapMapErrorToWin32( SdInfo->Ldap->ld_errno );
  110. goto FindDefaultExit;
  111. }
  112. ldap_msgfree( Message );
  113. Message = NULL;
  114. //
  115. // Ok, now we can read the default security descriptor
  116. //
  117. Attributes[ 0 ] = DSACL_DEFAULT_SD;
  118. Win32Err = LdapMapErrorToWin32( ldap_search_s( SdInfo->Ldap,
  119. SchemaObjectDn,
  120. LDAP_SCOPE_BASE,
  121. DSACL_ALL_FILTER,
  122. Attributes,
  123. 0,
  124. &Message ) );
  125. Entry = ldap_first_entry( SdInfo->Ldap, Message );
  126. if ( Entry ) {
  127. //
  128. // Now, we'll have to get the values
  129. //
  130. DefaultSdList = ldap_get_values( SdInfo->Ldap, Entry, Attributes[ 0 ] );
  131. if ( DefaultSdList ) {
  132. DefaultSd = DefaultSdList[ 0 ];
  133. } else {
  134. Win32Err = LdapMapErrorToWin32( SdInfo->Ldap->ld_errno );
  135. goto FindDefaultExit;
  136. }
  137. }
  138. //
  139. // Find a new node and insert it
  140. //
  141. Node = (DEFAULT_SD_NODE*)LocalAlloc( LMEM_FIXED | LMEM_ZEROINIT,
  142. sizeof( DEFAULT_SD_NODE ) );
  143. if ( !Node ) {
  144. Win32Err = ERROR_NOT_ENOUGH_MEMORY;
  145. goto FindDefaultExit;
  146. }
  147. if ( !ConvertStringSDToSDRootDomain( SdInfo->DomainSid,
  148. DefaultSd,
  149. SDDL_REVISION,
  150. &Node->DefaultSd,
  151. NULL ) ) {
  152. Win32Err = GetLastError();
  153. }
  154. if ( Win32Err == ERROR_SUCCESS ) {
  155. Node->ObjectClass =(LPWSTR) LocalAlloc( LMEM_FIXED,
  156. ( wcslen( ClassId ) + 1 ) * sizeof( WCHAR ) );
  157. if ( Node->ObjectClass == NULL ) {
  158. Win32Err = ERROR_NOT_ENOUGH_MEMORY;
  159. } else {
  160. wcscpy( Node->ObjectClass, ClassId );
  161. Node->Next = SdInfo->SdList;
  162. SdInfo->SdList = Node;
  163. }
  164. }
  165. if ( Win32Err != ERROR_SUCCESS ) {
  166. LocalFree( Node->DefaultSd );
  167. LocalFree( Node->ObjectClass );
  168. LocalFree( Node );
  169. } else {
  170. *DefaultSdNode = Node;
  171. }
  172. }
  173. FindDefaultExit:
  174. LocalFree( Filter );
  175. if(Message)
  176. ldap_msgfree( Message );
  177. if ( SchemaObjectDn ) {
  178. ldap_memfree( SchemaObjectDn );
  179. }
  180. if ( DefaultSdList ) {
  181. ldap_value_free( DefaultSdList );
  182. }
  183. return( Win32Err );
  184. }
  185. DWORD
  186. SetDefaultSdForObject(
  187. IN LDAP *Ldap,
  188. IN PWSTR ObjectPath,
  189. IN PDEFAULT_SD_INFO SdInfo,
  190. IN SECURITY_INFORMATION Protection
  191. )
  192. /*++
  193. Routine Description:
  194. This routine set the default security descriptor on the indicated object
  195. Arguments:
  196. Ldap - Ldap connect to the server holding the object
  197. ObjectPath - 1779 style path to the object
  198. SdInfo - Current list of default SDs and associated information
  199. Returns:
  200. ERROR_SUCCESS - Success
  201. ERROR_DS_NAME_TYPE_UNKNOWN - Unable to determine the class id of the object
  202. --*/
  203. {
  204. DWORD Win32Err = ERROR_SUCCESS;
  205. PWSTR Attributes[] = {
  206. DSACL_OBJECT_CLASS,
  207. NULL
  208. };
  209. LDAPMessage *Message = NULL, *Entry;
  210. PWSTR ClassId = NULL;
  211. PWSTR *ClassList = NULL;
  212. ULONG i;
  213. PDEFAULT_SD_NODE DefaultSdNode = NULL;
  214. PACTRL_ACCESS NewAccess = NULL;
  215. PACTRL_AUDIT NewAudit = NULL;
  216. //
  217. // First, get the class id off of the object
  218. //
  219. Win32Err = LdapMapErrorToWin32( ldap_search_s( Ldap,
  220. ObjectPath,
  221. LDAP_SCOPE_BASE,
  222. DSACL_ALL_FILTER,
  223. Attributes,
  224. 0,
  225. &Message ) );
  226. if ( Win32Err != ERROR_SUCCESS ) {
  227. goto SetDefaultExit;
  228. }
  229. Entry = ldap_first_entry( Ldap, Message );
  230. if ( Entry ) {
  231. //
  232. // Now, we'll have to get the values
  233. //
  234. ClassList = ldap_get_values( Ldap, Entry, Attributes[ 0 ] );
  235. if ( ClassList ) {
  236. //
  237. // Get the class id
  238. //
  239. i = 0;
  240. while ( TRUE ) {
  241. if ( ClassList[ i ] ) {
  242. i++;
  243. } else {
  244. break;
  245. }
  246. }
  247. // ASSERT( i > 0 );
  248. if ( i == 0 ) {
  249. Win32Err = ERROR_DS_NAME_TYPE_UNKNOWN;
  250. goto SetDefaultExit;
  251. }
  252. ClassId = ClassList[ i - 1 ];
  253. } else {
  254. Win32Err = LdapMapErrorToWin32( Ldap->ld_errno );
  255. goto SetDefaultExit;
  256. }
  257. ldap_msgfree( Message );
  258. Message = NULL;
  259. }
  260. if ( !ClassId ) {
  261. Win32Err = ERROR_DS_NAME_TYPE_UNKNOWN;
  262. goto SetDefaultExit;
  263. }
  264. //
  265. // Now, see if we have a cache entry for that...
  266. //
  267. Win32Err = FindDefaultSdForClass( ClassId,
  268. SdInfo,
  269. &DefaultSdNode );
  270. if ( Win32Err != ERROR_SUCCESS ) {
  271. goto SetDefaultExit;
  272. }
  273. if ( Win32Err == ERROR_SUCCESS ) {
  274. Win32Err = WriteObjectSecurity(ObjectPath,
  275. DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | Protection,
  276. DefaultSdNode->DefaultSd
  277. );
  278. }
  279. if ( Win32Err == ERROR_SUCCESS ) {
  280. DisplayMessageEx( 0, MSG_DSACLS_PROCESSED, ObjectPath );
  281. }
  282. SetDefaultExit:
  283. if ( ClassList ) {
  284. ldap_value_free( ClassList );
  285. }
  286. if ( Message ) {
  287. ldap_msgfree( Message );
  288. }
  289. LocalFree( NewAccess );
  290. LocalFree( NewAudit );
  291. return( Win32Err );
  292. }
  293. DWORD
  294. SetDefaultSdForObjectAndChildren(
  295. IN LDAP *Ldap,
  296. IN PWSTR ObjectPath,
  297. IN PDEFAULT_SD_INFO SdInfo,
  298. IN BOOLEAN Propagate,
  299. IN SECURITY_INFORMATION Protection
  300. )
  301. /*++
  302. Routine Description:
  303. This routine will set the security descriptor on the object and potentially all of its
  304. children to the default security as obtained from the schema
  305. Arguments:
  306. Ldap - Ldap connect to the server holding the object
  307. ObjectPath - 1779 style path to the object
  308. SdInfo - Current list of default SDs and associated information
  309. Propagate - If TRUE, reset the security on the children as well
  310. Returns:
  311. ERROR_SUCCESS - Success
  312. ERROR_NOT_ENOUGH_MEMORY - A memory allocation failed
  313. --*/
  314. {
  315. DWORD Win32Err = ERROR_SUCCESS;
  316. PWSTR Attributes[] = {
  317. NULL
  318. };
  319. LDAPMessage *Message = NULL, *Entry;
  320. PWSTR ChildName = NULL;
  321. PLDAPSearch SearchHandle = NULL;
  322. ULONG Count;
  323. //
  324. // First, get the class id off of the object
  325. //
  326. SearchHandle = ldap_search_init_pageW( Ldap,
  327. ObjectPath,
  328. Propagate ? LDAP_SCOPE_SUBTREE : LDAP_SCOPE_BASE,
  329. DSACL_ALL_FILTER,
  330. Attributes,
  331. FALSE,
  332. NULL,
  333. NULL,
  334. 0,
  335. 2000,
  336. NULL );
  337. if ( SearchHandle == NULL ) {
  338. Win32Err = LdapMapErrorToWin32( LdapGetLastError( ) );
  339. } else {
  340. while ( Win32Err == ERROR_SUCCESS ) {
  341. Count = 0;
  342. //
  343. // Get the next page
  344. //
  345. Win32Err = ldap_get_next_page_s( Ldap,
  346. SearchHandle,
  347. NULL,
  348. 100,
  349. &Count,
  350. &Message );
  351. if ( Message ) {
  352. Entry = ldap_first_entry( Ldap, Message );
  353. while ( Entry ) {
  354. ChildName = ldap_get_dn( SdInfo->Ldap, Entry );
  355. if ( !ChildName ) {
  356. Win32Err = ERROR_NOT_ENOUGH_MEMORY;
  357. break;
  358. }
  359. Win32Err = SetDefaultSdForObject( Ldap,
  360. ChildName,
  361. SdInfo,
  362. Protection);
  363. ldap_memfree( ChildName );
  364. if ( Win32Err != ERROR_SUCCESS ) {
  365. break;
  366. }
  367. Entry = ldap_next_entry( Ldap, Entry );
  368. }
  369. Win32Err = Ldap->ld_errno;
  370. ldap_msgfree( Message );
  371. Message = NULL;
  372. }
  373. if ( Win32Err == LDAP_NO_RESULTS_RETURNED ) {
  374. Win32Err = ERROR_SUCCESS;
  375. break;
  376. }
  377. }
  378. ldap_search_abandon_page( Ldap,
  379. SearchHandle );
  380. }
  381. return( Win32Err );
  382. }
  383. DWORD
  384. BindToDsObject(
  385. IN PWSTR ObjectPath,
  386. OUT PLDAP *Ldap,
  387. OUT PSID *DomainSid OPTIONAL
  388. )
  389. /*++
  390. Routine Description:
  391. This routine will bind to the ldap server on a domain controller that holds the specified
  392. object path. Optionally, the sid of the domain hosted by that domain controller is returned
  393. Arguments:
  394. ObjectPath - 1779 style path to the object
  395. Ldap - Where the ldap connection handle is returned
  396. DomainSid - Sid of the domain hosted by the domain controller.
  397. Returns:
  398. ERROR_SUCCESS - Success
  399. ERROR_PATH_NOT_FOUND - A domain controller for this path could not be located
  400. ERROR_NOT_ENOUGH_MEMORY - A memory allocation failed
  401. --*/
  402. {
  403. DWORD Win32Err = ERROR_SUCCESS;
  404. PWSTR ServerName = NULL;
  405. PWSTR Separator = NULL;
  406. PDOMAIN_CONTROLLER_INFO DcInfo = NULL;
  407. PWSTR Path = NULL;
  408. HANDLE DsHandle = NULL;
  409. PDS_NAME_RESULT NameRes = NULL;
  410. BOOLEAN NamedServer = FALSE;
  411. UNICODE_STRING ServerNameU;
  412. OBJECT_ATTRIBUTES ObjectAttributes;
  413. LSA_HANDLE LsaHandle;
  414. PPOLICY_PRIMARY_DOMAIN_INFO PolicyPDI = NULL;
  415. NTSTATUS Status;
  416. //
  417. // Get a server name
  418. //
  419. /* if ( wcslen( ObjectPath ) > 2 && *ObjectPath == L'\\' && *( ObjectPath + 1 ) == L'\\' ) {
  420. Separator = wcschr( ObjectPath + 2, L'\\' );
  421. if ( Separator ) {
  422. *Separator = L'\0';
  423. Path = Separator + 1;
  424. }
  425. ServerName = ObjectPath + 2;
  426. NamedServer = TRUE;
  427. } else {
  428. Path = ObjectPath;
  429. Win32Err = DsGetDcName( NULL,
  430. NULL,
  431. NULL,
  432. NULL,
  433. DS_IP_REQUIRED |
  434. DS_DIRECTORY_SERVICE_REQUIRED,
  435. &DcInfo );
  436. if ( Win32Err == ERROR_SUCCESS ) {
  437. ServerName = DcInfo[ 0 ].DomainControllerName + 2;
  438. }
  439. }
  440. //
  441. // Do the bind and crack
  442. //
  443. if ( Win32Err == ERROR_SUCCESS ) {
  444. Win32Err = DsBind( ServerName,
  445. NULL,
  446. &DsHandle );
  447. if ( Win32Err == ERROR_SUCCESS ) {
  448. Win32Err = DsCrackNames( DsHandle,
  449. DS_NAME_NO_FLAGS,
  450. DS_FQDN_1779_NAME,
  451. DS_FQDN_1779_NAME,
  452. 1,
  453. &Path,
  454. &NameRes );
  455. if ( Win32Err == ERROR_SUCCESS ) {
  456. if ( NameRes->cItems != 0 && !NamedServer &&
  457. NameRes->rItems[ 0 ].status == DS_NAME_ERROR_DOMAIN_ONLY ) {
  458. NetApiBufferFree( DcInfo );
  459. DcInfo = NULL;
  460. Win32Err = DsGetDcNameW( NULL,
  461. NameRes->rItems[ 0 ].pDomain,
  462. NULL,
  463. NULL,
  464. DS_IP_REQUIRED |
  465. DS_DIRECTORY_SERVICE_REQUIRED,
  466. &DcInfo );
  467. if ( Win32Err == ERROR_SUCCESS ) {
  468. DsUnBindW( &DsHandle );
  469. DsHandle = NULL;
  470. ServerName = DcInfo->DomainControllerName + 2;
  471. //Win32Err = DsBind( DcInfo->DomainControllerAddress,
  472. // NULL,
  473. // &DsHandle );
  474. //
  475. Win32Err = DsBind( ServerName,
  476. NULL,
  477. &DsHandle );
  478. if ( Win32Err == ERROR_SUCCESS ) {
  479. Win32Err = DsCrackNames( DsHandle,
  480. DS_NAME_NO_FLAGS,
  481. DS_FQDN_1779_NAME,
  482. DS_FQDN_1779_NAME,
  483. 1,
  484. &Path,
  485. &NameRes);
  486. }
  487. }
  488. }
  489. }
  490. }
  491. }
  492. */
  493. //
  494. // Now, do the bind
  495. //
  496. *Ldap = ldap_open( g_szServerName,
  497. LDAP_PORT );
  498. if ( *Ldap == NULL ) {
  499. Win32Err = ERROR_PATH_NOT_FOUND;
  500. } else {
  501. Win32Err = LdapMapErrorToWin32( ldap_bind_s( *Ldap,
  502. NULL,
  503. NULL,
  504. LDAP_AUTH_SSPI ) );
  505. }
  506. //
  507. // If specified, get the sid for the domain
  508. //
  509. if ( DomainSid ) {
  510. RtlInitUnicodeString( &ServerNameU, g_szServerName );
  511. InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
  512. //
  513. // Get the sid of the domain
  514. //
  515. Status = LsaOpenPolicy( &ServerNameU,
  516. &ObjectAttributes,
  517. POLICY_VIEW_LOCAL_INFORMATION,
  518. &LsaHandle );
  519. if ( NT_SUCCESS( Status ) ) {
  520. Status = LsaQueryInformationPolicy( LsaHandle,
  521. PolicyPrimaryDomainInformation,
  522. ( PVOID * )&PolicyPDI );
  523. if ( NT_SUCCESS( Status ) ) {
  524. *DomainSid = (PSID)LocalAlloc( LMEM_FIXED,
  525. RtlLengthSid( PolicyPDI->Sid ) );
  526. if ( *DomainSid == NULL ) {
  527. Status = STATUS_INSUFFICIENT_RESOURCES;
  528. } else {
  529. RtlCopySid( RtlLengthSid( PolicyPDI->Sid ), *DomainSid, PolicyPDI->Sid );
  530. }
  531. LsaFreeMemory( PolicyPDI );
  532. }
  533. LsaClose( LsaHandle );
  534. }
  535. if ( !NT_SUCCESS( Status ) ) {
  536. Win32Err = RtlNtStatusToDosError( Status );
  537. ldap_unbind( *Ldap );
  538. *Ldap = NULL;
  539. }
  540. }
  541. return( Win32Err );
  542. }
  543. DWORD
  544. SetDefaultSecurityOnObjectTree(
  545. IN PWSTR ObjectPath,
  546. IN BOOLEAN Propagate,
  547. IN SECURITY_INFORMATION Protection
  548. )
  549. /*++
  550. Routine Description:
  551. This routine will set the security descriptor on the object and potentially all of its
  552. children to the default security as obtained from the schema
  553. Arguments:
  554. ObjectPath - 1779 style path to the object
  555. Propagate - If TRUE, reset the security on the children as well
  556. Returns:
  557. ERROR_SUCCESS - Success
  558. --*/
  559. {
  560. DWORD Win32Err = ERROR_SUCCESS;
  561. PWSTR Attributes[] = {
  562. DSACL_SCHEMA_NC,
  563. NULL
  564. };
  565. LDAPMessage *Message = NULL;
  566. LDAPMessage *Entry = NULL;
  567. PWSTR *PathList = NULL;
  568. DEFAULT_SD_INFO SdInfo = {
  569. NULL,
  570. NULL,
  571. NULL,
  572. NULL
  573. };
  574. PDEFAULT_SD_NODE CleanupNode;
  575. //
  576. // Bind to the ds object
  577. //
  578. Win32Err = BindToDsObject( ObjectPath,
  579. &SdInfo.Ldap,
  580. &SdInfo.DomainSid );
  581. if ( Win32Err != ERROR_SUCCESS ) {
  582. goto SetDefaultExit;
  583. }
  584. //
  585. // Get the schema path
  586. //
  587. Win32Err = LdapMapErrorToWin32( ldap_search_s( SdInfo.Ldap,
  588. NULL,
  589. LDAP_SCOPE_BASE,
  590. DSACL_ALL_FILTER,
  591. Attributes,
  592. 0,
  593. &Message ) );
  594. if ( Win32Err == ERROR_SUCCESS ) {
  595. Entry = ldap_first_entry( SdInfo.Ldap, Message );
  596. if ( Entry ) {
  597. //
  598. // Now, we'll have to get the values
  599. //
  600. PathList = ldap_get_values( SdInfo.Ldap, Entry, Attributes[ 0 ] );
  601. if ( PathList ) {
  602. SdInfo.SchemaPath = PathList[ 0 ];
  603. } else {
  604. Win32Err = LdapMapErrorToWin32( SdInfo.Ldap->ld_errno );
  605. }
  606. }
  607. }
  608. if( SdInfo.Ldap )
  609. {
  610. Win32Err = SetDefaultSdForObjectAndChildren( SdInfo.Ldap,
  611. ObjectPath,
  612. &SdInfo,
  613. Propagate,
  614. Protection);
  615. }
  616. SetDefaultExit:
  617. if( Message )
  618. ldap_msgfree( Message );
  619. //
  620. // Unbind from the DS
  621. //
  622. if ( SdInfo.Ldap ) {
  623. ldap_unbind( SdInfo.Ldap );
  624. }
  625. if ( PathList ) {
  626. ldap_value_free( PathList );
  627. }
  628. //
  629. // Clean up the Default SD Info list
  630. //
  631. LocalFree( SdInfo.DomainSid );
  632. while ( SdInfo.SdList ) {
  633. CleanupNode = SdInfo.SdList;
  634. LocalFree( CleanupNode->ObjectClass );
  635. LocalFree( CleanupNode->DefaultSd );
  636. SdInfo.SdList = SdInfo.SdList->Next;
  637. LocalFree( CleanupNode );
  638. }
  639. return( Win32Err );
  640. }