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.

2127 lines
63 KiB

  1. /*++
  2. Copyright (c) 2000, 2001 Microsoft Corporation
  3. Module Name:
  4. compobj.cpp
  5. Abstract:
  6. routines for backing computer object support
  7. Author:
  8. Charlie Wickham (charlwi) 14-Dec-2000
  9. Environment:
  10. User Mode
  11. Revision History:
  12. --*/
  13. #define UNICODE 1
  14. #define _UNICODE 1
  15. #define LDAP_UNICODE 1
  16. extern "C" {
  17. #include "clusres.h"
  18. #include "clusrtl.h"
  19. #include <winsock2.h>
  20. #include <lm.h>
  21. #include <lmaccess.h>
  22. #include <winldap.h>
  23. #include <ntldap.h>
  24. #include <dsgetdc.h>
  25. #include <dsgetdcp.h>
  26. #include <ntdsapi.h>
  27. #include <sddl.h>
  28. #include <objbase.h>
  29. #include <iads.h>
  30. #include <adshlp.h>
  31. #include <adserr.h>
  32. #include "netname.h"
  33. #include "nameutil.h"
  34. }
  35. //
  36. // Constants
  37. //
  38. #define LOG_CURRENT_MODULE LOG_MODULE_NETNAME
  39. /* External */
  40. extern PLOG_EVENT_ROUTINE NetNameLogEvent;
  41. extern "C" {
  42. DWORD
  43. EncryptNetNameData(
  44. RESOURCE_HANDLE ResourceHandle,
  45. LPWSTR MachinePwd,
  46. PBYTE * EncryptedData,
  47. PDWORD EncryptedDataLength,
  48. HKEY Key
  49. );
  50. DWORD
  51. DecryptNetNameData(
  52. RESOURCE_HANDLE ResourceHandle,
  53. PBYTE EncryptedData,
  54. DWORD EncryptedDataLength,
  55. LPWSTR MachinePwd
  56. );
  57. }
  58. //
  59. // static data
  60. //
  61. static WCHAR LdapHeader[] = L"LDAP://";
  62. //
  63. // forward references
  64. //
  65. HRESULT
  66. GetComputerObjectViaFQDN(
  67. IN LPWSTR DistinguishedName,
  68. IN LPWSTR DCName OPTIONAL,
  69. IN OUT IDirectoryObject ** ComputerObject
  70. );
  71. //
  72. // private routines
  73. //
  74. DWORD
  75. GenerateRandomBytes(
  76. PWSTR Buffer,
  77. DWORD BufferLength
  78. )
  79. /*++
  80. Routine Description:
  81. Generate random bytes for a password. Length is specified in characters
  82. and allows room for the trailing null.
  83. Arguments:
  84. Buffer - pointer to area to receive random data
  85. BufferLength - size of Buffer in characters
  86. Return Value:
  87. ERROR_SUCCESS otherwise GetLastError()
  88. --*/
  89. {
  90. HCRYPTPROV cryptProvider;
  91. DWORD status = ERROR_SUCCESS;
  92. DWORD charLength = BufferLength - 1;
  93. DWORD byteLength = charLength * sizeof( WCHAR );
  94. BOOL success;
  95. if ( !CryptAcquireContext(&cryptProvider,
  96. NULL,
  97. NULL,
  98. PROV_RSA_FULL,
  99. CRYPT_VERIFYCONTEXT
  100. )) {
  101. return GetLastError();
  102. }
  103. //
  104. // leave room for the terminating null
  105. //
  106. if (CryptGenRandom( cryptProvider, byteLength, (BYTE *)Buffer )) {
  107. //
  108. // run down the array as WCHARs to make sure there is no premature
  109. // terminating NULL
  110. //
  111. PWCHAR pw = Buffer;
  112. while ( charLength-- ) {
  113. if ( *pw == UNICODE_NULL ) {
  114. *pw = 0xA3F5;
  115. }
  116. ++pw;
  117. }
  118. *pw = UNICODE_NULL;
  119. } else {
  120. status = GetLastError();
  121. }
  122. success = CryptReleaseContext( cryptProvider, 0 );
  123. ASSERT( success );
  124. return status;
  125. } // GenerateRandomBytes
  126. DWORD
  127. FindDomainForServer(
  128. IN PWSTR Server,
  129. OUT PDOMAIN_CONTROLLER_INFO * DCInfo
  130. )
  131. /*++
  132. Routine Description:
  133. get the name of a DC for our node
  134. Arguments:
  135. ServerName - pointer to string containing server (i.e., node) name
  136. DCInfo - address of a pointer that receives a pointer to DC information
  137. Return Value:
  138. ERROR_SUCCESS, otherwise appropriate Win32 error. If successful, caller must
  139. free DCInfo buffer.
  140. --*/
  141. {
  142. ULONG status;
  143. WCHAR localServerName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  144. PDOMAIN_CONTROLLER_INFOW dcInfo;
  145. DWORD dsFlags;
  146. //
  147. // MAX_COMPUTERNAME_LENGTH is defined to be 15 but I could create computer
  148. // objects with name lengths of up to 20 chars. Not sure why the
  149. // discrepenacy but we'll leave ourselves extra room by using the DNS
  150. // constants
  151. //
  152. wcsncpy( localServerName, Server, DNS_MAX_LABEL_LENGTH - 1 );
  153. wcscat( localServerName, L"$" );
  154. //
  155. // specifying that a writable DS is required makes us home in on a W2K DC
  156. // (as opposed to an NT4 PDC). Writable is needed since we always reset
  157. // the password to what is stored in the cluster registry.
  158. //
  159. dsFlags = DS_DIRECTORY_SERVICE_REQUIRED |
  160. DS_RETURN_DNS_NAME |
  161. DS_WRITABLE_REQUIRED;
  162. status = DsGetDcNameWithAccountW(NULL,
  163. localServerName,
  164. UF_MACHINE_ACCOUNT_MASK,
  165. L"",
  166. NULL,
  167. NULL,
  168. dsFlags,
  169. &dcInfo );
  170. if ( status == ERROR_NO_SUCH_DOMAIN ) {
  171. //
  172. // try again with rediscovery
  173. //
  174. dsFlags |= DS_FORCE_REDISCOVERY;
  175. status = DsGetDcNameWithAccountW(NULL,
  176. localServerName,
  177. UF_MACHINE_ACCOUNT_MASK,
  178. L"",
  179. NULL,
  180. NULL,
  181. dsFlags,
  182. &dcInfo );
  183. }
  184. if ( status == DS_S_SUCCESS ) {
  185. *DCInfo = dcInfo;
  186. }
  187. return status;
  188. } // FindDomainForServer
  189. AddDnsHostNameAttribute(
  190. RESOURCE_HANDLE ResourceHandle,
  191. IDirectoryObject * CompObj,
  192. PWCHAR VirtualName,
  193. PWCHAR DnsDomain
  194. )
  195. /*++
  196. Routine Description:
  197. add the DnsHostName attribute to the computer object for the specified
  198. virtual name.
  199. Arguments:
  200. ResourceHandle - used to log in cluster log
  201. CompObj - IDirObj COM pointer to object
  202. VirtualName - network name
  203. DnsDomain - DNS suffix for this name
  204. Return Value:
  205. ERROR_SUCCESS, otherwise appropriate Win32 error
  206. --*/
  207. {
  208. HRESULT hr;
  209. DWORD numberModified;
  210. ADSVALUE attrValue;
  211. WCHAR FQDnsName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  212. ADS_ATTR_INFO attrInfo;
  213. //
  214. // build the FQ Dns name for this host
  215. //
  216. _snwprintf( FQDnsName, COUNT_OF( FQDnsName ) - 1, L"%ws.%ws", VirtualName, DnsDomain );
  217. attrValue.dwType = ADSTYPE_CASE_IGNORE_STRING;
  218. attrValue.CaseIgnoreString = FQDnsName;
  219. attrInfo.pszAttrName = L"DnsHostName";
  220. attrInfo.dwControlCode = ADS_ATTR_UPDATE;
  221. attrInfo.dwADsType = ADSTYPE_CASE_IGNORE_STRING;
  222. attrInfo.pADsValues = &attrValue;
  223. attrInfo.dwNumValues = 1;
  224. hr = CompObj->SetObjectAttributes( &attrInfo, 1, &numberModified );
  225. if ( SUCCEEDED( hr ) && numberModified != 1 ) {
  226. //
  227. // don't know why this scenario would happen but we'd better log
  228. // it since it is unusual
  229. //
  230. (NetNameLogEvent)(ResourceHandle,
  231. LOG_ERROR,
  232. L"SetObjectAttributes succeeded but NumberModified is zero!\n");
  233. hr = E_ADS_PROPERTY_NOT_SET;
  234. }
  235. return hr;
  236. } // AddDnsHostNameAttribute
  237. DWORD
  238. AddServicePrincipalNames(
  239. HANDLE DsHandle,
  240. PWCHAR VirtualFQDN,
  241. PWCHAR VirtualName,
  242. PWCHAR DnsDomain
  243. )
  244. /*++
  245. Routine Description:
  246. add the DNS and Netbios host service principal names to the specified
  247. virtual name
  248. Arguments:
  249. DsHandle - handle obtained from DsBind
  250. VirtualFQDN - distinguished name of computer object for the virtual netname
  251. VirtualName - the network name to be added
  252. DnsDomain - the DNS domain used to construct the DNS SPN
  253. Return Value:
  254. ERROR_SUCCESS, otherwise appropriate Win32 error
  255. --*/
  256. {
  257. WCHAR netbiosSpn[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  258. WCHAR dnsSpn[ DNS_MAX_NAME_BUFFER_LENGTH ];
  259. DWORD status;
  260. PWSTR spnArray[2] = { netbiosSpn, dnsSpn };
  261. DWORD spnCount = COUNT_OF( spnArray );
  262. //
  263. // build the Host SPNs for netbios and DNS name variants
  264. //
  265. _snwprintf( netbiosSpn, COUNT_OF( netbiosSpn ) - 1, L"HOST/%ws", VirtualName );
  266. _snwprintf( dnsSpn, COUNT_OF( dnsSpn ) - 1, L"HOST/%ws.%ws", VirtualName, DnsDomain );
  267. //
  268. // write the SPNs to the DS
  269. //
  270. status = DsWriteAccountSpnW(DsHandle,
  271. DS_SPN_ADD_SPN_OP,
  272. VirtualFQDN,
  273. spnCount,
  274. (LPCWSTR *)spnArray);
  275. return status;
  276. } // AddServicePrincipalNames
  277. DWORD
  278. SetACLOnParametersKey(
  279. HKEY ParametersKey
  280. )
  281. /*++
  282. Routine Description:
  283. Set the ACL on the params key to allow only admin group and creator/owner
  284. to have access to the data
  285. Arguments:
  286. ParametersKey - cluster HKEY to the netname's params key
  287. Return Value:
  288. ERROR_SUCCESS if successful
  289. --*/
  290. {
  291. DWORD status = ERROR_SUCCESS;
  292. BOOL success;
  293. PSECURITY_DESCRIPTOR secDesc = NULL;
  294. //
  295. // build an SD the quick way. This gives builtin admins (local admins's
  296. // group), creator/owner and the service SID full access to the
  297. // key. Inheritance is prevented in both directions, i.e., doesn't inherit
  298. // from its parent nor passes the settings onto its children (of which the
  299. // node parameters keys are the only children).
  300. //
  301. success = ConvertStringSecurityDescriptorToSecurityDescriptor(
  302. L"D:P(A;;KA;;;BA)(A;;KA;;;CO)(A;;KA;;;SU)",
  303. SDDL_REVISION_1,
  304. &secDesc,
  305. NULL);
  306. if ( success &&
  307. (secDesc != NULL) ) {
  308. status = ClusterRegSetKeySecurity(ParametersKey,
  309. DACL_SECURITY_INFORMATION,
  310. secDesc);
  311. LocalFree( secDesc );
  312. }
  313. else {
  314. if ( secDesc != NULL )
  315. {
  316. LocalFree( secDesc );
  317. status = GetLastError();
  318. }
  319. }
  320. return status;
  321. } // SetACLOnParametersKey
  322. DWORD
  323. GetFQDN(
  324. IN RESOURCE_HANDLE ResourceHandle,
  325. IN LPWSTR NodeName,
  326. IN LPWSTR VirtualName,
  327. IN HANDLE DsHandle OPTIONAL,
  328. IN LPWSTR NTDomainName OPTIONAL,
  329. OUT LPWSTR * FQDistinguishedName
  330. )
  331. /*++
  332. Routine Description:
  333. for the given DNS name, find the DS distinguished name, dup it and return
  334. the dup'ed string in FQDistinguishedName
  335. Arguments:
  336. None
  337. Return Value:
  338. None
  339. --*/
  340. {
  341. PWCHAR dot;
  342. WCHAR flatName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  343. LPWSTR flat = flatName;
  344. HANDLE dsHandle = NULL;
  345. DWORD status;
  346. LPWSTR ntDomainName;
  347. BOOL firstTime = TRUE;
  348. PDS_NAME_RESULTW nameResult = NULL;
  349. DS_NAME_FLAGS dsFlags = DS_NAME_NO_FLAGS;
  350. PDOMAIN_CONTROLLER_INFO dcInfo = NULL;
  351. //
  352. // if no DS handle was specified, go get one now
  353. //
  354. if ( DsHandle == NULL ) {
  355. //
  356. // get the name of a DC
  357. //
  358. status = FindDomainForServer( NodeName, &dcInfo );
  359. if ( status != ERROR_SUCCESS ) {
  360. (NetNameLogEvent)(ResourceHandle,
  361. LOG_ERROR,
  362. L"Unable to get domain information for node %1!ws!: %2!u!.\n",
  363. NodeName,
  364. status);
  365. return status;
  366. }
  367. (NetNameLogEvent)(ResourceHandle,
  368. LOG_INFORMATION,
  369. L"GetFQDN: Binding to domain controller %1!ws!.\n",
  370. dcInfo->DomainControllerName);
  371. status = DsBindW( dcInfo->DomainControllerName, NULL, &dsHandle );
  372. if ( status != NO_ERROR ) {
  373. (NetNameLogEvent)(ResourceHandle,
  374. LOG_ERROR,
  375. L"Failed to bind to a DC in domain %1!ws!, status %2!u!\n",
  376. dcInfo->DomainName,
  377. status );
  378. NetApiBufferFree( dcInfo );
  379. return status;
  380. }
  381. ntDomainName = dcInfo->DomainName;
  382. }
  383. else {
  384. dsHandle = DsHandle;
  385. ntDomainName = NTDomainName;
  386. }
  387. //
  388. // build a SAM style name (domain\node$) in flatName and "crack" it for
  389. // its FQDN (LDAP distinguished name)
  390. //
  391. wcsncpy( flatName, ntDomainName, DNS_MAX_NAME_BUFFER_LENGTH );
  392. flatName[ DNS_MAX_NAME_BUFFER_LENGTH - 1 ] = UNICODE_NULL;
  393. dot = wcschr( flatName, L'.' );
  394. if ( dot ) {
  395. *dot = UNICODE_NULL;
  396. }
  397. wcscat( flatName, L"\\" );
  398. wcscat( flatName, VirtualName );
  399. wcscat( flatName, L"$" );
  400. retry:
  401. status = DsCrackNamesW(dsHandle,
  402. dsFlags,
  403. DS_NT4_ACCOUNT_NAME,
  404. DS_FQDN_1779_NAME,
  405. 1,
  406. &flat,
  407. &nameResult );
  408. //
  409. // CrackNames must succeed, there should only be one name, and the status
  410. // associated with the name result should be ok. If it doesn't work the
  411. // first time, force a trip to the DC to find the object's DN.
  412. //
  413. if ( status != DS_NAME_NO_ERROR ) {
  414. if ( firstTime ) {
  415. firstTime = FALSE;
  416. dsFlags = DS_NAME_FLAG_EVAL_AT_DC;
  417. if ( nameResult != NULL ) {
  418. DsFreeNameResult( nameResult );
  419. }
  420. goto retry;
  421. }
  422. else {
  423. goto cleanup;
  424. }
  425. }
  426. if ( nameResult->cItems != 1 ) {
  427. status = DS_NAME_ERROR_NOT_UNIQUE;
  428. goto cleanup;
  429. }
  430. if ( nameResult->rItems[0].status != DS_NAME_NO_ERROR ) {
  431. if ( firstTime ) {
  432. firstTime = FALSE;
  433. dsFlags = DS_NAME_FLAG_EVAL_AT_DC;
  434. if ( nameResult != NULL ) {
  435. DsFreeNameResult( nameResult );
  436. }
  437. goto retry;
  438. }
  439. else {
  440. status = nameResult->rItems[0].status;
  441. goto cleanup;
  442. }
  443. }
  444. if ( status == DS_NAME_NO_ERROR ) {
  445. *FQDistinguishedName = ResUtilDupString( nameResult->rItems[0].pName );
  446. if ( *FQDistinguishedName == NULL ) {
  447. status = GetLastError();
  448. }
  449. }
  450. cleanup:
  451. if ( DsHandle == NULL ) {
  452. NetApiBufferFree( dcInfo );
  453. DsUnBind( &dsHandle );
  454. }
  455. if ( nameResult != NULL ) {
  456. DsFreeNameResult( nameResult );
  457. }
  458. return status;
  459. } // GetFQDN
  460. HRESULT
  461. GetComputerObjectViaFQDN(
  462. IN LPWSTR DistinguishedName,
  463. IN LPWSTR DCName OPTIONAL,
  464. IN OUT IDirectoryObject ** ComputerObject
  465. )
  466. /*++
  467. Routine Description:
  468. for the specified distinguished name, get an IDirectoryObject pointer to
  469. it
  470. Arguments:
  471. DistinguishedName - FQDN of object in DS to find
  472. DCName - optional pointer to name of DC (not domain) that we should bind to
  473. ComputerObject - address of pointer that receives pointer to computer object
  474. Return Value:
  475. success if everything worked, otherwise....
  476. --*/
  477. {
  478. WCHAR buffer[ 256 ];
  479. PWCHAR bindingString = buffer;
  480. LONG charCount;
  481. HRESULT hr;
  482. DWORD dnLength;
  483. //
  484. // format an LDAP binding string for our distingiushed name. If DCName is
  485. // specified, we need to add a trailing "/".
  486. //
  487. dnLength = (DWORD)( COUNT_OF( LdapHeader ) + wcslen( DistinguishedName ));
  488. if ( DCName != NULL ) {
  489. dnLength += wcslen( DCName );
  490. }
  491. if ( dnLength > COUNT_OF( buffer )) {
  492. bindingString = (PWCHAR)HeapAlloc( GetProcessHeap(), 0, dnLength * sizeof( WCHAR ));
  493. if ( bindingString == NULL ) {
  494. return ERROR_NOT_ENOUGH_MEMORY;
  495. }
  496. }
  497. wcscpy( bindingString, LdapHeader );
  498. if ( DCName != NULL ) {
  499. wcscat( bindingString, DCName );
  500. wcscat( bindingString, L"/" );
  501. }
  502. wcscat( bindingString, DistinguishedName );
  503. *ComputerObject = NULL;
  504. hr = ADsGetObject( bindingString, IID_IDirectoryObject, (VOID **)ComputerObject );
  505. if ( bindingString != buffer ) {
  506. HeapFree( GetProcessHeap(), 0, bindingString );
  507. }
  508. if ( FAILED( hr ) && *ComputerObject != NULL ) {
  509. (*ComputerObject)->Release();
  510. }
  511. return hr;
  512. } // GetComputerObjectViaFQDN
  513. HRESULT
  514. GetComputerObjectViaGUID(
  515. IN LPWSTR ObjectGUID,
  516. IN LPWSTR DCName OPTIONAL,
  517. IN OUT IDirectoryObject ** ComputerObject
  518. )
  519. /*++
  520. Routine Description:
  521. for the specified object GUID, get an IDirectoryObject pointer to it
  522. Arguments:
  523. ObjectGUID - GUID of object in DS to find
  524. DCName - optional pointer to name of DC (not domain) that we should bind to
  525. ComputerObject - address of pointer that receives pointer to computer object
  526. Return Value:
  527. success if everything worked, otherwise....
  528. --*/
  529. {
  530. WCHAR ldapGuidHeader[] = L"LDAP://<GUID=";
  531. WCHAR ldapTrailer[] = L">";
  532. LONG charCount;
  533. HRESULT hr;
  534. DWORD dnLength;
  535. //
  536. // 37 = guid length
  537. //
  538. WCHAR buffer[ COUNT_OF( ldapGuidHeader) + DNS_MAX_NAME_BUFFER_LENGTH + 37 + COUNT_OF( ldapTrailer) ];
  539. PWCHAR bindingString = buffer;
  540. //
  541. // format an LDAP binding string for the object GUID. If DCName is
  542. // specified, we need to add a trailing / plus the trailing null.
  543. //
  544. ASSERT( ObjectGUID != NULL );
  545. dnLength = (DWORD)( COUNT_OF( ldapGuidHeader ) + COUNT_OF( ldapTrailer ) + wcslen( ObjectGUID ));
  546. if ( DCName != NULL ) {
  547. dnLength += wcslen( DCName );
  548. }
  549. if ( dnLength > COUNT_OF( buffer )) {
  550. bindingString = (PWCHAR)HeapAlloc( GetProcessHeap(), 0, dnLength * sizeof( WCHAR ));
  551. if ( bindingString == NULL ) {
  552. return ERROR_NOT_ENOUGH_MEMORY;
  553. }
  554. }
  555. wcscpy( bindingString, ldapGuidHeader );
  556. if ( DCName != NULL ) {
  557. wcscat( bindingString, DCName );
  558. wcscat( bindingString, L"/" );
  559. }
  560. wcscat( bindingString, ObjectGUID );
  561. wcscat( bindingString, ldapTrailer );
  562. *ComputerObject = NULL;
  563. hr = ADsGetObject( bindingString, IID_IDirectoryObject, (VOID **)ComputerObject );
  564. if ( bindingString != buffer ) {
  565. HeapFree( GetProcessHeap(), 0, bindingString );
  566. }
  567. if ( FAILED( hr ) && *ComputerObject != NULL ) {
  568. (*ComputerObject)->Release();
  569. }
  570. return hr;
  571. } // GetComputerObjectViaGUID
  572. //
  573. // exported routines
  574. //
  575. DWORD
  576. NetNameDeleteComputerObject(
  577. IN PNETNAME_RESOURCE Resource
  578. )
  579. /*++
  580. Routine Description:
  581. delete the computer object in the DS for this name.
  582. Not called right now since we don't have the virtual netname at this point
  583. in time. The name must be kept around and cleaned during close processing
  584. instead of offline where it is done now. This means dealing with renaming
  585. issues while it is offline but not getting deleted.
  586. Arguments:
  587. ResourceHandle - for logging cluster log events
  588. VirtualName - pointer to buffer holding virtual name to be deleted
  589. Return Value:
  590. ERROR_SUCCESS if everything worked
  591. --*/
  592. {
  593. DWORD status;
  594. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  595. HKEY resourceKey = Resource->ResKey;
  596. PWSTR virtualName = Resource->Params.NetworkName;
  597. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  598. PDOMAIN_CONTROLLER_INFO dcInfo;
  599. //
  600. // get the name of a DC
  601. //
  602. status = FindDomainForServer( Resource->NodeName, &dcInfo );
  603. if ( status != ERROR_SUCCESS ) {
  604. (NetNameLogEvent)(resourceHandle,
  605. LOG_ERROR,
  606. L"Unable to get domain information: %1!u!.\n",
  607. status);
  608. return status;
  609. }
  610. (NetNameLogEvent)(resourceHandle,
  611. LOG_INFORMATION,
  612. L"Using domain controller %1!ws! to delete computer account.\n",
  613. dcInfo->DomainControllerName);
  614. //
  615. // add a $ to the end of the name
  616. //
  617. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", virtualName );
  618. status = NetUserDel( dcInfo->DomainControllerName, virtualDollarName );
  619. if ( status == NERR_Success ) {
  620. (NetNameLogEvent)(resourceHandle,
  621. LOG_INFORMATION,
  622. L"Deleted computer account %1!ws! in domain %2!ws!.\n",
  623. virtualName,
  624. dcInfo->DomainName);
  625. ClusResLogSystemEventByKey1(resourceKey,
  626. LOG_NOISE,
  627. RES_NETNAME_COMPUTER_ACCOUNT_DELETED,
  628. dcInfo->DomainName);
  629. LocalFree( Resource->ObjectGUID );
  630. Resource->ObjectGUID = NULL;
  631. } else {
  632. LPWSTR msgBuff;
  633. DWORD msgBytes;
  634. (NetNameLogEvent)(resourceHandle,
  635. LOG_WARNING,
  636. L"Unable to delete computer account for %1!ws! in domain %2!ws!, status %3!u!.\n",
  637. virtualName,
  638. dcInfo->DomainName,
  639. status);
  640. msgBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  641. FORMAT_MESSAGE_FROM_SYSTEM,
  642. NULL,
  643. status,
  644. 0,
  645. (LPWSTR)&msgBuff,
  646. 0,
  647. NULL);
  648. if ( msgBytes > 0 ) {
  649. ClusResLogSystemEventByKey2(resourceKey,
  650. LOG_UNUSUAL,
  651. RES_NETNAME_DELETE_COMPUTER_ACCOUNT_FAILED,
  652. dcInfo->DomainName,
  653. msgBuff);
  654. LocalFree( msgBuff );
  655. } else {
  656. ClusResLogSystemEventByKeyData1(resourceKey,
  657. LOG_UNUSUAL,
  658. RES_NETNAME_DELETE_COMPUTER_ACCOUNT_FAILED_STATUS,
  659. sizeof( status ),
  660. &status,
  661. dcInfo->DomainName);
  662. }
  663. }
  664. NetApiBufferFree( dcInfo );
  665. return status;
  666. } // NetNameDeleteComputerObject
  667. DWORD
  668. NetNameAddComputerObject(
  669. IN PCLUS_WORKER Worker,
  670. IN PNETNAME_RESOURCE Resource,
  671. OUT PWCHAR * MachinePwd
  672. )
  673. /*++
  674. Routine Description:
  675. Create a computer object in the DS that is primarily used for kerb
  676. authentication.
  677. Out of the box ACL on the computer container seems to let any
  678. authenticated user create a computer object; they can't delete it
  679. though. Also, authenticated users might have the "right to create computer
  680. objects" granted to them. This seems to allow the delete rights on the
  681. objects they create.
  682. Arguments:
  683. Worker - cluster worker thread so we can abort early if asked to do so
  684. Resource - pointer to netname resource context block
  685. MachinePwd - address of pointer to receive pointer to machine account PWD
  686. Return Value:
  687. ERROR_SUCCESS, otherwise appropriate Win32 error
  688. --*/
  689. {
  690. DWORD status;
  691. PWSTR virtualName = Resource->Params.NetworkName;
  692. DWORD virtualNameSize = wcslen( virtualName );
  693. PWSTR virtualFQDN = NULL;
  694. HANDLE dsHandle = NULL;
  695. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  696. PWCHAR machinePwd = NULL;
  697. DWORD pwdBufferByteLength = ((LM20_PWLEN + 1) * sizeof( WCHAR ));
  698. DWORD pwdBufferCharLength = LM20_PWLEN + 1;
  699. DWORD paramInError = 0;
  700. BOOL deleteObjectOnFailure = FALSE; // only delete the CO if we create it
  701. WCHAR dnsSuffix[ DNS_MAX_NAME_BUFFER_LENGTH ];
  702. DWORD dnsSuffixSize;
  703. BOOL success;
  704. DWORD setValueStatus;
  705. HINSTANCE hClusres;
  706. USER_INFO_1 netUI1;
  707. USER_INFO_1003 netUI1003;
  708. ULARGE_INTEGER updateTime;
  709. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  710. IDirectoryObject * compObj = NULL;
  711. PDOMAIN_CONTROLLER_INFO dcInfo;
  712. *MachinePwd = NULL;
  713. //
  714. // get DC related info
  715. //
  716. status = FindDomainForServer( Resource->NodeName, &dcInfo );
  717. if ( status != ERROR_SUCCESS ) {
  718. (NetNameLogEvent)(resourceHandle,
  719. LOG_ERROR,
  720. L"Unable to get domain information to create computer account: %1!u!.\n",
  721. status);
  722. return status;
  723. }
  724. (NetNameLogEvent)(resourceHandle,
  725. LOG_INFORMATION,
  726. L"Using domain controller %1!ws! to add or update computer account.\n",
  727. dcInfo->DomainControllerName);
  728. if ( ClusWorkerCheckTerminate( Worker )) {
  729. status = ERROR_OPERATION_ABORTED;
  730. goto cleanup;
  731. }
  732. //
  733. // add a $ to the end of the name. I don't know why we need to do this;
  734. // computer accounts have always had a $ at the end.
  735. //
  736. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", virtualName );
  737. //
  738. // get a buffer to hold the machine Pwd
  739. //
  740. machinePwd = (PWCHAR)HeapAlloc( GetProcessHeap(), 0, pwdBufferByteLength );
  741. if ( machinePwd == NULL ) {
  742. status = ERROR_NOT_ENOUGH_MEMORY;
  743. (NetNameLogEvent)(resourceHandle,
  744. LOG_ERROR,
  745. L"Unable to allocate memory for computer account data. status %1!u!.\n",
  746. status);
  747. goto cleanup;
  748. }
  749. //
  750. // see if this object was created at some time in the past; if so, its
  751. // random property will be non-null
  752. //
  753. if ( Resource->Params.NetworkRandom == NULL ) {
  754. //
  755. // generate a random stream of bytes for the password
  756. //
  757. status = GenerateRandomBytes( machinePwd, pwdBufferCharLength );
  758. if ( status != ERROR_SUCCESS ) {
  759. (NetNameLogEvent)(resourceHandle,
  760. LOG_ERROR,
  761. L"Unable to generate computer account data. status %1!u!.\n",
  762. status);
  763. goto cleanup;
  764. }
  765. //
  766. // set the ACL on the parameters key to contain just the cluster
  767. // service account since we're about to store sensitive info in there.
  768. //
  769. status = SetACLOnParametersKey( Resource->ParametersKey );
  770. if ( status != ERROR_SUCCESS ) {
  771. (NetNameLogEvent)(resourceHandle,
  772. LOG_ERROR,
  773. L"Unable to set ACL on parameters key. status %1!u!\n",
  774. status );
  775. goto cleanup;
  776. }
  777. //
  778. // take our new password, encrypt it and store it in the cluster
  779. // registry
  780. //
  781. status = EncryptNetNameData(resourceHandle,
  782. machinePwd,
  783. &Resource->Params.NetworkRandom,
  784. &Resource->RandomSize,
  785. Resource->ParametersKey);
  786. if ( status != ERROR_SUCCESS ) {
  787. (NetNameLogEvent)(resourceHandle,
  788. LOG_ERROR,
  789. L"Unable to store computer account data. status %1!u!\n",
  790. status );
  791. goto cleanup;
  792. }
  793. //
  794. // record when it is time to rotate the pwd; per MSDN, convert to
  795. // ULARGE Int and add in the update interval.
  796. //
  797. GetSystemTimeAsFileTime( &Resource->Params.NextUpdate );
  798. updateTime.LowPart = Resource->Params.NextUpdate.dwLowDateTime;
  799. updateTime.HighPart = Resource->Params.NextUpdate.dwHighDateTime;
  800. updateTime.QuadPart += ( Resource->Params.UpdateInterval * 60 * 1000 * 100 );
  801. Resource->Params.NextUpdate.dwLowDateTime = updateTime.LowPart;
  802. Resource->Params.NextUpdate.dwHighDateTime = updateTime.HighPart;
  803. setValueStatus = ResUtilSetBinaryValue(Resource->ParametersKey,
  804. PARAM_NAME__NEXT_UPDATE,
  805. (const LPBYTE)&updateTime,
  806. sizeof( updateTime ),
  807. NULL,
  808. NULL);
  809. ASSERT( setValueStatus == ERROR_SUCCESS );
  810. }
  811. else {
  812. //
  813. // we have an encrypted blob which means that the object was created
  814. // at some point in time. Extract the password from the blob.
  815. //
  816. status = DecryptNetNameData(resourceHandle,
  817. Resource->Params.NetworkRandom,
  818. Resource->RandomSize,
  819. machinePwd);
  820. if ( status != ERROR_SUCCESS ) {
  821. (NetNameLogEvent)(resourceHandle,
  822. LOG_ERROR,
  823. L"Unable to decrypt computer account password. status %1!u!\n",
  824. status );
  825. goto cleanup;
  826. }
  827. }
  828. //
  829. // update/create the computer object (machine account). Even though the
  830. // object might have been created by us in the past, it could have been
  831. // deleted and recreated at the DC or mangled in some other way. We'll
  832. // optimize for the case where it is left alone. If the object does exist,
  833. // our password on the SRV transport name and the object's password have
  834. // to be the same. We'll try NetUserSetInfo first to update the password
  835. // and test to see if the object exists. If we see that the object doesn't
  836. // exist, then we'll call NetUserAdd to create it.
  837. //
  838. // For some reason, Info level 1 fails with access denied when trying to
  839. // just update the password; possibly because it tries to set more than
  840. // just the password. 1003 works reliably when we create the computer
  841. // object.
  842. //
  843. netUI1003.usri1003_password = (PWCHAR)machinePwd;
  844. status = NetUserSetInfo(dcInfo->DomainControllerName,
  845. virtualDollarName,
  846. 1003,
  847. (PBYTE)&netUI1003,
  848. &paramInError );
  849. if ( ClusWorkerCheckTerminate( Worker )) {
  850. status = ERROR_OPERATION_ABORTED;
  851. goto cleanup;
  852. }
  853. if ( status != NERR_Success ) {
  854. if ( status == NERR_UserNotFound ) {
  855. RtlZeroMemory( &netUI1, sizeof( netUI1 ) );
  856. netUI1.usri1_password = (PWCHAR)machinePwd;
  857. netUI1.usri1_priv = USER_PRIV_USER;
  858. netUI1.usri1_name = virtualDollarName;
  859. netUI1.usri1_flags = UF_WORKSTATION_TRUST_ACCOUNT | UF_SCRIPT;
  860. netUI1.usri1_comment = NetNameCompObjAccountDesc;
  861. status = NetUserAdd( dcInfo->DomainControllerName, 1, (PBYTE)&netUI1, &paramInError );
  862. if ( status == NERR_Success ) {
  863. (NetNameLogEvent)(resourceHandle,
  864. LOG_INFORMATION,
  865. L"Created computer account %1!ws! on DC %2!ws!.\n",
  866. virtualName,
  867. dcInfo->DomainControllerName);
  868. deleteObjectOnFailure = TRUE;
  869. } // if NetUserAdd was successful
  870. else {
  871. (NetNameLogEvent)(resourceHandle,
  872. LOG_ERROR,
  873. L"Unable to create computer account %1!ws! on DC %2!ws!, "
  874. L"status %3!u! (paramInfo: %4!u!)\n",
  875. virtualName,
  876. dcInfo->DomainControllerName,
  877. status,
  878. paramInError);
  879. goto cleanup;
  880. }
  881. } // if NetUserSetInfo didn't find the specified user
  882. else {
  883. (NetNameLogEvent)(resourceHandle,
  884. LOG_ERROR,
  885. L"Unable to update password for computer account on DC %1!ws!, "
  886. L"status %2!u!.\n",
  887. dcInfo->DomainControllerName,
  888. status);
  889. goto cleanup;
  890. }
  891. } // if NetUserSetInfo failed
  892. else {
  893. PUSER_INFO_20 netUI20;
  894. //
  895. // check if the account is disabled
  896. //
  897. status = NetUserGetInfo(dcInfo->DomainControllerName,
  898. virtualDollarName,
  899. 20,
  900. (LPBYTE *)&netUI20);
  901. if ( status == NERR_Success ) {
  902. if ( netUI20->usri20_flags & UF_ACCOUNTDISABLE ) {
  903. (NetNameLogEvent)(resourceHandle,
  904. LOG_ERROR,
  905. L"Computer account for %1!ws! is disabled.\n",
  906. virtualName);
  907. status = ERROR_ACCOUNT_DISABLED;
  908. }
  909. NetApiBufferFree( netUI20 );
  910. if ( status != NERR_Success ) {
  911. goto cleanup;
  912. }
  913. } else {
  914. (NetNameLogEvent)(resourceHandle,
  915. LOG_WARNING,
  916. L"Failed to determine if computer account for %1!ws! is disabled. status %2!u!\n",
  917. virtualName,
  918. status);
  919. }
  920. }
  921. //
  922. // bind to the DS so we can write the DnsHostName and SPNs. We always
  923. // rewrite these since the things may have changed since the name was last
  924. // brought online.
  925. //
  926. status = DsBindW( dcInfo->DomainControllerName, NULL, &dsHandle );
  927. if ( status != NO_ERROR ) {
  928. (NetNameLogEvent)(resourceHandle,
  929. LOG_ERROR,
  930. L"Failed to bind to DC %1!ws! in domain %2!ws!, status %3!u!\n",
  931. dcInfo->DomainControllerName,
  932. dcInfo->DomainName,
  933. status );
  934. goto cleanup;
  935. }
  936. if ( ClusWorkerCheckTerminate( Worker )) {
  937. status = ERROR_OPERATION_ABORTED;
  938. goto cleanup;
  939. }
  940. //
  941. // get the LDAP distinguished name for this object; used temporarily to
  942. // set the DNS host attribute and SPNs on the account
  943. //
  944. status = GetFQDN(resourceHandle,
  945. Resource->NodeName,
  946. virtualName,
  947. dsHandle,
  948. dcInfo->DomainName,
  949. &virtualFQDN );
  950. if ( status != DS_NAME_NO_ERROR ) {
  951. (NetNameLogEvent)(resourceHandle,
  952. LOG_ERROR,
  953. L"Failed to find distinguished name for %1!ws! on DC %2!ws!, "
  954. L"status %3!u!\n",
  955. virtualName,
  956. dcInfo->DomainControllerName,
  957. status );
  958. goto cleanup;
  959. }
  960. //
  961. // use the Object GUID for binding during CheckComputerObjectAttributes so
  962. // we don't have to track changes to the DN. If the object moved in the
  963. // DS, its DN will change but not its GUID. We use this code instead of
  964. // GetComputerObjectGuid because we want to target a specific DC.
  965. //
  966. {
  967. PWCHAR dcName = dcInfo->DomainControllerName;
  968. IADs * pADs = NULL;
  969. HRESULT hr;
  970. if ( *dcName == L'\\' && *(dcName+1) == L'\\' ) {
  971. //
  972. // skip over double backslashes
  973. //
  974. dcName += 2;
  975. }
  976. hr = GetComputerObjectViaFQDN( virtualFQDN, dcName, &compObj );
  977. if ( SUCCEEDED( hr )) {
  978. hr = compObj->QueryInterface(IID_IADs, (void**) &pADs);
  979. if ( SUCCEEDED( hr )) {
  980. BSTR guidStr = NULL;
  981. hr = pADs->get_GUID( &guidStr );
  982. if ( SUCCEEDED( hr )) {
  983. if ( Resource->ObjectGUID != NULL ) {
  984. LocalFree( Resource->ObjectGUID );
  985. }
  986. Resource->ObjectGUID = ResUtilDupString( guidStr );
  987. }
  988. else {
  989. (NetNameLogEvent)(resourceHandle,
  990. LOG_ERROR,
  991. L"Failed to get computer object GUID for %1!ws!, status %2!08X!\n",
  992. virtualFQDN,
  993. hr );
  994. status = hr;
  995. }
  996. if ( guidStr ) {
  997. SysFreeString( guidStr );
  998. }
  999. }
  1000. if ( pADs != NULL ) {
  1001. pADs->Release();
  1002. }
  1003. if ( FAILED( hr )) {
  1004. status = hr;
  1005. goto cleanup;
  1006. }
  1007. if ( Resource->ObjectGUID == NULL ) {
  1008. status = GetLastError();
  1009. goto cleanup;
  1010. }
  1011. }
  1012. else {
  1013. (NetNameLogEvent)(resourceHandle,
  1014. LOG_ERROR,
  1015. L"Failed to get pointer to Computer Object for %1!ws!, status %2!08X!\n",
  1016. virtualFQDN,
  1017. hr );
  1018. status = hr;
  1019. goto cleanup;
  1020. }
  1021. }
  1022. //
  1023. // add the DnsHostName and ServicePrincipalName attributes
  1024. //
  1025. dnsSuffixSize = COUNT_OF( dnsSuffix );
  1026. success = GetComputerNameEx(ComputerNameDnsDomain,
  1027. dnsSuffix,
  1028. &dnsSuffixSize);
  1029. if ( success ) {
  1030. status = AddDnsHostNameAttribute(resourceHandle,
  1031. compObj,
  1032. virtualName,
  1033. dnsSuffix);
  1034. if ( status != ERROR_SUCCESS ) {
  1035. (NetNameLogEvent)(resourceHandle,
  1036. LOG_ERROR,
  1037. L"Unable to set DnsHostName attribute in the DS for %1!ws!, status %2!u!.\n",
  1038. virtualName,
  1039. status);
  1040. goto cleanup;
  1041. }
  1042. }
  1043. else {
  1044. status = GetLastError();
  1045. (NetNameLogEvent)(resourceHandle,
  1046. LOG_ERROR,
  1047. L"Unable to get primary DNS domain for this node, status %1!u!.\n",
  1048. status);
  1049. goto cleanup;
  1050. }
  1051. if ( ClusWorkerCheckTerminate( Worker )) {
  1052. status = ERROR_OPERATION_ABORTED;
  1053. goto cleanup;
  1054. }
  1055. status = AddServicePrincipalNames( dsHandle, virtualFQDN, virtualName, dcInfo->DomainName );
  1056. if ( status != ERROR_SUCCESS ) {
  1057. (NetNameLogEvent)(resourceHandle,
  1058. LOG_ERROR,
  1059. L"Unable to set ServicePrincipalName attribute in the DS for %1!ws!, status %2!u!.\n",
  1060. virtualName,
  1061. status);
  1062. }
  1063. cleanup:
  1064. //
  1065. // always free these
  1066. //
  1067. if ( dsHandle != NULL ) {
  1068. DsUnBind( &dsHandle );
  1069. }
  1070. if ( compObj != NULL ) {
  1071. compObj->Release();
  1072. }
  1073. if ( status == ERROR_SUCCESS ) {
  1074. *MachinePwd = machinePwd;
  1075. } else {
  1076. if ( status != ERROR_OPERATION_ABORTED ) {
  1077. LPWSTR msgBuff;
  1078. DWORD msgBytes;
  1079. msgBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  1080. FORMAT_MESSAGE_FROM_SYSTEM,
  1081. NULL,
  1082. status,
  1083. 0,
  1084. (LPWSTR)&msgBuff,
  1085. 0,
  1086. NULL);
  1087. if ( msgBytes > 0 ) {
  1088. ClusResLogSystemEventByKey2(Resource->ResKey,
  1089. LOG_CRITICAL,
  1090. RES_NETNAME_ADD_COMPUTER_ACCOUNT_FAILED,
  1091. dcInfo->DomainName,
  1092. msgBuff);
  1093. LocalFree( msgBuff );
  1094. } else {
  1095. ClusResLogSystemEventByKeyData1(Resource->ResKey,
  1096. LOG_CRITICAL,
  1097. RES_NETNAME_ADD_COMPUTER_ACCOUNT_FAILED_STATUS,
  1098. sizeof( status ),
  1099. &status,
  1100. dcInfo->DomainName);
  1101. }
  1102. }
  1103. if ( machinePwd != NULL ) {
  1104. //
  1105. // don't zero out the string since we don't know if it was
  1106. // properly constructed, i.e., decryption might have failed.
  1107. //
  1108. HeapFree( GetProcessHeap(), 0, machinePwd );
  1109. }
  1110. if ( deleteObjectOnFailure ) {
  1111. //
  1112. // only delete the object if we created it. It is possible that
  1113. // the name was online at one time and the object was properly
  1114. // created allowing additional information/SPNs to be registered
  1115. // with the CO. For this reason, we shouldn't undo the work done
  1116. // by other applications.
  1117. //
  1118. NetNameDeleteComputerObject( Resource );
  1119. }
  1120. }
  1121. NetApiBufferFree( dcInfo );
  1122. return status;
  1123. } // NetNameAddComputerObject
  1124. HRESULT
  1125. GetComputerObjectGuid(
  1126. IN PNETNAME_RESOURCE Resource
  1127. )
  1128. /*++
  1129. Routine Description:
  1130. For the given resource, find its computer object's GUID in the DS
  1131. Arguments:
  1132. Resource - pointer to netname resource context block
  1133. ResourceHandle - used to log in cluster log
  1134. Return Value:
  1135. ERROR_SUCCESS, otherwise appropriate Win32 error
  1136. --*/
  1137. {
  1138. LPWSTR virtualFQDN;
  1139. HRESULT hr;
  1140. //
  1141. // get the FQ Distinguished Name of the object
  1142. //
  1143. hr = GetFQDN(Resource->ResourceHandle,
  1144. Resource->NodeName,
  1145. Resource->Params.NetworkName,
  1146. NULL, /* DsHandle */
  1147. NULL, /* NTDomainName */
  1148. &virtualFQDN);
  1149. if ( hr == ERROR_SUCCESS ) {
  1150. IDirectoryObject * compObj = NULL;
  1151. //
  1152. // get a COM pointer to the computer object
  1153. //
  1154. hr = GetComputerObjectViaFQDN( virtualFQDN, NULL, &compObj );
  1155. if ( SUCCEEDED( hr )) {
  1156. IADs * pADs = NULL;
  1157. //
  1158. // get a pointer to the generic IADs interface so we can get the
  1159. // GUID
  1160. //
  1161. hr = compObj->QueryInterface(IID_IADs, (void**) &pADs);
  1162. if ( SUCCEEDED( hr )) {
  1163. BSTR guidStr = NULL;
  1164. hr = pADs->get_GUID( &guidStr );
  1165. if ( SUCCEEDED( hr )) {
  1166. if ( Resource->ObjectGUID != NULL ) {
  1167. LocalFree( Resource->ObjectGUID );
  1168. }
  1169. Resource->ObjectGUID = ResUtilDupString( guidStr );
  1170. }
  1171. if ( guidStr ) {
  1172. SysFreeString( guidStr );
  1173. }
  1174. }
  1175. if ( pADs != NULL ) {
  1176. pADs->Release();
  1177. }
  1178. }
  1179. if ( compObj != NULL ) {
  1180. compObj->Release();
  1181. }
  1182. LocalFree( virtualFQDN );
  1183. }
  1184. return hr;
  1185. } // GetComputerObjectGuid
  1186. HRESULT
  1187. CheckComputerObjectAttributes(
  1188. IN PNETNAME_RESOURCE Resource
  1189. )
  1190. /*++
  1191. Routine Description:
  1192. LooksAlive routine for computer object. Using an IDirectoryObject pointer
  1193. to the virtual CO, check its DnsHostName and SPN attributes
  1194. Arguments:
  1195. None
  1196. Return Value:
  1197. None
  1198. --*/
  1199. {
  1200. HRESULT hr;
  1201. ADS_ATTR_INFO * attributeInfo = NULL;
  1202. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  1203. IDirectoryObject * compObj = NULL;
  1204. //
  1205. // get a pointer to our CO
  1206. //
  1207. hr = GetComputerObjectViaGUID( Resource->ObjectGUID, NULL, &compObj );
  1208. if ( SUCCEEDED( hr )) {
  1209. LPWSTR attributeNames[2] = { L"DnsHostName", L"ServicePrincipalName" };
  1210. DWORD numAttributes = COUNT_OF( attributeNames );
  1211. DWORD countOfAttrs;;
  1212. hr = compObj->GetObjectAttributes(attributeNames,
  1213. numAttributes,
  1214. &attributeInfo,
  1215. &countOfAttrs );
  1216. if ( SUCCEEDED( hr )) {
  1217. DWORD i;
  1218. WCHAR fqDnsName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  1219. DWORD nodeCharCount;
  1220. DWORD fqDnsSize;
  1221. BOOL setUnexpected = FALSE;
  1222. BOOL success;
  1223. ADS_ATTR_INFO * attrInfo;
  1224. //
  1225. // check that we got our attributes
  1226. //
  1227. if ( countOfAttrs != numAttributes ) {
  1228. (NetNameLogEvent)(resourceHandle,
  1229. LOG_ERROR,
  1230. L"DnsHostName and/or ServicePrincipalName attributes are "
  1231. L"missing from computer account in DS.\n");
  1232. hr = E_UNEXPECTED;
  1233. goto cleanup;
  1234. }
  1235. //
  1236. // build our FQDnsName using the primary DNS domain for this
  1237. // node. Add 1 for the dot.
  1238. //
  1239. nodeCharCount = wcslen( Resource->Params.NetworkName ) + 1;
  1240. wcscpy( fqDnsName, Resource->Params.NetworkName );
  1241. wcscat( fqDnsName, L"." );
  1242. fqDnsSize = COUNT_OF( fqDnsName ) - nodeCharCount;
  1243. success = GetComputerNameEx( ComputerNameDnsDomain,
  1244. &fqDnsName[ nodeCharCount ],
  1245. &fqDnsSize );
  1246. ASSERT( success );
  1247. attrInfo = attributeInfo;
  1248. for( i = 0; i < countOfAttrs; i++, attrInfo++ ) {
  1249. if ( _wcsicmp( attrInfo->pszAttrName, L"DnsHostName" ) == 0 ) {
  1250. //
  1251. // should only be one entry and it should match our constructed FQDN
  1252. //
  1253. if ( attrInfo->dwNumValues == 1 ) {
  1254. if ( _wcsicmp( attrInfo->pADsValues->CaseIgnoreString,
  1255. fqDnsName ) != 0 )
  1256. {
  1257. (NetNameLogEvent)(resourceHandle,
  1258. LOG_ERROR,
  1259. L"DnsHostName attribute in DS doesn't match. "
  1260. L"Expected: %1!ws! Actual: %2!ws!\n",
  1261. fqDnsName,
  1262. attrInfo->pADsValues->CaseIgnoreString);
  1263. setUnexpected = TRUE;
  1264. }
  1265. }
  1266. else {
  1267. (NetNameLogEvent)(resourceHandle,
  1268. LOG_ERROR,
  1269. L"Found more than one string for DnsHostName attribute in DS.\n");
  1270. setUnexpected = TRUE;
  1271. }
  1272. }
  1273. else {
  1274. //
  1275. // SPNs require more work since we publish two and other
  1276. // services may have added their SPNs.
  1277. //
  1278. if ( attrInfo->dwNumValues >= 2 ) {
  1279. DWORD countOfOurSPNs = 0;
  1280. DWORD value;
  1281. for ( value = 0; value < attrInfo->dwNumValues; value++, attrInfo->pADsValues++) {
  1282. if ( _wcsnicmp( attrInfo->pADsValues->CaseIgnoreString, L"HOST/", 5 ) == 0 ) {
  1283. PWCHAR hostName = attrInfo->pADsValues->CaseIgnoreString + 5;
  1284. if ( _wcsicmp( hostName, fqDnsName ) == 0 ) {
  1285. ++countOfOurSPNs;
  1286. }
  1287. else {
  1288. PWCHAR dot = wcschr( fqDnsName, L'.' );
  1289. //
  1290. // try again, this time with our Netbios variant
  1291. //
  1292. if ( dot ) {
  1293. *dot = UNICODE_NULL;
  1294. }
  1295. if ( _wcsicmp( hostName, fqDnsName ) == 0 ) {
  1296. ++countOfOurSPNs;
  1297. }
  1298. if ( !dot ) {
  1299. *dot = L'.';
  1300. }
  1301. }
  1302. } // if we found a HOST SPN
  1303. } // end of for each SPN value
  1304. if ( countOfOurSPNs != 2 ) {
  1305. (NetNameLogEvent)(resourceHandle,
  1306. LOG_ERROR,
  1307. L"There are missing HOST entries for ServicePrincipalName "
  1308. L"attribute in DS.\n");
  1309. setUnexpected = TRUE;
  1310. }
  1311. }
  1312. else {
  1313. (NetNameLogEvent)(resourceHandle,
  1314. LOG_ERROR,
  1315. L"Found less than two entries for ServicePrincipalName "
  1316. L"attribute in DS.\n");
  1317. setUnexpected = TRUE;
  1318. }
  1319. }
  1320. } // for each attribute info entry
  1321. if ( setUnexpected ) {
  1322. hr = E_UNEXPECTED;
  1323. }
  1324. } // if GetObjectAttributes succeeded
  1325. else {
  1326. (NetNameLogEvent)(resourceHandle,
  1327. LOG_ERROR,
  1328. L"Unable find attributes for computer object in DS. status %1!08X!.\n",
  1329. hr);
  1330. }
  1331. } // if GetComputerObjectViaFQDN succeeded
  1332. else {
  1333. (NetNameLogEvent)(resourceHandle,
  1334. LOG_ERROR,
  1335. L"Unable find computer object in DS. status %1!08X!.\n",
  1336. hr);
  1337. }
  1338. cleanup:
  1339. if ( attributeInfo != NULL ) {
  1340. FreeADsMem( attributeInfo );
  1341. }
  1342. if ( compObj != NULL ) {
  1343. compObj->Release();
  1344. }
  1345. return hr;
  1346. } // CheckComputerObjectAttributes
  1347. DWORD
  1348. IsComputerObjectInDS(
  1349. IN LPWSTR NodeName,
  1350. IN LPWSTR NewObjectName,
  1351. OUT PBOOL ObjectExists
  1352. )
  1353. /*++
  1354. Routine Description:
  1355. See if the specified name has a computer object in the DS. We do this by:
  1356. 1) binding to a domain controller in the domain and QI'ing for an IDirectorySearch object
  1357. 2) specifying (&(objectCategory=computer)(cn=<new name>)) as the search string
  1358. 3) examining result count of search; 1 means it exists.
  1359. Arguments:
  1360. NewObjectName - requested new name of object
  1361. ObjectExists - TRUE if object already exists; only valid if function status is success
  1362. Return Value:
  1363. ERROR_SUCCESS if everything worked
  1364. --*/
  1365. {
  1366. BOOL objectExists;
  1367. HRESULT hr;
  1368. DWORD charsFormatted;
  1369. LPWSTR commonName = L"cn";
  1370. WCHAR bindingString[ DNS_MAX_NAME_BUFFER_LENGTH + COUNT_OF( LdapHeader ) ];
  1371. WCHAR searchLeader[] = L"(&(objectCategory=computer)(cn=";
  1372. WCHAR searchTrailer[] = L"))";
  1373. WCHAR searchFilter[ COUNT_OF( searchLeader ) + MAX_COMPUTERNAME_LENGTH + COUNT_OF( searchTrailer )];
  1374. ADS_SEARCHPREF_INFO searchPrefs[2];
  1375. IDirectorySearch * pDSSearch = NULL;
  1376. ADS_SEARCH_HANDLE searchHandle;
  1377. PDOMAIN_CONTROLLER_INFO dcInfo;
  1378. //
  1379. // get DC related info
  1380. //
  1381. hr = FindDomainForServer( NodeName, &dcInfo );
  1382. if ( hr != ERROR_SUCCESS ) {
  1383. return hr;
  1384. }
  1385. //
  1386. // format an LDAP binding string for DNS suffix of the domain.
  1387. //
  1388. _snwprintf( bindingString, COUNT_OF( bindingString ) - 1, L"%ws%ws", LdapHeader, dcInfo->DomainName );
  1389. hr = ADsGetObject( bindingString, IID_IDirectorySearch, (VOID **)&pDSSearch );
  1390. if ( FAILED( hr )) {
  1391. goto cleanup;
  1392. }
  1393. //
  1394. // build search preference array. we limit the size to one and we want to
  1395. // scope the search to check all subtrees.
  1396. //
  1397. searchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SIZE_LIMIT;
  1398. searchPrefs[0].vValue.dwType = ADSTYPE_INTEGER;
  1399. searchPrefs[0].vValue.Integer = 1;
  1400. searchPrefs[1].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
  1401. searchPrefs[1].vValue.dwType = ADSTYPE_INTEGER;
  1402. searchPrefs[0].vValue.Integer = ADS_SCOPE_SUBTREE;
  1403. hr = pDSSearch->SetSearchPreference( searchPrefs, COUNT_OF( searchPrefs ));
  1404. if ( FAILED( hr )) {
  1405. goto cleanup;
  1406. }
  1407. //
  1408. // build the search filter and execute the search; constrain the
  1409. // attributes to just the common name. This is just an existance test.
  1410. //
  1411. charsFormatted = _snwprintf(searchFilter,
  1412. COUNT_OF( searchFilter ) - 1,
  1413. L"%ws%ws%ws",
  1414. searchLeader,
  1415. NewObjectName,
  1416. searchTrailer);
  1417. ASSERT( charsFormatted > COUNT_OF( searchLeader ));
  1418. hr = pDSSearch->ExecuteSearch(searchFilter,
  1419. &commonName,
  1420. 1,
  1421. &searchHandle);
  1422. if ( FAILED( hr )) {
  1423. goto cleanup;
  1424. }
  1425. //
  1426. // try to get the first row. Anything but S_OK returns FALSE
  1427. //
  1428. hr = pDSSearch->GetFirstRow( searchHandle );
  1429. *ObjectExists = (hr == S_OK);
  1430. if ( hr == S_ADS_NOMORE_ROWS ) {
  1431. hr = S_OK;
  1432. }
  1433. pDSSearch->CloseSearchHandle( searchHandle );
  1434. cleanup:
  1435. if ( pDSSearch != NULL ) {
  1436. pDSSearch->Release();
  1437. }
  1438. if ( dcInfo != NULL ) {
  1439. NetApiBufferFree( dcInfo );
  1440. }
  1441. return hr;
  1442. } // IsComputerObjectInDS
  1443. HRESULT
  1444. RenameComputerObject(
  1445. IN PNETNAME_RESOURCE Resource,
  1446. IN LPWSTR NewName OPTIONAL
  1447. )
  1448. /*++
  1449. Routine Description:
  1450. Rename the computer object at the DS. Do this by:
  1451. 1) using the supplied name or calling NetnameGetParams to get new name
  1452. from the name property from the registry
  1453. 2) get the FQDN of the computer object
  1454. 3) get the parent FQDN by calling GetObjectInfomation
  1455. 4) get an IADsContainer pointer to the parent object
  1456. 5) call MoveHere with an updated FQDN to change the name
  1457. Current status of renaming computer objects as of 4/5/01 - charlwi
  1458. While this routine will rename the computer object (if the user account of
  1459. the calling thread has the proper permissions), MoveHere does not fix any
  1460. other properties that allow the renamed object to be useful. At a minimum,
  1461. SamAccountName needs to be updated with the new name. When the name goes
  1462. back online, netname will fix the DnsHostName and SPN attributes to be
  1463. correct. The workaround is to use adsiedit.msc (from the resource kit) to
  1464. rename the object and change SamAccountName under the mandatory property
  1465. list.
  1466. The good news is that MoveHere is not affected by child objects, such as
  1467. MSMQ configuration objects. The bad news is that the creator of the object
  1468. does not have permission to rename it. By running the cluster service in
  1469. the domain admin's account, COs can be renamed. By default, the creating
  1470. account does not have permission to rename the object nor can it modify
  1471. the permissions on the object to grant itself that permission. It is not
  1472. clear which permission allows renaming to occur.
  1473. What I think this means is that the cluster team needs to work with the DS
  1474. team to integrate virtual COs into the DS in a more cluster adminitrator
  1475. friendly way.
  1476. Arguments:
  1477. Resource - pointer to the netname context block
  1478. Return Value:
  1479. ERROR_SUCCESS if it worked...
  1480. --*/
  1481. {
  1482. LPWSTR newName = NULL;
  1483. LPWSTR oldNameFQDN = NULL;
  1484. HRESULT hr;
  1485. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  1486. IDirectoryObject * oldCompObj = NULL;
  1487. //
  1488. // we shouldn't be here if the name specified in the resource's param
  1489. // block is null.
  1490. //
  1491. if ( Resource->Params.NetworkName == NULL ) {
  1492. ASSERT( Resource->Params.NetworkName != NULL );
  1493. return ERROR_INVALID_PARAMETER;
  1494. }
  1495. if ( NewName == NULL ) {
  1496. //
  1497. // get the name parameter out of the registry.
  1498. //
  1499. newName = ResUtilGetSzValue( Resource->ParametersKey, PARAM_NAME__NAME );
  1500. if (newName == NULL) {
  1501. hr = GetLastError();
  1502. (NetNameLogEvent)(resourceHandle,
  1503. LOG_ERROR,
  1504. L"Unable to read NetworkName parameter to rename computer account, status %1!u!\n",
  1505. hr);
  1506. return hr;
  1507. }
  1508. } else {
  1509. newName = NewName;
  1510. }
  1511. //
  1512. // make sure the name in the registry is different than the current name.
  1513. //
  1514. if ( _wcsicmp( newName, Resource->Params.NetworkName ) == 0 ) {
  1515. hr = ERROR_SUCCESS;
  1516. goto cleanup;
  1517. }
  1518. //
  1519. // get the FQDN of the computer object for the current name. If one
  1520. // doesn't exist then there is nothing to rename.
  1521. //
  1522. hr = GetFQDN(resourceHandle,
  1523. Resource->NodeName,
  1524. Resource->Params.NetworkName,
  1525. NULL,
  1526. NULL,
  1527. &oldNameFQDN);
  1528. if ( hr == DS_NAME_ERROR_NOT_FOUND ) {
  1529. (NetNameLogEvent)(resourceHandle,
  1530. LOG_WARNING,
  1531. L"No computer account was found to rename %1!ws! to %2!ws!.\n",
  1532. Resource->Params.NetworkName,
  1533. newName);
  1534. hr = ERROR_SUCCESS;
  1535. goto cleanup;
  1536. }
  1537. else if ( hr != ERROR_SUCCESS ) {
  1538. (NetNameLogEvent)(resourceHandle,
  1539. LOG_ERROR,
  1540. L"Unable to get distinguished name to rename computer account, status 0x%1!08X!\n",
  1541. hr);
  1542. goto cleanup;
  1543. }
  1544. //
  1545. // get a pointer to our CO
  1546. //
  1547. hr = GetComputerObjectViaFQDN( oldNameFQDN, NULL, &oldCompObj );
  1548. if ( SUCCEEDED( hr )) {
  1549. PADS_OBJECT_INFO objInfo;
  1550. //
  1551. // get the parent's FQDN string; it comes with the LDAP header already
  1552. // on it
  1553. //
  1554. hr = oldCompObj->GetObjectInformation( &objInfo );
  1555. if ( SUCCEEDED( hr )) {
  1556. IADsContainer * parentContainer = NULL;
  1557. //
  1558. // get a pointer to the parent container object
  1559. //
  1560. hr = ADsGetObject( objInfo->pszParentDN, IID_IADsContainer, (void **)&parentContainer );
  1561. if ( SUCCEEDED( hr )) {
  1562. WCHAR cnHeader[] = L"cn=";
  1563. WCHAR newNameRDN[ COUNT_OF( cnHeader ) + MAX_COMPUTERNAME_LENGTH ];
  1564. LPWSTR oldNamePath = NULL;
  1565. DWORD oldNamePathByteLength;
  1566. IDispatch * newNameDispatch = NULL;
  1567. //
  1568. // construct the relative distinguished name for the new name
  1569. //
  1570. _snwprintf( newNameRDN, COUNT_OF( newNameRDN ) - 1, L"%ws%ws", cnHeader, newName );
  1571. //
  1572. // argh. MoveHere needs BSTRs and the source DN needs the LDAP
  1573. // header. sheesh!
  1574. //
  1575. oldNamePathByteLength = ( wcslen( oldNameFQDN ) + COUNT_OF( LdapHeader )) * sizeof( WCHAR );
  1576. oldNamePath = (LPWSTR)HeapAlloc( GetProcessHeap(), 0, oldNamePathByteLength );
  1577. if ( oldNamePath != NULL ) {
  1578. BSTR bstrOldNamePath;
  1579. BSTR bstrNewNameRDN;
  1580. wcscpy( oldNamePath, LdapHeader );
  1581. wcscat( oldNamePath, oldNameFQDN );
  1582. bstrOldNamePath = SysAllocString( oldNamePath );
  1583. bstrNewNameRDN = SysAllocString( newNameRDN );
  1584. if ( bstrOldNamePath != NULL && bstrNewNameRDN != NULL ) {
  1585. hr = parentContainer->MoveHere( bstrOldNamePath, bstrNewNameRDN, &newNameDispatch );
  1586. SysFreeString( bstrOldNamePath );
  1587. SysFreeString( bstrNewNameRDN );
  1588. if ( newNameDispatch != NULL ) {
  1589. newNameDispatch->Release();
  1590. }
  1591. if ( SUCCEEDED( hr )) {
  1592. (NetNameLogEvent)(resourceHandle,
  1593. LOG_INFORMATION,
  1594. L"Renamed computer account for %1!ws! to %2!ws!.\n",
  1595. Resource->Params.NetworkName,
  1596. newName);
  1597. } else {
  1598. (NetNameLogEvent)(resourceHandle,
  1599. LOG_ERROR,
  1600. L"Unable to rename computer account for %1!ws! to %2!ws!. "
  1601. L"status 0x%3!08X!.\n",
  1602. Resource->Params.NetworkName,
  1603. newName,
  1604. hr);
  1605. }
  1606. }
  1607. else {
  1608. if ( bstrOldNamePath != NULL ) {
  1609. SysFreeString( bstrOldNamePath );
  1610. }
  1611. if ( bstrNewNameRDN != NULL ) {
  1612. SysFreeString( bstrNewNameRDN );
  1613. }
  1614. (NetNameLogEvent)(resourceHandle,
  1615. LOG_ERROR,
  1616. L"Unable to allocate memory to rename %1!ws! to %2!ws!.\n",
  1617. Resource->Params.NetworkName,
  1618. newName);
  1619. }
  1620. HeapFree( GetProcessHeap(), 0, oldNamePath );
  1621. }
  1622. else {
  1623. hr = ERROR_NOT_ENOUGH_MEMORY;
  1624. (NetNameLogEvent)(resourceHandle,
  1625. LOG_ERROR,
  1626. L"Unable to allocate memory to rename %1!ws! to %2!ws!.\n",
  1627. Resource->Params.NetworkName,
  1628. newName);
  1629. }
  1630. }
  1631. else {
  1632. (NetNameLogEvent)(resourceHandle,
  1633. LOG_ERROR,
  1634. L"Unable to bind to parent container for computer account rename. "
  1635. L"status 0x%1!08X!.\n",
  1636. hr);
  1637. }
  1638. if ( parentContainer != NULL ) {
  1639. parentContainer->Release();
  1640. }
  1641. FreeADsMem( objInfo );
  1642. } // if GetObjectInformation succeeded
  1643. else {
  1644. (NetNameLogEvent)(resourceHandle,
  1645. LOG_ERROR,
  1646. L"Unable to get computer object information in DS. status 0x%1!08X!.\n",
  1647. hr);
  1648. }
  1649. } // if GetComputerObjectViaFQDN succeeded
  1650. else {
  1651. (NetNameLogEvent)(resourceHandle,
  1652. LOG_ERROR,
  1653. L"Unable find computer object in DS. status 0x%1!08X!.\n",
  1654. hr);
  1655. }
  1656. cleanup:
  1657. if ( newName != NULL && newName != NewName ) {
  1658. LocalFree( newName );
  1659. }
  1660. if ( oldNameFQDN != NULL ) {
  1661. LocalFree( oldNameFQDN );
  1662. }
  1663. if ( oldCompObj != NULL ) {
  1664. oldCompObj->Release();
  1665. }
  1666. return hr;
  1667. } // RenameComputerObject
  1668. DWORD
  1669. UpdateCompObjPassword(
  1670. IN PNETNAME_RESOURCE Resource
  1671. )
  1672. /*++
  1673. Routine Description:
  1674. Description
  1675. Arguments:
  1676. None
  1677. Return Value:
  1678. None
  1679. --*/
  1680. {
  1681. return ERROR_SUCCESS;
  1682. } // UpdateCompObjPassword