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.

4920 lines
166 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. #define SECURITY_WIN32
  17. extern "C" {
  18. #include "clusres.h"
  19. #include "clusstrs.h"
  20. #include "clusrtl.h"
  21. #include <winsock2.h>
  22. #include <lm.h>
  23. #include <lmaccess.h>
  24. #include <sspi.h>
  25. #include <winldap.h>
  26. #include <ntldap.h>
  27. #include <dsgetdc.h>
  28. #include <dsgetdcp.h>
  29. #include <ntdsapi.h>
  30. #include <sddl.h>
  31. #include <objbase.h>
  32. #include <iads.h>
  33. #include <adshlp.h>
  34. #include <adserr.h>
  35. #include "netname.h"
  36. #include "nameutil.h"
  37. }
  38. //
  39. // Constants
  40. //
  41. #define LOG_CURRENT_MODULE LOG_MODULE_NETNAME
  42. #define NTLMSP_NAME TEXT("NTLM")
  43. //
  44. // private structures
  45. //
  46. #define SPN_MAX_SPN_COUNT 6
  47. typedef struct _NN_SPN_LIST {
  48. DWORD SpnCount;
  49. LPWSTR Spn[ SPN_MAX_SPN_COUNT ];
  50. } NN_SPN_LIST, *PNN_SPN_LIST;
  51. //
  52. // Externs
  53. //
  54. extern PLOG_EVENT_ROUTINE NetNameLogEvent;
  55. extern "C" {
  56. DWORD
  57. EncryptNNResourceData(
  58. PNETNAME_RESOURCE Resource,
  59. LPWSTR MachinePwd,
  60. PBYTE * EncryptedData,
  61. PDWORD EncryptedDataLength
  62. );
  63. DWORD
  64. DecryptNNResourceData(
  65. PNETNAME_RESOURCE Resource,
  66. PBYTE EncryptedData,
  67. DWORD EncryptedDataLength,
  68. LPWSTR MachinePwd
  69. );
  70. }
  71. //
  72. // static data
  73. //
  74. static WCHAR LdapHeader[] = L"LDAP://";
  75. //
  76. // partial list of SPNs used by clustering. All netname resources get Host and
  77. // VS SPN. Cluster core name gets server cluster SPN which is used to find
  78. // "clusters" in the DS (for Whistler). This array is order dependent: the
  79. // last entry must be for the core resource.
  80. //
  81. static PWCHAR SPNServiceClass[] = {
  82. L"HOST/",
  83. L"MSClusterVirtualServer/",
  84. L"MSServerCluster/"
  85. };
  86. //
  87. // each SPN ServiceClass is used to form two SPNs: netbios based and DNS
  88. // based. Virtual servers use the first two entries while core resources use
  89. // all three.
  90. //
  91. #define SPN_VS_SPN_COUNT 4
  92. #define SPN_CORE_SPN_COUNT 6
  93. #define SPN_VS_SERVICECLASS_COUNT 2
  94. #define SPN_CORE_SERVICECLASS_COUNT 3
  95. #define SPN_MAX_SERVICECLASS_COUNT 3
  96. #define SPN_MAX_SERVICECLASS_CHARS 23 // virtual server
  97. //
  98. // forward references
  99. //
  100. HRESULT
  101. GetComputerObjectViaFQDN(
  102. IN LPWSTR DistinguishedName,
  103. IN LPWSTR DCName OPTIONAL,
  104. IN OUT IDirectoryObject ** ComputerObject
  105. );
  106. //
  107. // private routines
  108. //
  109. static DWORD
  110. GenerateRandomBytes(
  111. PWSTR Buffer,
  112. DWORD BufferLength
  113. )
  114. /*++
  115. Routine Description:
  116. Generate random bytes for a password. Length is specified in characters
  117. and allows room for the trailing null.
  118. Arguments:
  119. Buffer - pointer to area to receive random data
  120. BufferLength - size of Buffer in characters
  121. Return Value:
  122. ERROR_SUCCESS otherwise GetLastError()
  123. --*/
  124. {
  125. HCRYPTPROV cryptProvider;
  126. DWORD status = ERROR_SUCCESS;
  127. DWORD charLength = BufferLength - 1;
  128. DWORD byteLength = charLength * sizeof( WCHAR );
  129. BOOL success;
  130. if ( !CryptAcquireContext(&cryptProvider,
  131. NULL,
  132. NULL,
  133. PROV_RSA_FULL,
  134. CRYPT_VERIFYCONTEXT
  135. )) {
  136. return GetLastError();
  137. }
  138. //
  139. // leave room for the terminating null
  140. //
  141. if (CryptGenRandom( cryptProvider, byteLength, (BYTE *)Buffer )) {
  142. //
  143. // run down the array as WCHARs to make sure there is no premature
  144. // terminating NULL
  145. //
  146. PWCHAR pw = Buffer;
  147. while ( charLength-- ) {
  148. if ( *pw == UNICODE_NULL ) {
  149. *pw = 0xA3F5;
  150. }
  151. ++pw;
  152. }
  153. *pw = UNICODE_NULL;
  154. } else {
  155. status = GetLastError();
  156. }
  157. success = CryptReleaseContext( cryptProvider, 0 );
  158. ASSERT( success );
  159. return status;
  160. } // GenerateRandomBytes
  161. DWORD
  162. FindDomainForServer(
  163. IN RESOURCE_HANDLE ResourceHandle,
  164. IN PWSTR Server,
  165. IN PWSTR DCName, OPTIONAL
  166. IN DWORD DSFlags,
  167. OUT PDOMAIN_CONTROLLER_INFO * DCInfo
  168. )
  169. /*++
  170. Routine Description:
  171. get the name of a DC for our node
  172. Arguments:
  173. Server - pointer to string containing our server (i.e., node) name
  174. DCName - non-null if we should connect to a specific DC
  175. DSFlags - any specific flags needed by the caller
  176. DCInfo - address of a pointer that receives a pointer to DC information
  177. Return Value:
  178. ERROR_SUCCESS, otherwise appropriate Win32 error. If successful, caller must
  179. free DCInfo buffer.
  180. --*/
  181. {
  182. ULONG status;
  183. WCHAR localServerName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  184. PDOMAIN_CONTROLLER_INFOW dcInfo;
  185. //
  186. // MAX_COMPUTERNAME_LENGTH is defined to be 15 but I could create computer
  187. // objects with name lengths of up to 20 chars. 15 is the max number of
  188. // chars for a Netbios name. In any case, we'll leave ourselves extra room
  189. // by using the DNS constants. Leave space for the dollar sign and
  190. // trailing null.
  191. //
  192. wcsncpy( localServerName, Server, DNS_MAX_LABEL_LENGTH - 2 );
  193. wcscat( localServerName, L"$" );
  194. //
  195. // specifying that a DS is required makes us home in on a W2K or better DC
  196. // (as opposed to an NT4 PDC).
  197. //
  198. DSFlags |= DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME;
  199. retry_findDC:
  200. status = DsGetDcNameWithAccountW(DCName,
  201. localServerName,
  202. UF_MACHINE_ACCOUNT_MASK,
  203. L"",
  204. NULL,
  205. NULL,
  206. DSFlags,
  207. &dcInfo );
  208. #if DBG
  209. (NetNameLogEvent)(ResourceHandle,
  210. LOG_INFORMATION,
  211. L"FindDomainForServer: DsGetGetDcName returned %1!u! with flags %2!08X!\n",
  212. status,
  213. DSFlags);
  214. #endif
  215. if ( status == NO_ERROR ) {
  216. HANDLE dsHandle = NULL;
  217. //
  218. // make sure it is really available by binding to it
  219. //
  220. status = DsBindW( dcInfo->DomainControllerName, NULL, &dsHandle );
  221. #if DBG
  222. (NetNameLogEvent)(ResourceHandle,
  223. LOG_INFORMATION,
  224. L"FindDomainForServer: DsBind returned %1!u!\n",
  225. status);
  226. #endif
  227. if (DCName == NULL && status != NO_ERROR && !(DSFlags & DS_FORCE_REDISCOVERY )) {
  228. //
  229. // couldn't bind to the returned DC and we haven't forced a
  230. // rediscovery; do so now
  231. //
  232. (NetNameLogEvent)(ResourceHandle,
  233. LOG_WARNING,
  234. L"FindDomainForServer: Bind to DS failed (status %1!u). "
  235. L"Forcing discovery of available DC.\n",
  236. status);
  237. NetApiBufferFree( dcInfo );
  238. dcInfo = NULL;
  239. DSFlags |= DS_FORCE_REDISCOVERY;
  240. goto retry_findDC;
  241. }
  242. else if ( status == NO_ERROR ) {
  243. DsUnBind( &dsHandle );
  244. *DCInfo = dcInfo;
  245. } else {
  246. //
  247. // some other error; free up DC info and return error to caller
  248. //
  249. NetApiBufferFree( dcInfo );
  250. }
  251. }
  252. return status;
  253. } // FindDomainForServer
  254. HRESULT
  255. AddDnsHostNameAttribute(
  256. RESOURCE_HANDLE ResourceHandle,
  257. IDirectoryObject * CompObj,
  258. PWCHAR VirtualName,
  259. BOOL Rename
  260. )
  261. /*++
  262. Routine Description:
  263. add the DnsHostName attribute to the computer object for the specified
  264. virtual name.
  265. Arguments:
  266. ResourceHandle - used to log in cluster log
  267. CompObj - IDirObj COM pointer to object
  268. VirtualName - network name
  269. Rename - true if we're renaming the CO
  270. Return Value:
  271. ERROR_SUCCESS, otherwise appropriate Win32 error
  272. --*/
  273. {
  274. HRESULT hr;
  275. DWORD numberModified;
  276. WCHAR dnsSuffix[ DNS_MAX_NAME_BUFFER_LENGTH ];
  277. DWORD dnsSuffixSize;
  278. WCHAR FQDnsName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  279. BOOL success;
  280. ADSVALUE attrValue;
  281. ADS_ATTR_INFO attrInfo;
  282. //
  283. // get the node's primary DNS domain
  284. //
  285. dnsSuffixSize = COUNT_OF( dnsSuffix );
  286. success = GetComputerNameEx(ComputerNameDnsDomain,
  287. dnsSuffix,
  288. &dnsSuffixSize);
  289. if ( success ) {
  290. //
  291. // build the FQ Dns name for this host
  292. //
  293. FQDnsName[ COUNT_OF( FQDnsName ) - 1 ] = UNICODE_NULL;
  294. _snwprintf( FQDnsName, COUNT_OF( FQDnsName ) - 1, L"%ws.%ws", VirtualName, dnsSuffix );
  295. attrValue.dwType = ADSTYPE_CASE_IGNORE_STRING;
  296. attrValue.CaseIgnoreString = FQDnsName;
  297. attrInfo.pszAttrName = L"dNSHostName";
  298. attrInfo.dwControlCode = ADS_ATTR_UPDATE;
  299. attrInfo.dwADsType = ADSTYPE_CASE_IGNORE_STRING;
  300. attrInfo.pADsValues = &attrValue;
  301. attrInfo.dwNumValues = 1;
  302. hr = CompObj->SetObjectAttributes( &attrInfo, 1, &numberModified );
  303. if ( SUCCEEDED( hr ) && numberModified != 1 ) {
  304. //
  305. // don't know why this scenario would happen but we'd better log
  306. // it since it is unusual
  307. //
  308. (NetNameLogEvent)(ResourceHandle,
  309. LOG_ERROR,
  310. L"Setting DnsHostName attribute succeeded but NumberModified is zero!\n");
  311. hr = E_ADS_PROPERTY_NOT_SET;
  312. }
  313. if ( Rename && SUCCEEDED( hr )) {
  314. WCHAR dollarName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  315. //
  316. // try to update the display name as well. Default access rights
  317. // don't allow this to be written but in case the domain admin has
  318. // given the cluster service account additional privileges, we
  319. // might be able to update it
  320. //
  321. dollarName[ COUNT_OF( dollarName ) - 1 ] = UNICODE_NULL;
  322. _snwprintf( dollarName, COUNT_OF( dollarName ) - 1, L"%ws$", VirtualName );
  323. attrValue.dwType = ADSTYPE_CASE_IGNORE_STRING;
  324. attrValue.CaseIgnoreString = dollarName;
  325. attrInfo.pszAttrName = L"displayName";
  326. attrInfo.dwControlCode = ADS_ATTR_UPDATE;
  327. attrInfo.dwADsType = ADSTYPE_CASE_IGNORE_STRING;
  328. attrInfo.pADsValues = &attrValue;
  329. attrInfo.dwNumValues = 1;
  330. hr = CompObj->SetObjectAttributes( &attrInfo, 1, &numberModified );
  331. if ( FAILED( hr )) {
  332. (NetNameLogEvent)(ResourceHandle,
  333. LOG_WARNING,
  334. L"Failed to set DisplayName attribute. status %1!08X!\n",
  335. hr);
  336. hr = NO_ERROR;
  337. }
  338. }
  339. }
  340. else {
  341. hr = HRESULT_FROM_WIN32( GetLastError() );
  342. }
  343. return hr;
  344. } // AddDnsHostNameAttribute
  345. DWORD
  346. BuildNetNameSPNs(
  347. IN LPWSTR HostName,
  348. IN BOOL CoreResource,
  349. OUT PNN_SPN_LIST * SpnList
  350. )
  351. /*++
  352. Routine Description:
  353. For the given name, build the appropriate list of SPNs. Caller frees
  354. returned list with only one call to LocalFree().
  355. Arguments:
  356. HostName - host name to be used as part of the SPN
  357. CoreResource - TRUE if this is the core netname resource. It gets extra SPNs
  358. SpnList - address of pointer that receives structure containing array of
  359. pointers to SPNs
  360. Return Value:
  361. ERROR_SUCCESS if everything worked ok....
  362. --*/
  363. {
  364. DWORD spnCount;
  365. DWORD serviceClassCount;
  366. PWCHAR spnBuffer;
  367. DWORD charsLeft;
  368. DWORD spnSize;
  369. DWORD i;
  370. BOOL success;
  371. WCHAR dnsDomain[ DNS_MAX_NAME_BUFFER_LENGTH ];
  372. DWORD dnsDomainChars;
  373. DWORD hostNameChars = wcslen( HostName );
  374. PNN_SPN_LIST spnList;
  375. //
  376. // get the node's DNS domain
  377. //
  378. dnsDomainChars = COUNT_OF( dnsDomain );
  379. success = GetComputerNameEx( ComputerNameDnsDomain, dnsDomain, &dnsDomainChars );
  380. if ( !success ) {
  381. return GetLastError();
  382. }
  383. //
  384. // compute the space needed for the SPN List - minimum is for virtual servers
  385. //
  386. if ( CoreResource ) {
  387. spnCount = SPN_CORE_SPN_COUNT;
  388. serviceClassCount = SPN_CORE_SERVICECLASS_COUNT;
  389. } else {
  390. spnCount = SPN_VS_SPN_COUNT;
  391. serviceClassCount = SPN_VS_SERVICECLASS_COUNT;
  392. }
  393. //
  394. // total size is: the header, the size of the Netbios based SPNs, and the
  395. // size of the DNS based SPNs.
  396. //
  397. spnSize = sizeof( NN_SPN_LIST ) +
  398. ( serviceClassCount * ( SPN_MAX_SERVICECLASS_CHARS + hostNameChars + 1 ) +
  399. serviceClassCount * ( SPN_MAX_SERVICECLASS_CHARS + dnsDomainChars )
  400. ) * sizeof( WCHAR );
  401. spnList = (PNN_SPN_LIST)LocalAlloc( LMEM_FIXED, spnSize );
  402. if ( spnList == NULL ) {
  403. return GetLastError();
  404. }
  405. //
  406. // build up the structure
  407. //
  408. spnList->SpnCount = spnCount;
  409. spnBuffer = (PWCHAR)(spnList + 1);
  410. charsLeft = ( spnSize - sizeof( NN_SPN_LIST )) / sizeof( WCHAR );
  411. for ( i = 0; i < serviceClassCount; ++i ) {
  412. LONG charCount;
  413. //
  414. // build Netbios based SPN
  415. //
  416. spnList->Spn[ 2 * i ] = spnBuffer;
  417. charCount = _snwprintf( spnBuffer, charsLeft, L"%ws%ws", SPNServiceClass[i], HostName );
  418. ASSERT( charCount > 0 );
  419. spnBuffer += ( charCount + 1 );
  420. charsLeft -= ( charCount + 1 );
  421. //
  422. // build DNS based SPN
  423. //
  424. spnList->Spn[ 2 * i + 1 ] = spnBuffer;
  425. charCount = _snwprintf( spnBuffer, charsLeft, L"%ws%ws.%ws", SPNServiceClass[i], HostName, dnsDomain );
  426. ASSERT( charCount > 0 );
  427. spnBuffer += ( charCount + 1 );
  428. charsLeft -= ( charCount + 1 );
  429. }
  430. *SpnList = spnList;
  431. return ERROR_SUCCESS;
  432. } // BuildNetNameSPNs
  433. DWORD
  434. AddServicePrincipalNames(
  435. HANDLE DsHandle,
  436. PWCHAR VirtualFQDN,
  437. PWCHAR VirtualName,
  438. PWCHAR DnsDomain,
  439. BOOL IsCoreResource
  440. )
  441. /*++
  442. Routine Description:
  443. add the DNS and Netbios host service principal names to the specified
  444. virtual name. These are: HOST, MSClusterVirtualServer, and MSServerCluster
  445. if it is the core resource.
  446. Arguments:
  447. DsHandle - handle obtained from DsBind
  448. VirtualFQDN - distinguished name of computer object for the virtual netname
  449. VirtualName - the network name to be added
  450. DnsDomain - the DNS domain used to construct the DNS SPN
  451. IsCoreResource - true if we need to add the core resource SPN
  452. Return Value:
  453. ERROR_SUCCESS, otherwise appropriate Win32 error
  454. --*/
  455. {
  456. DWORD status;
  457. PNN_SPN_LIST spnList;
  458. //
  459. // get a list of the SPNs
  460. //
  461. status = BuildNetNameSPNs( VirtualName, IsCoreResource, &spnList );
  462. if ( status != ERROR_SUCCESS ) {
  463. return status;
  464. }
  465. //
  466. //
  467. //
  468. // write the SPNs to the DS
  469. //
  470. status = DsWriteAccountSpnW(DsHandle,
  471. DS_SPN_ADD_SPN_OP,
  472. VirtualFQDN,
  473. spnList->SpnCount,
  474. (LPCWSTR *)spnList->Spn);
  475. LocalFree( spnList );
  476. return status;
  477. } // AddServicePrincipalNames
  478. DWORD
  479. SetACLOnParametersKey(
  480. HKEY ParametersKey
  481. )
  482. /*++
  483. Routine Description:
  484. Set the ACL on the params key to allow only admin group and creator/owner
  485. to have access to the data
  486. Arguments:
  487. ParametersKey - cluster HKEY to the netname's params key
  488. Return Value:
  489. ERROR_SUCCESS if successful
  490. --*/
  491. {
  492. DWORD status = ERROR_SUCCESS;
  493. BOOL success;
  494. PSECURITY_DESCRIPTOR secDesc = NULL;
  495. //
  496. // build an SD the quick way. This gives builtin admins (local admins's
  497. // group), creator/owner and the service SID full access to the
  498. // key. Inheritance is prevented in both directions, i.e., doesn't inherit
  499. // from its parent nor passes the settings onto its children (of which the
  500. // node parameters keys are the only children).
  501. //
  502. success = ConvertStringSecurityDescriptorToSecurityDescriptor(
  503. L"D:P(A;;KA;;;BA)(A;;KA;;;CO)(A;;KA;;;SU)",
  504. SDDL_REVISION_1,
  505. &secDesc,
  506. NULL);
  507. if ( success &&
  508. (secDesc != NULL) ) {
  509. status = ClusterRegSetKeySecurity(ParametersKey,
  510. DACL_SECURITY_INFORMATION,
  511. secDesc);
  512. LocalFree( secDesc );
  513. }
  514. else {
  515. if ( secDesc != NULL )
  516. {
  517. LocalFree( secDesc );
  518. status = GetLastError();
  519. }
  520. }
  521. return status;
  522. } // SetACLOnParametersKey
  523. HRESULT
  524. GetComputerObjectViaFQDN(
  525. IN LPWSTR DistinguishedName,
  526. IN LPWSTR DCName OPTIONAL,
  527. IN OUT IDirectoryObject ** ComputerObject
  528. )
  529. /*++
  530. Routine Description:
  531. for the specified distinguished name, get an IDirectoryObject pointer to
  532. it
  533. Arguments:
  534. DistinguishedName - FQDN of object in DS to find
  535. DCName - optional pointer to name of DC (not domain) that we should bind to
  536. ComputerObject - address of pointer that receives pointer to computer object
  537. Return Value:
  538. success if everything worked, otherwise....
  539. --*/
  540. {
  541. WCHAR buffer[ 256 ];
  542. PWCHAR bindingString = buffer;
  543. LONG charCount;
  544. HRESULT hr;
  545. DWORD dnLength;
  546. DWORD adsFlags = ADS_SECURE_AUTHENTICATION;
  547. //
  548. // format an LDAP binding string for our distingiushed name. If DCName is
  549. // specified, we need to add a trailing "/".
  550. //
  551. dnLength = (DWORD)( COUNT_OF( LdapHeader ) + wcslen( DistinguishedName ));
  552. if ( DCName != NULL ) {
  553. if ( *DCName == L'\\' && *(DCName+1) == L'\\' ) { // skip over double backslashes
  554. DCName += 2;
  555. }
  556. dnLength += wcslen( DCName ) + 1;
  557. adsFlags |= ADS_SERVER_BIND;
  558. }
  559. if ( dnLength > COUNT_OF( buffer )) {
  560. bindingString = (PWCHAR)LocalAlloc( LMEM_FIXED, dnLength * sizeof( WCHAR ));
  561. if ( bindingString == NULL ) {
  562. return HRESULT_FROM_WIN32( GetLastError());
  563. }
  564. }
  565. wcscpy( bindingString, LdapHeader );
  566. if ( DCName != NULL ) {
  567. wcscat( bindingString, DCName );
  568. wcscat( bindingString, L"/" );
  569. }
  570. wcscat( bindingString, DistinguishedName );
  571. *ComputerObject = NULL;
  572. hr = ADsOpenObject(bindingString,
  573. NULL, // username
  574. NULL, // password
  575. adsFlags,
  576. IID_IDirectoryObject,
  577. (VOID **)ComputerObject );
  578. if ( bindingString != buffer ) {
  579. LocalFree( bindingString );
  580. }
  581. if ( FAILED( hr ) && *ComputerObject != NULL ) {
  582. (*ComputerObject)->Release();
  583. }
  584. return hr;
  585. } // GetComputerObjectViaFQDN
  586. HRESULT
  587. GetComputerObjectViaGUID(
  588. IN LPWSTR ObjectGUID,
  589. IN LPWSTR DCName OPTIONAL,
  590. IN OUT IDirectoryObject ** ComputerObject
  591. )
  592. /*++
  593. Routine Description:
  594. for the specified object GUID, get an IDirectoryObject pointer to it
  595. Arguments:
  596. ObjectGUID - GUID of object in DS to find
  597. DCName - optional pointer to name of DC (not domain) that we should bind to
  598. ComputerObject - address of pointer that receives pointer to computer object
  599. Return Value:
  600. success if everything worked, otherwise....
  601. --*/
  602. {
  603. WCHAR guidHeader[] = L"<GUID=";
  604. WCHAR guidTrailer[] = L">";
  605. LONG charCount;
  606. HRESULT hr;
  607. DWORD dnLength;
  608. DWORD adsFlags = ADS_SECURE_AUTHENTICATION;
  609. //
  610. // 37 = guid length
  611. //
  612. WCHAR buffer[ COUNT_OF( LdapHeader ) +
  613. DNS_MAX_NAME_BUFFER_LENGTH +
  614. COUNT_OF( guidHeader ) +
  615. 37 +
  616. COUNT_OF( guidTrailer ) ];
  617. PWCHAR bindingString = buffer;
  618. //
  619. // format an LDAP binding string for the object GUID. If DCName is
  620. // specified, we need to add a trailing / plus the trailing null.
  621. //
  622. ASSERT( ObjectGUID != NULL );
  623. dnLength = (DWORD)( COUNT_OF( LdapHeader ) +
  624. COUNT_OF( guidHeader ) +
  625. COUNT_OF( guidTrailer ) +
  626. wcslen( ObjectGUID ));
  627. if ( DCName != NULL ) {
  628. if ( *DCName == L'\\' && *(DCName+1) == L'\\' ) { // skip over double backslashes
  629. DCName += 2;
  630. }
  631. dnLength += ( wcslen( DCName ) + 1 );
  632. adsFlags |= ADS_SERVER_BIND;
  633. }
  634. if ( dnLength > COUNT_OF( buffer )) {
  635. bindingString = (PWCHAR)LocalAlloc( LMEM_FIXED, dnLength * sizeof( WCHAR ));
  636. if ( bindingString == NULL ) {
  637. return ERROR_NOT_ENOUGH_MEMORY;
  638. }
  639. }
  640. wcscpy( bindingString, LdapHeader );
  641. if ( DCName != NULL ) {
  642. wcscat( bindingString, DCName );
  643. wcscat( bindingString, L"/" );
  644. }
  645. wcscat( bindingString, guidHeader );
  646. wcscat( bindingString, ObjectGUID );
  647. wcscat( bindingString, guidTrailer );
  648. *ComputerObject = NULL;
  649. hr = ADsOpenObject(bindingString,
  650. NULL, // username
  651. NULL, // password
  652. adsFlags,
  653. IID_IDirectoryObject,
  654. (VOID **)ComputerObject );
  655. if ( bindingString != buffer ) {
  656. LocalFree( bindingString );
  657. }
  658. if ( FAILED( hr ) && *ComputerObject != NULL ) {
  659. (*ComputerObject)->Release();
  660. }
  661. return hr;
  662. } // GetComputerObjectViaGUID
  663. DWORD
  664. DeleteComputerObject(
  665. IN PNETNAME_RESOURCE Resource
  666. )
  667. /*++
  668. Routine Description:
  669. delete the computer object in the DS for this name.
  670. Not called right now since we don't have the virtual netname at this point
  671. in time. The name must be kept around and cleaned during close processing
  672. instead of offline where it is done now. This means dealing with renaming
  673. issues while it is offline but not getting deleted.
  674. Arguments:
  675. Resource - pointer to the resource context info
  676. Return Value:
  677. ERROR_SUCCESS if everything worked
  678. --*/
  679. {
  680. DWORD status;
  681. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  682. HKEY resourceKey = Resource->ResKey;
  683. PWSTR virtualName = Resource->Params.NetworkName;
  684. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  685. PDOMAIN_CONTROLLER_INFO dcInfo;
  686. //
  687. // get the name of a writable DC
  688. //
  689. status = FindDomainForServer( resourceHandle, Resource->NodeName, NULL, DS_WRITABLE_REQUIRED, &dcInfo );
  690. if ( status != ERROR_SUCCESS ) {
  691. (NetNameLogEvent)(resourceHandle,
  692. LOG_ERROR,
  693. L"Unable to find a DC, status %1!u!.\n",
  694. status);
  695. return status;
  696. }
  697. (NetNameLogEvent)(resourceHandle,
  698. LOG_INFORMATION,
  699. L"Using domain controller %1!ws! to delete computer account %2!ws!.\n",
  700. dcInfo->DomainControllerName,
  701. virtualName);
  702. //
  703. // add a $ to the end of the name
  704. //
  705. virtualDollarName[ COUNT_OF( virtualDollarName ) - 1 ] = UNICODE_NULL;
  706. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", virtualName );
  707. status = NetUserDel( dcInfo->DomainControllerName, virtualDollarName );
  708. if ( status == NERR_Success ) {
  709. (NetNameLogEvent)(resourceHandle,
  710. LOG_INFORMATION,
  711. L"Deleted computer account %1!ws! in domain %2!ws!.\n",
  712. virtualName,
  713. dcInfo->DomainName);
  714. ClusResLogSystemEventByKey1(resourceKey,
  715. LOG_NOISE,
  716. RES_NETNAME_COMPUTER_ACCOUNT_DELETED,
  717. dcInfo->DomainName);
  718. if ( Resource->ObjectGUID ) {
  719. LocalFree( Resource->ObjectGUID );
  720. Resource->ObjectGUID = NULL;
  721. }
  722. } else {
  723. LPWSTR msgBuff;
  724. DWORD msgBytes;
  725. (NetNameLogEvent)(resourceHandle,
  726. LOG_WARNING,
  727. L"Unable to delete computer account %1!ws! in domain %2!ws!, status %3!u!.\n",
  728. virtualName,
  729. dcInfo->DomainName,
  730. status);
  731. msgBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  732. FORMAT_MESSAGE_FROM_SYSTEM,
  733. NULL,
  734. status,
  735. 0,
  736. (LPWSTR)&msgBuff,
  737. 0,
  738. NULL);
  739. if ( msgBytes > 0 ) {
  740. ClusResLogSystemEventByKey2(resourceKey,
  741. LOG_UNUSUAL,
  742. RES_NETNAME_DELETE_COMPUTER_ACCOUNT_FAILED,
  743. dcInfo->DomainName,
  744. msgBuff);
  745. LocalFree( msgBuff );
  746. } else {
  747. ClusResLogSystemEventByKeyData1(resourceKey,
  748. LOG_UNUSUAL,
  749. RES_NETNAME_DELETE_COMPUTER_ACCOUNT_FAILED_STATUS,
  750. sizeof( status ),
  751. &status,
  752. dcInfo->DomainName);
  753. }
  754. }
  755. NetApiBufferFree( dcInfo );
  756. return status;
  757. } // DeleteComputerObject
  758. DWORD
  759. NNLogonUser(
  760. RESOURCE_HANDLE ResourceHandle,
  761. LPTSTR UserName,
  762. LPTSTR DomainName,
  763. LPTSTR Password,
  764. PHANDLE VSToken,
  765. PDWORD TokenStatus
  766. )
  767. /*++
  768. Routine Description:
  769. Do a network logon of the machine account credentials by generating a
  770. security context between the cluster service account and the machine.
  771. NTLM is currently used for this purpose though Kerberos should eventually
  772. be used. If it is changed to Kerb, then NNGetAuthenticatingDC has to be
  773. changed to query Kerb for its DC.
  774. Arguments:
  775. ResourceHandle - used to log in cluster log
  776. UserName - pointer to buffer holding machine name with trailing $
  777. DomainName - duh....
  778. Password - double duh...
  779. VSToken - pointer which gets token handle to passed in credentials
  780. TokenStatus - pointer to DWORD returning status on getting token handle
  781. Return Value:
  782. ERROR_SUCCESS if password is good...
  783. --*/
  784. {
  785. SECURITY_STATUS secStatus = SEC_E_OK;
  786. DWORD tokenStatus = ERROR_SUCCESS;
  787. BOOL success;
  788. CredHandle csaCredHandle = { 0, 0 }; // cluster service account
  789. CredHandle computerCredHandle = { 0, 0 }; // machine account
  790. CtxtHandle computerCtxtHandle = { 0, 0 };
  791. CtxtHandle csaCtxtHandle = { 0, 0 };
  792. ULONG contextAttributes;
  793. ULONG packageCount;
  794. ULONG packageIndex;
  795. PSecPkgInfo packageInfo;
  796. DWORD cbMaxToken = 0;
  797. TimeStamp ctxtLifeTime;
  798. SEC_WINNT_AUTH_IDENTITY authIdentity;
  799. SecBufferDesc NegotiateDesc;
  800. SecBuffer NegotiateBuffer;
  801. SecBufferDesc ChallengeDesc;
  802. SecBuffer ChallengeBuffer;
  803. SecBufferDesc AuthenticateDesc;
  804. SecBuffer AuthenticateBuffer;
  805. PVOID computerBuffer = NULL;
  806. PVOID csaBuffer = NULL;
  807. //
  808. // validate parameters
  809. //
  810. if ( DomainName == NULL || UserName == NULL || Password == NULL ) {
  811. return ERROR_INVALID_PARAMETER;
  812. }
  813. //
  814. // << this section could be cached in a repeat caller scenario >>
  815. //
  816. //
  817. // Get info about the security packages.
  818. //
  819. secStatus = EnumerateSecurityPackages( &packageCount, &packageInfo );
  820. if( secStatus != SEC_E_OK ) {
  821. return secStatus;
  822. }
  823. //
  824. // loop through the packages looking for NTLM
  825. //
  826. for(packageIndex = 0 ; packageIndex < packageCount ; packageIndex++ ) {
  827. if(packageInfo[packageIndex].Name != NULL) {
  828. if( ClRtlStrICmp(packageInfo[packageIndex].Name, NTLMSP_NAME) == 0) {
  829. cbMaxToken = packageInfo[packageIndex].cbMaxToken;
  830. break;
  831. }
  832. }
  833. }
  834. FreeContextBuffer( packageInfo );
  835. if( cbMaxToken == 0 ) {
  836. secStatus = ERROR_INVALID_PARAMETER;
  837. goto cleanup;
  838. }
  839. //
  840. // << end of cached section >>
  841. //
  842. //
  843. // Acquire an outbound (client side) credential handle for the computer
  844. // account
  845. //
  846. ZeroMemory( &authIdentity, sizeof(authIdentity) );
  847. authIdentity.Domain = DomainName;
  848. authIdentity.DomainLength = lstrlen(DomainName);
  849. authIdentity.User = UserName;
  850. authIdentity.UserLength = lstrlen(UserName);
  851. authIdentity.Password = Password;
  852. authIdentity.PasswordLength = lstrlen(Password);
  853. authIdentity.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
  854. secStatus = AcquireCredentialsHandle(
  855. NULL, // New principal
  856. NTLMSP_NAME, // Package Name
  857. SECPKG_CRED_OUTBOUND,
  858. NULL,
  859. &authIdentity,
  860. NULL,
  861. NULL,
  862. &computerCredHandle,
  863. &ctxtLifeTime
  864. );
  865. if ( secStatus != SEC_E_OK ) {
  866. goto cleanup;
  867. }
  868. //
  869. // Acquire an inbound (server side) credential handle for the cluster
  870. // service account. The resulting security context will represent the
  871. // computer account.
  872. //
  873. secStatus = AcquireCredentialsHandle(
  874. NULL, // New principal
  875. NTLMSP_NAME, // Package Name
  876. SECPKG_CRED_INBOUND,
  877. NULL,
  878. NULL,
  879. NULL,
  880. NULL,
  881. &csaCredHandle,
  882. &ctxtLifeTime
  883. );
  884. if ( secStatus != SEC_E_OK ) {
  885. goto cleanup;
  886. }
  887. //
  888. // allocate token buffers for the computer and CSA sides.
  889. //
  890. computerBuffer = LocalAlloc( LMEM_FIXED, cbMaxToken);
  891. if( computerBuffer == NULL ) {
  892. secStatus = ERROR_OUTOFMEMORY;
  893. goto cleanup;
  894. }
  895. csaBuffer = LocalAlloc( LMEM_FIXED, cbMaxToken);
  896. if( csaBuffer == NULL ) {
  897. secStatus = ERROR_OUTOFMEMORY;
  898. goto cleanup;
  899. }
  900. //
  901. // Get the NegotiateMessage (ClientSide)
  902. //
  903. NegotiateDesc.ulVersion = 0;
  904. NegotiateDesc.cBuffers = 1;
  905. NegotiateDesc.pBuffers = &NegotiateBuffer;
  906. NegotiateBuffer.cbBuffer = cbMaxToken;
  907. NegotiateBuffer.BufferType = SECBUFFER_TOKEN;
  908. NegotiateBuffer.pvBuffer = computerBuffer;
  909. secStatus = InitializeSecurityContext(
  910. &computerCredHandle,
  911. NULL, // No Client context yet
  912. NULL, // target name
  913. /* ISC_REQ_SEQUENCE_DETECT | ISC_REQ_DELEGATE */ 0,
  914. 0, // Reserved 1
  915. SECURITY_NATIVE_DREP,
  916. NULL, // No initial input token
  917. 0, // Reserved 2
  918. &computerCtxtHandle,
  919. &NegotiateDesc,
  920. &contextAttributes,
  921. &ctxtLifeTime
  922. );
  923. if( secStatus < 0 ) {
  924. goto cleanup;
  925. }
  926. //
  927. // Get the ChallengeMessage (ServerSide)
  928. //
  929. NegotiateBuffer.BufferType |= SECBUFFER_READONLY;
  930. ChallengeDesc.ulVersion = 0;
  931. ChallengeDesc.cBuffers = 1;
  932. ChallengeDesc.pBuffers = &ChallengeBuffer;
  933. ChallengeBuffer.cbBuffer = cbMaxToken;
  934. ChallengeBuffer.BufferType = SECBUFFER_TOKEN;
  935. ChallengeBuffer.pvBuffer = csaBuffer;
  936. secStatus = AcceptSecurityContext(
  937. &csaCredHandle,
  938. NULL, // No Server context yet
  939. &NegotiateDesc,
  940. ASC_REQ_ALLOW_NON_USER_LOGONS, // | ASC_REQ_DELEGATE,
  941. SECURITY_NATIVE_DREP,
  942. &csaCtxtHandle,
  943. &ChallengeDesc,
  944. &contextAttributes,
  945. &ctxtLifeTime
  946. );
  947. if( secStatus < 0 ) {
  948. goto cleanup;
  949. }
  950. //
  951. // Get the AuthenticateMessage (ClientSide)
  952. //
  953. ChallengeBuffer.BufferType |= SECBUFFER_READONLY;
  954. AuthenticateDesc.ulVersion = 0;
  955. AuthenticateDesc.cBuffers = 1;
  956. AuthenticateDesc.pBuffers = &AuthenticateBuffer;
  957. AuthenticateBuffer.cbBuffer = cbMaxToken;
  958. AuthenticateBuffer.BufferType = SECBUFFER_TOKEN;
  959. AuthenticateBuffer.pvBuffer = computerBuffer;
  960. secStatus = InitializeSecurityContext(
  961. NULL,
  962. &computerCtxtHandle,
  963. NULL, // target name
  964. 0,
  965. 0, // Reserved 1
  966. SECURITY_NATIVE_DREP,
  967. &ChallengeDesc,
  968. 0, // Reserved 2
  969. &computerCtxtHandle,
  970. &AuthenticateDesc,
  971. &contextAttributes,
  972. &ctxtLifeTime
  973. );
  974. if ( secStatus < 0 ) {
  975. goto cleanup;
  976. }
  977. //
  978. // Finally authenticate the user (ServerSide)
  979. //
  980. AuthenticateBuffer.BufferType |= SECBUFFER_READONLY;
  981. secStatus = AcceptSecurityContext(
  982. NULL,
  983. &csaCtxtHandle,
  984. &AuthenticateDesc,
  985. ASC_REQ_ALLOW_NON_USER_LOGONS, // | ASC_REQ_DELEGATE,
  986. SECURITY_NATIVE_DREP,
  987. &csaCtxtHandle,
  988. NULL,
  989. &contextAttributes,
  990. &ctxtLifeTime
  991. );
  992. //
  993. // now get a primary token to the virtual computer account if we were
  994. // successful up to this point
  995. //
  996. if ( secStatus == SEC_E_OK ) {
  997. if ( *VSToken != NULL ) {
  998. CloseHandle( *VSToken );
  999. *VSToken = NULL;
  1000. }
  1001. tokenStatus = ImpersonateSecurityContext( &csaCtxtHandle );
  1002. if ( tokenStatus == SEC_E_OK ) {
  1003. SECURITY_STATUS revertStatus;
  1004. //
  1005. // create a new primary token that represents the virtual computer
  1006. // account
  1007. //
  1008. success = LogonUser(UserName,
  1009. DomainName,
  1010. Password,
  1011. LOGON32_LOGON_NEW_CREDENTIALS,
  1012. LOGON32_PROVIDER_DEFAULT,
  1013. VSToken);
  1014. if ( !success ) {
  1015. secStatus = GetLastError();
  1016. (NetNameLogEvent)(ResourceHandle,
  1017. LOG_ERROR,
  1018. L"Could not create primary token for the virtual computer account "
  1019. L"associated with this resource. status %u\n",
  1020. secStatus );
  1021. }
  1022. revertStatus = RevertSecurityContext( &csaCtxtHandle );
  1023. if ( revertStatus != SEC_E_OK ) {
  1024. (NetNameLogEvent)(ResourceHandle,
  1025. LOG_ERROR,
  1026. L"Could not revert this thread's security context back to "
  1027. L"the Cluster Service Account after acquiring a token to "
  1028. L"this resource's virtual computer account - status %u. This will subsequently "
  1029. L"cause different types of access denied failures when this thread "
  1030. L"is used by the resource monitor. The cluster service should be stopped "
  1031. L"and restarted on this node to recover from this situation\n",
  1032. revertStatus);
  1033. tokenStatus = revertStatus;
  1034. }
  1035. } else {
  1036. (NetNameLogEvent)(ResourceHandle,
  1037. LOG_ERROR,
  1038. L"Unable to impersonate virtual computer account - status %1!u!\n",
  1039. tokenStatus);
  1040. }
  1041. *TokenStatus = tokenStatus;
  1042. }
  1043. cleanup:
  1044. //
  1045. // clean up the security contexts
  1046. //
  1047. if ( computerCtxtHandle.dwUpper != 0 && computerCtxtHandle.dwLower != 0 ) {
  1048. DeleteSecurityContext( &computerCtxtHandle );
  1049. }
  1050. if ( csaCtxtHandle.dwUpper != 0 && csaCtxtHandle.dwLower != 0 ) {
  1051. DeleteSecurityContext( &csaCtxtHandle );
  1052. }
  1053. //
  1054. // Free credential handles
  1055. //
  1056. if ( csaCredHandle.dwUpper != 0 && csaCredHandle.dwLower != 0 ) {
  1057. FreeCredentialsHandle( &csaCredHandle );
  1058. }
  1059. if ( computerCredHandle.dwUpper != 0 && computerCredHandle.dwLower != 0 ) {
  1060. FreeCredentialsHandle( &computerCredHandle );
  1061. }
  1062. if( computerBuffer != NULL ) {
  1063. ZeroMemory( computerBuffer, cbMaxToken );
  1064. LocalFree( computerBuffer );
  1065. }
  1066. if( csaBuffer != NULL ) {
  1067. ZeroMemory( csaBuffer, cbMaxToken );
  1068. LocalFree( csaBuffer );
  1069. }
  1070. return secStatus;
  1071. } // NNLogonUser
  1072. #ifdef BUG601100
  1073. don't want to throw this code away since it might be useful in the
  1074. future. It's not clear it is needed for anything at this point. It does make
  1075. sense to restore the ACL back to its original form making the CSA the owner.
  1076. HRESULT
  1077. ResetComputerObjectSecurityDesc(
  1078. IN LPWSTR DCName,
  1079. IN LPWSTR VirtualName,
  1080. IDirectoryObject * ComputerObject
  1081. )
  1082. /*++
  1083. Routine Description:
  1084. Reset the security descriptor on the computer object as specified in the
  1085. schema.
  1086. By default, only domain level admins have this access. The SELF is not
  1087. granted this right since this could lead to DS DoS attacks where millions
  1088. of child objects are created. The Cluster Service Account could be given
  1089. elevated access rights to accomplish but there are other issues involved
  1090. like this code doesn't correctly inherit the ACEs in the parent container.
  1091. Arguments:
  1092. VirtualName - pointer to the name of the object
  1093. Return Value:
  1094. ERROR_SUCCESS
  1095. --*/
  1096. {
  1097. HRESULT hr;
  1098. IADs * rootDSE = NULL;
  1099. IADs * computerSchema = NULL;
  1100. VARIANT schemaNamingCtxt;
  1101. VARIANT computerSchemaSDDL;
  1102. WCHAR computerCN[] = L"/cn=computer,";
  1103. DWORD pathChars;
  1104. LPWSTR computerSchemaPath = NULL;
  1105. DWORD numberModified;
  1106. WCHAR rootDN[] = L"/rootDSE";
  1107. WCHAR rootServerPath[ COUNT_OF( LdapHeader ) + DNS_MAX_NAME_BUFFER_LENGTH + COUNT_OF( rootDN ) ];
  1108. ADSVALUE attrValue;
  1109. ADS_ATTR_INFO attrInfo;
  1110. PSECURITY_DESCRIPTOR schemaSD = NULL;
  1111. //
  1112. // skip over '\\' if it is in front of the DC name
  1113. //
  1114. if ( *DCName == L'\\' && *(DCName+1) == L'\\' ) {
  1115. DCName += 2;
  1116. }
  1117. //
  1118. // Get rootDSE and the schema container's DN. Bind to current user's
  1119. // domain using current user's security context.
  1120. //
  1121. rootServerPath[ COUNT_OF( rootServerPath ) - 1 ] = UNICODE_NULL;
  1122. _snwprintf( rootServerPath,
  1123. COUNT_OF( rootServerPath ) - 1,
  1124. L"%ws%ws%ws",
  1125. LdapHeader,
  1126. DCName,
  1127. rootDN);
  1128. hr = ADsOpenObject(L"LDAP://rootDSE",
  1129. NULL,
  1130. NULL,
  1131. ADS_SECURE_AUTHENTICATION | ADS_SERVER_BIND,
  1132. IID_IADs,
  1133. (void**)&rootDSE);
  1134. if ( FAILED( hr )) {
  1135. goto cleanup;
  1136. }
  1137. //
  1138. // get the DN to the schema
  1139. //
  1140. hr = rootDSE->Get( L"schemaNamingContext", &schemaNamingCtxt );
  1141. if ( FAILED( hr )) {
  1142. goto cleanup;
  1143. }
  1144. //
  1145. // build the path to the computer class schema object and get an ADs
  1146. // pointer to it. Subtract one since COUNT_OF includes the NULL and we
  1147. // only need to do that once. The path is of the form:
  1148. //
  1149. // LDAP://DCName/cn=computer,<path to computer classSchema object in schema>
  1150. //
  1151. pathChars = COUNT_OF( LdapHeader ) +
  1152. wcslen( DCName ) +
  1153. COUNT_OF( computerCN ) +
  1154. wcslen( schemaNamingCtxt.bstrVal ) -
  1155. 1;
  1156. computerSchemaPath = (LPWSTR)LocalAlloc( 0, pathChars * sizeof( WCHAR ));
  1157. if ( computerSchemaPath == NULL ) {
  1158. hr = HRESULT_FROM_WIN32( GetLastError());
  1159. goto cleanup;
  1160. }
  1161. computerSchemaPath[ pathChars - 1 ] = UNICODE_NULL;
  1162. _snwprintf(computerSchemaPath,
  1163. pathChars - 1,
  1164. L"%ws%ws%ws%ws",
  1165. LdapHeader,
  1166. DCName,
  1167. computerCN,
  1168. schemaNamingCtxt.bstrVal );
  1169. hr = ADsOpenObject(computerSchemaPath,
  1170. NULL,
  1171. NULL,
  1172. ADS_SECURE_AUTHENTICATION | ADS_SERVER_BIND,
  1173. IID_IADs,
  1174. (void**)&computerSchema);
  1175. LocalFree( computerSchemaPath );
  1176. if ( FAILED( hr )) {
  1177. goto cleanup;
  1178. }
  1179. //
  1180. // now read the default security descriptor and convert it from string
  1181. // notation to a self-relative SD
  1182. //
  1183. hr = computerSchema->Get( L"defaultSecurityDescriptor", &computerSchemaSDDL );
  1184. if ( FAILED( hr )) {
  1185. goto cleanup;
  1186. }
  1187. if ( computerSchemaSDDL.vt == VT_BSTR ) {
  1188. if ( !ConvertStringSecurityDescriptorToSecurityDescriptor(computerSchemaSDDL.bstrVal,
  1189. SDDL_REVISION_1,
  1190. &schemaSD,
  1191. NULL)) {
  1192. hr = HRESULT_FROM_WIN32( GetLastError());
  1193. }
  1194. } else {
  1195. hr = E_INVALIDARG;
  1196. }
  1197. if ( FAILED( hr )) {
  1198. goto cleanup;
  1199. }
  1200. //
  1201. // finally, set the schema SD on our object.
  1202. //
  1203. attrValue.dwType = ADSTYPE_NT_SECURITY_DESCRIPTOR;
  1204. attrValue.SecurityDescriptor.dwLength = GetSecurityDescriptorLength( schemaSD );
  1205. attrValue.SecurityDescriptor.lpValue = (LPBYTE)schemaSD;
  1206. attrInfo.pszAttrName = L"nTSecurityDescriptor";
  1207. attrInfo.dwControlCode = ADS_ATTR_UPDATE;
  1208. attrInfo.dwADsType = ADSTYPE_NT_SECURITY_DESCRIPTOR;
  1209. attrInfo.pADsValues = &attrValue;
  1210. attrInfo.dwNumValues = 1;
  1211. hr = ComputerObject->SetObjectAttributes( &attrInfo, 1, &numberModified );
  1212. if ( SUCCEEDED( hr ) && numberModified != 1 ) {
  1213. hr = E_ADS_PROPERTY_NOT_SET;
  1214. }
  1215. cleanup:
  1216. VariantClear( &computerSchemaSDDL );
  1217. if ( computerSchema ) {
  1218. computerSchema->Release();
  1219. }
  1220. VariantClear( &schemaNamingCtxt );
  1221. if ( rootDSE ) {
  1222. rootDSE->Release();
  1223. }
  1224. return hr;
  1225. } // ResetComputerObjectSecurityDesc
  1226. #endif // BUG601100
  1227. DWORD
  1228. NNGetAuthenticatingDCName(
  1229. LPWSTR * AuthDCName
  1230. )
  1231. /*++
  1232. Routine Description:
  1233. get the DC that netname uses to authenticate with when generating the VS
  1234. token. Currently, we use NTLM so this is the other end of netlogon's
  1235. trusted connection
  1236. Arguments:
  1237. AuthDCName - address of pointer that receives the buffer containing
  1238. the name (that Jack built)
  1239. Return Value:
  1240. ERROR_SUCCESS
  1241. --*/
  1242. {
  1243. WCHAR trustedDomainName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  1244. PWCHAR inputParam = trustedDomainName;
  1245. DWORD nameBufferSize = COUNT_OF( trustedDomainName );
  1246. DWORD status = ERROR_SUCCESS;
  1247. BOOL success;
  1248. NET_API_STATUS netStatus;
  1249. PNETLOGON_INFO_2 nlInfo2;
  1250. //
  1251. // get our domain name
  1252. //
  1253. success = GetComputerNameEx(ComputerNameDnsDomain,
  1254. trustedDomainName,
  1255. &nameBufferSize);
  1256. if ( success ) {
  1257. netStatus = I_NetLogonControl2(NULL, // this node
  1258. NETLOGON_CONTROL_TC_QUERY,
  1259. 2,
  1260. (LPBYTE)&inputParam,
  1261. (LPBYTE *)&nlInfo2 );
  1262. if ( netStatus == NERR_Success ) {
  1263. *AuthDCName = ResUtilDupString( nlInfo2->netlog2_trusted_dc_name );
  1264. NetApiBufferFree( nlInfo2 );
  1265. } else {
  1266. status = netStatus;
  1267. }
  1268. } else {
  1269. status = GetLastError();
  1270. }
  1271. return status;
  1272. } // NNGetAuthenticatingDCName
  1273. //
  1274. // exported routines
  1275. //
  1276. DWORD
  1277. AddComputerObject(
  1278. IN PCLUS_WORKER Worker,
  1279. IN PNETNAME_RESOURCE Resource,
  1280. OUT PWCHAR * MachinePwd
  1281. )
  1282. /*++
  1283. Routine Description:
  1284. Create a computer object in the DS that is used primarily for kerb
  1285. authentication.
  1286. Arguments:
  1287. Worker - cluster worker thread so we can abort early if asked to do so
  1288. Resource - pointer to netname resource context block
  1289. MachinePwd - address of pointer to receive pointer to machine account PWD
  1290. Return Value:
  1291. ERROR_SUCCESS, otherwise appropriate Win32 error
  1292. --*/
  1293. {
  1294. DWORD status;
  1295. PWSTR virtualName = Resource->Params.NetworkName;
  1296. DWORD virtualNameSize = wcslen( virtualName );
  1297. PWSTR virtualFQDN = NULL;
  1298. HANDLE dsHandle = NULL;
  1299. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  1300. PWCHAR machinePwd = NULL;
  1301. DWORD pwdBufferByteLength = ((LM20_PWLEN + 1) * sizeof( WCHAR ));
  1302. DWORD pwdBufferCharLength = LM20_PWLEN + 1;
  1303. DWORD paramInError = 0;
  1304. BOOL deleteObjectOnFailure = FALSE; // only delete the CO if we create it
  1305. HRESULT hr = S_OK;
  1306. BOOL objectExists = FALSE;
  1307. DWORD addtlErrTextID;
  1308. PWCHAR dcName = NULL;
  1309. DWORD dsFlags = DS_PDC_REQUIRED | DS_WRITABLE_REQUIRED;
  1310. USER_INFO_1 netUI1;
  1311. PUSER_INFO_20 netUI20 = NULL;
  1312. USER_INFO_1003 netUI1003;
  1313. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  1314. IDirectoryObject * compObj = NULL;
  1315. PDOMAIN_CONTROLLER_INFO dcInfo = NULL;
  1316. //
  1317. // Time to choose a DC. The order of preference is: PDC, authenticating
  1318. // DC, any DC. This strategy centers around how computer/machine account
  1319. // passwords are handled with the goal being to increase the chances that
  1320. // genearting a token for the virtual computer account will succeed.
  1321. //
  1322. // Just like user accounts, if a logon attempt fails at the authenticating
  1323. // DC, the logon is forwarded to the PDC. If it has the correct password,
  1324. // the logon succeeds. Unlike user accounts, password changes for machine
  1325. // accounts are not automatically forwarded to the PDC. Hence we favor the
  1326. // PDC when selecting the DC on which to either create or update the
  1327. // computer object.
  1328. //
  1329. // This strategy breaks down when the computer object doesn't exist in the
  1330. // domain or more specifically, the authenticating DC. That DC will not
  1331. // forward the logon request if it can't find the object in its SAM. For
  1332. // that reason, getting a token for the virtual computer account is not
  1333. // considered a failure during online. If one is requested later on, the
  1334. // logon attempt will be retried. Replication of the object and discovery
  1335. // by netlogon takes about 15 seconds so this shouldn't be that big of a
  1336. // problem.
  1337. //
  1338. (NetNameLogEvent)(resourceHandle,
  1339. LOG_INFORMATION,
  1340. L"Searching the PDC for existing computer account %1!ws!.\n",
  1341. virtualName);
  1342. while ( TRUE ) {
  1343. LPWSTR hostingDCName = NULL;
  1344. LPWSTR searchDCName;
  1345. //
  1346. // if we've tried the PDC and the auth DC, set our search routine to
  1347. // search the domain and not a specific DC
  1348. //
  1349. if (( dsFlags & DS_PDC_REQUIRED ) == 0 && dcName == NULL ) {
  1350. searchDCName = NULL;
  1351. status = ERROR_SUCCESS;
  1352. } else {
  1353. //
  1354. // we have a specific DC in mind; look up its info
  1355. //
  1356. status = FindDomainForServer(resourceHandle,
  1357. Resource->NodeName,
  1358. dcName,
  1359. dsFlags,
  1360. &dcInfo );
  1361. if ( status == ERROR_SUCCESS ) {
  1362. searchDCName = dcInfo->DomainControllerName;
  1363. }
  1364. }
  1365. if ( ClusWorkerCheckTerminate( Worker )) {
  1366. status = ERROR_OPERATION_ABORTED;
  1367. goto cleanup;
  1368. }
  1369. if ( status == ERROR_SUCCESS ) {
  1370. hr = IsComputerObjectInDS(resourceHandle,
  1371. Resource->NodeName,
  1372. virtualName,
  1373. searchDCName,
  1374. &objectExists,
  1375. &virtualFQDN,
  1376. &hostingDCName);
  1377. if ( FAILED( hr )) {
  1378. (NetNameLogEvent)(resourceHandle,
  1379. LOG_WARNING,
  1380. L"Search for existing computer account failed. status %1!08X!\n",
  1381. hr);
  1382. }
  1383. } // if DC lookup was successful
  1384. else {
  1385. if ( dsFlags & DS_PDC_REQUIRED ) {
  1386. (NetNameLogEvent)(resourceHandle,
  1387. LOG_WARNING,
  1388. L"Couldn't get information about PDC. status %1!u!\n",
  1389. status);
  1390. }
  1391. else if ( dcName != NULL ) {
  1392. (NetNameLogEvent)(resourceHandle,
  1393. LOG_WARNING,
  1394. L"Couldn't get information about authenticating DC %1!ws!. status %2!u!\n",
  1395. dcName,
  1396. status);
  1397. }
  1398. else {
  1399. (NetNameLogEvent)(resourceHandle,
  1400. LOG_ERROR,
  1401. L"Couldn't get information about any DC in the domain. status %1!u!\n",
  1402. status);
  1403. }
  1404. }
  1405. if ( ClusWorkerCheckTerminate( Worker )) {
  1406. status = ERROR_OPERATION_ABORTED;
  1407. if ( hostingDCName ) {
  1408. LocalFree( hostingDCName );
  1409. }
  1410. goto cleanup;
  1411. }
  1412. if ( SUCCEEDED( hr ) && objectExists ) {
  1413. //
  1414. // object is on a DC; if this was a general domain wide search,
  1415. // then get this DC's information
  1416. //
  1417. if ( searchDCName == NULL ) {
  1418. status = FindDomainForServer(resourceHandle,
  1419. Resource->NodeName,
  1420. hostingDCName,
  1421. dsFlags,
  1422. &dcInfo );
  1423. }
  1424. LocalFree( hostingDCName );
  1425. break;
  1426. }
  1427. if ( dsFlags & DS_PDC_REQUIRED ) {
  1428. //
  1429. // not on PDC; look up authenticating DC and try that. If we
  1430. // can't get the auth DC, then just search the domain
  1431. //
  1432. (NetNameLogEvent)(resourceHandle,
  1433. LOG_INFORMATION,
  1434. L"Computer account %1!ws! not found on PDC.\n",
  1435. virtualName);
  1436. dsFlags &= ~DS_PDC_REQUIRED;
  1437. if ( dcInfo != NULL ) {
  1438. NetApiBufferFree( dcInfo );
  1439. dcInfo = NULL;
  1440. }
  1441. status = NNGetAuthenticatingDCName( &dcName );
  1442. if ( status == ERROR_SUCCESS ) {
  1443. (NetNameLogEvent)(resourceHandle,
  1444. LOG_INFORMATION,
  1445. L"Searching %1!ws! for existing computer account %2!ws!.\n",
  1446. dcName,
  1447. virtualName);
  1448. } else {
  1449. //
  1450. // couldn't get auth DC name; skip to looking in the whole domain
  1451. //
  1452. dcName = NULL;
  1453. (NetNameLogEvent)(resourceHandle,
  1454. LOG_WARNING,
  1455. L"Couldn't get name of authenticating DC (status %1!u!). "
  1456. L"Performing domain wide search.\n",
  1457. status);
  1458. }
  1459. }
  1460. else if ( dcName != NULL ) {
  1461. //
  1462. // not on the auth DC; try just the domain
  1463. //
  1464. (NetNameLogEvent)(resourceHandle,
  1465. LOG_INFORMATION,
  1466. L"Computer account %1!ws! not found on DC %2!ws!. Performing "
  1467. L"domain wide search.\n",
  1468. virtualName,
  1469. dcName);
  1470. if ( dcInfo != NULL ) {
  1471. NetApiBufferFree( dcInfo );
  1472. dcInfo = NULL;
  1473. }
  1474. LocalFree( dcName );
  1475. dcName = NULL;
  1476. }
  1477. else {
  1478. (NetNameLogEvent)(resourceHandle,
  1479. LOG_INFORMATION,
  1480. L"Computer account %1!ws! does not exist in this domain.\n",
  1481. virtualName);
  1482. break;
  1483. }
  1484. } // end of while TRUE
  1485. if ( ClusWorkerCheckTerminate( Worker )) {
  1486. status = ERROR_OPERATION_ABORTED;
  1487. goto cleanup;
  1488. }
  1489. if ( !objectExists ) {
  1490. //
  1491. // couldn't find object anywhere so we need to pick a DC on which to
  1492. // create the object. Our preference is the PDC, then the auth DC,
  1493. // then any DC.
  1494. //
  1495. status = FindDomainForServer(resourceHandle,
  1496. Resource->NodeName,
  1497. NULL,
  1498. DS_PDC_REQUIRED | DS_WRITABLE_REQUIRED,
  1499. &dcInfo );
  1500. if ( status != ERROR_SUCCESS ) {
  1501. (NetNameLogEvent)(resourceHandle,
  1502. LOG_WARNING,
  1503. L"Failed to find the PDC in this domain. status %1!u!\n",
  1504. status );
  1505. status = NNGetAuthenticatingDCName( &dcName );
  1506. if ( status == ERROR_SUCCESS ) {
  1507. //
  1508. // got the auth DC name; get its info
  1509. //
  1510. status = FindDomainForServer(resourceHandle,
  1511. Resource->NodeName,
  1512. dcName,
  1513. DS_WRITABLE_REQUIRED,
  1514. &dcInfo );
  1515. LocalFree( dcName );
  1516. dcName = NULL;
  1517. } // else couldn't get auth DC name
  1518. if ( status != ERROR_SUCCESS ) {
  1519. //
  1520. // find any DC out there
  1521. //
  1522. status = FindDomainForServer(resourceHandle,
  1523. Resource->NodeName,
  1524. NULL,
  1525. DS_WRITABLE_REQUIRED,
  1526. &dcInfo );
  1527. } // else was able to get auth DC info
  1528. } // else PDC info was available
  1529. } // else an existing object was found
  1530. if ( status != ERROR_SUCCESS ) {
  1531. LPWSTR msgBuff;
  1532. DWORD msgBytes;
  1533. (NetNameLogEvent)(resourceHandle,
  1534. LOG_ERROR,
  1535. L"Failed to find a suitable DC in this domain. status %1!u!\n",
  1536. status );
  1537. msgBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  1538. FORMAT_MESSAGE_FROM_SYSTEM,
  1539. NULL,
  1540. status,
  1541. 0,
  1542. (LPWSTR)&msgBuff,
  1543. 0,
  1544. NULL);
  1545. if ( msgBytes > 0 ) {
  1546. ClusResLogSystemEventByKey1(Resource->ResKey,
  1547. LOG_CRITICAL,
  1548. RES_NETNAME_NO_WRITEABLE_DC,
  1549. msgBuff);
  1550. LocalFree( msgBuff );
  1551. } else {
  1552. ClusResLogSystemEventByKeyData(Resource->ResKey,
  1553. LOG_CRITICAL,
  1554. RES_NETNAME_NO_WRITEABLE_DC_STATUS,
  1555. sizeof( status ),
  1556. &status);
  1557. }
  1558. goto cleanup;
  1559. } // else we found a suitable DC
  1560. //
  1561. // bind to the DS on this DC
  1562. //
  1563. status = DsBindW( dcInfo->DomainControllerName, NULL, &dsHandle );
  1564. if ( status != NO_ERROR ) {
  1565. (NetNameLogEvent)(resourceHandle,
  1566. LOG_ERROR,
  1567. L"Failed to bind to DC %1!ws!, status %2!u!\n",
  1568. dcInfo->DomainControllerName,
  1569. status );
  1570. addtlErrTextID = RES_NETNAME_DC_BIND_FAILED;
  1571. goto cleanup;
  1572. }
  1573. if ( ClusWorkerCheckTerminate( Worker )) {
  1574. status = ERROR_OPERATION_ABORTED;
  1575. goto cleanup;
  1576. }
  1577. (NetNameLogEvent)(resourceHandle,
  1578. LOG_INFORMATION,
  1579. L"Using domain controller %1!ws!.\n",
  1580. dcInfo->DomainControllerName);
  1581. //
  1582. // initialize the user's pwd buffer and get a buffer to hold the machine
  1583. // Pwd
  1584. //
  1585. *MachinePwd = NULL;
  1586. machinePwd = (PWCHAR)LocalAlloc( LMEM_FIXED, pwdBufferByteLength );
  1587. if ( machinePwd == NULL ) {
  1588. status = ERROR_NOT_ENOUGH_MEMORY;
  1589. (NetNameLogEvent)(resourceHandle,
  1590. LOG_ERROR,
  1591. L"Unable to allocate memory for resource data. status %1!u!.\n",
  1592. status);
  1593. addtlErrTextID = RES_NETNAME_RESDATA_ALLOC_FAILED;
  1594. goto cleanup;
  1595. }
  1596. //
  1597. // generate a random stream of bytes for the password
  1598. //
  1599. status = GenerateRandomBytes( machinePwd, pwdBufferCharLength );
  1600. if ( status != ERROR_SUCCESS ) {
  1601. (NetNameLogEvent)(resourceHandle,
  1602. LOG_ERROR,
  1603. L"Unable to generate password. status %1!u!.\n",
  1604. status);
  1605. addtlErrTextID = RES_NETNAME_PASSWORD_UPDATE_FAILED;
  1606. goto cleanup;
  1607. }
  1608. //
  1609. // set the ACL on the parameters key to contain just the cluster
  1610. // service account since we're about to store sensitive info in there.
  1611. //
  1612. status = SetACLOnParametersKey( Resource->ParametersKey );
  1613. if ( status != ERROR_SUCCESS ) {
  1614. (NetNameLogEvent)(resourceHandle,
  1615. LOG_ERROR,
  1616. L"Unable to set permissions on resource's parameters key. status %1!u!\n",
  1617. status );
  1618. addtlErrTextID = RES_NETNAME_PARAMS_KEY_PERMISSION_UPDATE_FAILED;
  1619. goto cleanup;
  1620. }
  1621. //
  1622. // take our new password, encrypt it and store it in the cluster
  1623. // registry
  1624. //
  1625. if ( Resource->Params.ResourceData != NULL ) {
  1626. LocalFree( Resource->Params.ResourceData );
  1627. Resource->Params.ResourceData = NULL;
  1628. }
  1629. status = EncryptNNResourceData(Resource,
  1630. machinePwd,
  1631. &Resource->Params.ResourceData,
  1632. &Resource->ResDataSize);
  1633. if ( status != ERROR_SUCCESS ) {
  1634. (NetNameLogEvent)(resourceHandle,
  1635. LOG_ERROR,
  1636. L"Unable to store resource data. status %1!u!\n",
  1637. status );
  1638. addtlErrTextID = RES_NETNAME_RESDATA_STORE_FAILED;
  1639. goto cleanup;
  1640. }
  1641. #ifdef PASSWORD_ROTATION
  1642. //
  1643. // record when it is time to rotate the pwd; per MSDN, convert to
  1644. // ULARGE Int and add in the update interval.
  1645. //
  1646. GetSystemTimeAsFileTime( &Resource->Params.NextUpdate );
  1647. updateTime.LowPart = Resource->Params.NextUpdate.dwLowDateTime;
  1648. updateTime.HighPart = Resource->Params.NextUpdate.dwHighDateTime;
  1649. updateTime.QuadPart += ( Resource->Params.UpdateInterval * 60 * 1000 * 100 );
  1650. Resource->Params.NextUpdate.dwLowDateTime = updateTime.LowPart;
  1651. Resource->Params.NextUpdate.dwHighDateTime = updateTime.HighPart;
  1652. setValueStatus = ResUtilSetBinaryValue(Resource->ParametersKey,
  1653. PARAM_NAME__NEXT_UPDATE,
  1654. (const LPBYTE)&updateTime,
  1655. sizeof( updateTime ),
  1656. NULL,
  1657. NULL);
  1658. if ( setValueStatus != ERROR_SUCCESS ) {
  1659. status = setValueStatus;
  1660. goto cleanup;
  1661. }
  1662. #endif
  1663. //
  1664. // identify this as a machine account by adding a $ to the end of the name.
  1665. //
  1666. virtualDollarName[ COUNT_OF( virtualDollarName ) - 1 ] = UNICODE_NULL;
  1667. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", virtualName );
  1668. if ( objectExists ) {
  1669. //
  1670. // found an object; see if it is enabled and whether we can hijack it.
  1671. //
  1672. (NetNameLogEvent)(resourceHandle,
  1673. LOG_INFORMATION,
  1674. L"Found existing computer account %1!ws! on DC %2!ws!.\n",
  1675. virtualName,
  1676. dcInfo->DomainControllerName);
  1677. //
  1678. // check if the account is disabled
  1679. //
  1680. status = NetUserGetInfo(dcInfo->DomainControllerName,
  1681. virtualDollarName,
  1682. 20,
  1683. (LPBYTE *)&netUI20);
  1684. if ( ClusWorkerCheckTerminate( Worker )) {
  1685. status = ERROR_OPERATION_ABORTED;
  1686. goto cleanup;
  1687. }
  1688. if ( status == NERR_Success ) {
  1689. if ( netUI20->usri20_flags & UF_ACCOUNTDISABLE ) {
  1690. USER_INFO_1008 netUI1008;
  1691. //
  1692. // try to re-enable; fail if we can't
  1693. //
  1694. netUI1008.usri1008_flags = netUI20->usri20_flags & ~UF_ACCOUNTDISABLE;
  1695. status = NetUserSetInfo(dcInfo->DomainControllerName,
  1696. virtualDollarName,
  1697. 1008,
  1698. (LPBYTE)&netUI1008,
  1699. &paramInError);
  1700. if ( status != NERR_Success ) {
  1701. (NetNameLogEvent)(resourceHandle,
  1702. LOG_ERROR,
  1703. L"Computer account %1!ws! is disabled and couldn't be re-enabled. "
  1704. L"status %2!u!\n",
  1705. virtualName,
  1706. status);
  1707. addtlErrTextID = RES_NETNAME_CO_CANT_BE_REENABLED;
  1708. }
  1709. }
  1710. NetApiBufferFree( netUI20 );
  1711. netUI20 = NULL;
  1712. if ( status != NERR_Success ) {
  1713. goto cleanup;
  1714. }
  1715. } else {
  1716. (NetNameLogEvent)(resourceHandle,
  1717. LOG_WARNING,
  1718. L"Couldn't determine if computer account %1!ws! "
  1719. L"is disabled. status %2!u!\n",
  1720. virtualName,
  1721. status);
  1722. }
  1723. //
  1724. // set the password on the computer object
  1725. //
  1726. netUI1003.usri1003_password = (PWCHAR)machinePwd;
  1727. status = NetUserSetInfo(dcInfo->DomainControllerName,
  1728. virtualDollarName,
  1729. 1003,
  1730. (PBYTE)&netUI1003,
  1731. &paramInError );
  1732. if ( ClusWorkerCheckTerminate( Worker )) {
  1733. status = ERROR_OPERATION_ABORTED;
  1734. goto cleanup;
  1735. }
  1736. if ( status != NERR_Success ) {
  1737. (NetNameLogEvent)(resourceHandle,
  1738. LOG_ERROR,
  1739. L"Unable to update password for computer account "
  1740. L"%1!ws! on DC %2!ws!, status %3!u!.\n",
  1741. virtualName,
  1742. dcInfo->DomainControllerName,
  1743. status);
  1744. addtlErrTextID = RES_NETNAME_CO_PASSWORD_UPDATE_FAILED;
  1745. goto cleanup;
  1746. }
  1747. }
  1748. else {
  1749. //
  1750. // the computer account doesn't exist in the DS. Create it.
  1751. //
  1752. (NetNameLogEvent)(resourceHandle,
  1753. LOG_INFORMATION,
  1754. L"Failed to find a computer account for "
  1755. L"%1!ws! on DC %2!ws!. Attempting to create one.\n",
  1756. virtualName,
  1757. dcInfo->DomainControllerName);
  1758. //
  1759. // create the computer object (machine account).
  1760. //
  1761. RtlZeroMemory( &netUI1, sizeof( netUI1 ) );
  1762. netUI1.usri1_password = (PWCHAR)machinePwd;
  1763. netUI1.usri1_priv = USER_PRIV_USER;
  1764. netUI1.usri1_name = virtualDollarName;
  1765. netUI1.usri1_flags = UF_WORKSTATION_TRUST_ACCOUNT | UF_SCRIPT;
  1766. netUI1.usri1_comment = NetNameCompObjAccountDesc;
  1767. status = NetUserAdd( dcInfo->DomainControllerName, 1, (PBYTE)&netUI1, &paramInError );
  1768. if ( status == NERR_Success ) {
  1769. (NetNameLogEvent)(resourceHandle,
  1770. LOG_INFORMATION,
  1771. L"Created computer account %1!ws! on DC %2!ws!.\n",
  1772. virtualName,
  1773. dcInfo->DomainControllerName);
  1774. deleteObjectOnFailure = TRUE;
  1775. } // if NetUserAdd was successful
  1776. else {
  1777. (NetNameLogEvent)(resourceHandle,
  1778. LOG_ERROR,
  1779. L"Unable to create computer account %1!ws! on DC %2!ws!, "
  1780. L"status %3!u! (paramInError: %4!u!)\n",
  1781. virtualName,
  1782. dcInfo->DomainControllerName,
  1783. status,
  1784. paramInError);
  1785. addtlErrTextID = RES_NETNAME_CREATE_CO_FAILED;
  1786. goto cleanup;
  1787. }
  1788. //
  1789. // get the FQDN for setting attributes
  1790. //
  1791. hr = IsComputerObjectInDS( resourceHandle,
  1792. Resource->NodeName,
  1793. virtualName,
  1794. dcInfo->DomainControllerName,
  1795. &objectExists,
  1796. &virtualFQDN,
  1797. NULL);
  1798. if ( FAILED( hr ) || !objectExists ) {
  1799. (NetNameLogEvent)(resourceHandle,
  1800. LOG_ERROR,
  1801. L"Unable to get LDAP distinguished name for computer account %1!ws! "
  1802. L"on DC %2!ws!, status %3!08X!.\n",
  1803. virtualName,
  1804. dcInfo->DomainControllerName,
  1805. hr);
  1806. addtlErrTextID = RES_NETNAME_GET_LDAP_NAME_FAILED;
  1807. status = hr;
  1808. goto cleanup;
  1809. }
  1810. }
  1811. //
  1812. // use the Object GUID for binding during CheckComputerObjectAttributes so
  1813. // we don't have to track changes to the DN. If the object is moved in the
  1814. // DS, its DN will change but not its GUID. We use this code instead of
  1815. // GetComputerObjectGuid because we need to target a specific DC.
  1816. //
  1817. {
  1818. IADs * pADs = NULL;
  1819. hr = GetComputerObjectViaFQDN( virtualFQDN, dcInfo->DomainControllerName, &compObj );
  1820. if ( SUCCEEDED( hr )) {
  1821. hr = compObj->QueryInterface(IID_IADs, (void**) &pADs);
  1822. if ( SUCCEEDED( hr )) {
  1823. BSTR guidStr = NULL;
  1824. hr = pADs->get_GUID( &guidStr );
  1825. if ( SUCCEEDED( hr )) {
  1826. if ( Resource->ObjectGUID != NULL ) {
  1827. LocalFree( Resource->ObjectGUID );
  1828. Resource->ObjectGUID = NULL;
  1829. }
  1830. Resource->ObjectGUID = ResUtilDupString( guidStr );
  1831. if ( Resource->ObjectGUID == NULL ) {
  1832. hr = HRESULT_FROM_WIN32( GetLastError());
  1833. }
  1834. }
  1835. else {
  1836. (NetNameLogEvent)(resourceHandle,
  1837. LOG_ERROR,
  1838. L"Failed to get object GUID for computer account %1!ws!, "
  1839. L"status %2!08X!\n",
  1840. virtualName,
  1841. hr );
  1842. }
  1843. if ( guidStr ) {
  1844. SysFreeString( guidStr );
  1845. }
  1846. }
  1847. if ( pADs != NULL ) {
  1848. pADs->Release();
  1849. }
  1850. if ( FAILED( hr )) {
  1851. addtlErrTextID = RES_NETNAME_GET_CO_GUID_FAILED;
  1852. status = hr;
  1853. goto cleanup;
  1854. }
  1855. }
  1856. else {
  1857. (NetNameLogEvent)(resourceHandle,
  1858. LOG_ERROR,
  1859. L"Failed to obtain access to computer account %1!ws!, status %2!08X!\n",
  1860. virtualName,
  1861. hr );
  1862. status = hr;
  1863. addtlErrTextID = RES_NETNAME_GET_CO_POINTER_FAILED;
  1864. goto cleanup;
  1865. }
  1866. }
  1867. //
  1868. // add the DnsHostName and ServicePrincipalName attributes
  1869. //
  1870. hr = AddDnsHostNameAttribute( resourceHandle, compObj, virtualName, FALSE );
  1871. if ( FAILED( hr )) {
  1872. status = hr;
  1873. (NetNameLogEvent)(resourceHandle,
  1874. LOG_ERROR,
  1875. L"Unable to set DnsHostName attribute for computer account "
  1876. L"%1!ws!, status %2!08X!.\n",
  1877. virtualName,
  1878. hr);
  1879. addtlErrTextID = RES_NETNAME_DNSHOSTNAME_UPDATE_FAILED;
  1880. goto cleanup;
  1881. }
  1882. if ( ClusWorkerCheckTerminate( Worker )) {
  1883. status = ERROR_OPERATION_ABORTED;
  1884. goto cleanup;
  1885. }
  1886. status = AddServicePrincipalNames( dsHandle,
  1887. virtualFQDN,
  1888. virtualName,
  1889. dcInfo->DomainName,
  1890. Resource->dwFlags & CLUS_FLAG_CORE );
  1891. if ( status != ERROR_SUCCESS ) {
  1892. (NetNameLogEvent)(resourceHandle,
  1893. LOG_ERROR,
  1894. L"Unable to set ServicePrincipalName attribute for computer account "
  1895. L"%1!ws!, status %2!u!.\n",
  1896. virtualName,
  1897. status);
  1898. addtlErrTextID = RES_NETNAME_SPN_UPDATE_FAILED;
  1899. goto cleanup;
  1900. }
  1901. //
  1902. // store the name of our creating DC in that property and make a copy for
  1903. // the online parameters block
  1904. //
  1905. if ( Resource->Params.CreatingDC != NULL ) {
  1906. //
  1907. // this shouldn't be the case, but it will avoid mem leaks
  1908. //
  1909. LocalFree( Resource->Params.CreatingDC );
  1910. }
  1911. Resource->Params.CreatingDC = ResUtilDupString( dcInfo->DomainControllerName );
  1912. if ( Resource->Params.CreatingDC == NULL ) {
  1913. status = ERROR_NOT_ENOUGH_MEMORY;
  1914. (NetNameLogEvent)(resourceHandle,
  1915. LOG_ERROR,
  1916. L"Unable to allocate memory for CreatingDC property, status %1!u!.\n",
  1917. status);
  1918. addtlErrTextID = RES_NETNAME_CREATINDC_ALLOC_FAILED;
  1919. goto cleanup;
  1920. }
  1921. status = ResUtilSetSzValue(Resource->ParametersKey,
  1922. PARAM_NAME__CREATING_DC,
  1923. Resource->Params.CreatingDC,
  1924. NULL);
  1925. if ( status != ERROR_SUCCESS ) {
  1926. (NetNameLogEvent)(resourceHandle,
  1927. LOG_ERROR,
  1928. L"Unable to set CreatingDC property, status %1!u!.\n",
  1929. status);
  1930. addtlErrTextID = RES_NETNAME_CREATINGDC_UPDATE_FAILED;
  1931. goto cleanup;
  1932. }
  1933. cleanup:
  1934. //
  1935. // always free these
  1936. //
  1937. if ( dsHandle != NULL ) {
  1938. DsUnBind( &dsHandle );
  1939. }
  1940. if ( compObj != NULL ) {
  1941. compObj->Release();
  1942. }
  1943. if ( virtualFQDN != NULL ) {
  1944. LocalFree( virtualFQDN );
  1945. }
  1946. if ( dcName != NULL ) {
  1947. LocalFree( dcName );
  1948. }
  1949. if ( netUI20 != NULL ) {
  1950. NetApiBufferFree( netUI20 );
  1951. }
  1952. if ( status == ERROR_SUCCESS ) {
  1953. *MachinePwd = machinePwd;
  1954. } else {
  1955. if ( status != ERROR_OPERATION_ABORTED ) {
  1956. LPWSTR msgBuff;
  1957. DWORD msgBytes;
  1958. LPWSTR addtlErrText;
  1959. LPWSTR domainName;
  1960. WCHAR domainNameBuffer[ DNS_MAX_NAME_BUFFER_LENGTH ];
  1961. DWORD domainNameChars = COUNT_OF( domainNameBuffer );
  1962. BOOL success;
  1963. msgBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  1964. FORMAT_MESSAGE_FROM_SYSTEM,
  1965. NULL,
  1966. status,
  1967. 0,
  1968. (LPWSTR)&msgBuff,
  1969. 0,
  1970. NULL);
  1971. //
  1972. // get the additional error text to go with the generic failed message
  1973. //
  1974. addtlErrText = ClusResLoadMessage( addtlErrTextID );
  1975. success = GetComputerNameEx(ComputerNameDnsDomain,
  1976. domainNameBuffer,
  1977. &domainNameChars);
  1978. if ( success ) {
  1979. domainName = domainNameBuffer;
  1980. }
  1981. else {
  1982. domainName = NULL;
  1983. }
  1984. if ( msgBytes > 0 ) {
  1985. ClusResLogSystemEventByKey3(Resource->ResKey,
  1986. LOG_CRITICAL,
  1987. RES_NETNAME_ADD_COMPUTER_ACCOUNT_FAILED,
  1988. domainName,
  1989. addtlErrText,
  1990. msgBuff);
  1991. LocalFree( msgBuff );
  1992. } else {
  1993. ClusResLogSystemEventByKeyData2(Resource->ResKey,
  1994. LOG_CRITICAL,
  1995. RES_NETNAME_ADD_COMPUTER_ACCOUNT_FAILED_STATUS,
  1996. sizeof( status ),
  1997. &status,
  1998. domainName,
  1999. addtlErrText);
  2000. }
  2001. if ( addtlErrText ) {
  2002. LocalFree( addtlErrText );
  2003. }
  2004. }
  2005. if ( machinePwd != NULL ) {
  2006. RtlSecureZeroMemory( machinePwd, pwdBufferByteLength );
  2007. LocalFree( machinePwd );
  2008. }
  2009. if ( Resource->Params.ResourceData ) {
  2010. DWORD deleteStatus;
  2011. RemoveNNCryptoCheckpoint( Resource );
  2012. deleteStatus = ClusterRegDeleteValue(Resource->ParametersKey,
  2013. PARAM_NAME__RESOURCE_DATA);
  2014. if ( deleteStatus != ERROR_SUCCESS && deleteStatus != ERROR_FILE_NOT_FOUND ) {
  2015. (NetNameLogEvent)(Resource->ResourceHandle,
  2016. LOG_ERROR,
  2017. L"Unable to clear the "
  2018. PARAM_NAME__RESOURCE_DATA
  2019. L" property. status %1!u!\n",
  2020. deleteStatus );
  2021. }
  2022. LocalFree( Resource->Params.ResourceData );
  2023. Resource->Params.ResourceData = NULL;
  2024. }
  2025. if ( Resource->Params.CreatingDC ) {
  2026. DWORD deleteStatus;
  2027. deleteStatus = ClusterRegDeleteValue(Resource->ParametersKey,
  2028. PARAM_NAME__CREATING_DC);
  2029. if ( deleteStatus != ERROR_SUCCESS && deleteStatus != ERROR_FILE_NOT_FOUND ) {
  2030. (NetNameLogEvent)(Resource->ResourceHandle,
  2031. LOG_ERROR,
  2032. L"Unable to clear the "
  2033. PARAM_NAME__CREATING_DC
  2034. L" property. status %1!u!\n",
  2035. deleteStatus );
  2036. }
  2037. LocalFree( Resource->Params.CreatingDC );
  2038. Resource->Params.CreatingDC = NULL;
  2039. }
  2040. if ( deleteObjectOnFailure ) {
  2041. //
  2042. // only delete the object if we created it. It is possible that
  2043. // the name was online at one time and the object was properly
  2044. // created allowing additional information/SPNs to be registered
  2045. // with the CO. For this reason, we shouldn't undo the work done
  2046. // by other applications.
  2047. //
  2048. DeleteComputerObject( Resource );
  2049. }
  2050. }
  2051. if ( dcInfo != NULL ) {
  2052. NetApiBufferFree( dcInfo );
  2053. }
  2054. return status;
  2055. } // AddComputerObject
  2056. DWORD
  2057. UpdateComputerObject(
  2058. IN PCLUS_WORKER Worker,
  2059. IN PNETNAME_RESOURCE Resource,
  2060. OUT PWCHAR * MachinePwd
  2061. )
  2062. /*++
  2063. Routine Description:
  2064. Check the status of our existing computer object in the DS, i.e., make
  2065. sure that nothing has happened to it.
  2066. Arguments:
  2067. Worker - cluster worker thread so we can abort early if asked to do so
  2068. Resource - pointer to netname resource context block
  2069. MachinePwd - address of pointer to receive pointer to machine account PWD
  2070. Return Value:
  2071. ERROR_SUCCESS, otherwise appropriate Win32 error
  2072. --*/
  2073. {
  2074. DWORD status;
  2075. PWSTR virtualName = Resource->Params.NetworkName;
  2076. PWSTR virtualFQDN = NULL;
  2077. HANDLE dsHandle = NULL;
  2078. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  2079. PWCHAR machinePwd = NULL;
  2080. DWORD pwdBufferByteLength = ((LM20_PWLEN + 1) * sizeof( WCHAR ));
  2081. DWORD paramInError = 0;
  2082. BOOL objectFound = FALSE;
  2083. HRESULT hr;
  2084. DWORD addtlErrTextID;
  2085. DWORD tokenStatus;
  2086. BOOL success;
  2087. PWCHAR hostingDCName = NULL;
  2088. PUSER_INFO_20 netUI20 = NULL;
  2089. USER_INFO_1003 netUI1003;
  2090. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  2091. IDirectoryObject * compObj = NULL;
  2092. PDOMAIN_CONTROLLER_INFO dcInfo = NULL;
  2093. *MachinePwd = NULL;
  2094. //
  2095. // find the object in the domain
  2096. //
  2097. hr = IsComputerObjectInDS(resourceHandle,
  2098. Resource->NodeName,
  2099. virtualName,
  2100. NULL,
  2101. &objectFound,
  2102. &virtualFQDN,
  2103. &hostingDCName);
  2104. if ( SUCCEEDED( hr )) {
  2105. if ( !objectFound ) {
  2106. //
  2107. // couldn't find object anywhere in the DS; let's specifically
  2108. // retry on the creating DC in the event it was overlooked
  2109. //
  2110. (NetNameLogEvent)(resourceHandle,
  2111. LOG_WARNING,
  2112. L"Computer account %1!ws! could not be found on the available "
  2113. L"domain controllers. Checking the DC where the object was "
  2114. L"created.\n",
  2115. virtualName);
  2116. hostingDCName = Resource->Params.CreatingDC;
  2117. hr = IsComputerObjectInDS(resourceHandle,
  2118. Resource->NodeName,
  2119. virtualName,
  2120. hostingDCName,
  2121. &objectFound,
  2122. &virtualFQDN,
  2123. NULL);
  2124. if ( SUCCEEDED( hr )) {
  2125. if ( !objectFound ) {
  2126. //
  2127. // something's amiss. Our object should be on the creating
  2128. // DC node but it's not. Fail and log a message to the
  2129. // system event log.
  2130. //
  2131. (NetNameLogEvent)(resourceHandle,
  2132. LOG_ERROR,
  2133. L"Unable to find computer account %1!ws! on DC %2!ws! where it was "
  2134. L"created.\n",
  2135. virtualName,
  2136. hostingDCName);
  2137. status = ERROR_DS_OBJ_NOT_FOUND;
  2138. addtlErrTextID = RES_NETNAME_MISSING_CO;
  2139. goto cleanup;
  2140. }
  2141. } // if the search succeeded
  2142. else {
  2143. //
  2144. // search on creating DC failed. assume it is not available.
  2145. //
  2146. (NetNameLogEvent)(resourceHandle,
  2147. LOG_WARNING,
  2148. L"Search for computer account %1!ws! on DC %2!ws! "
  2149. L"failed. status %3!08X!.\n",
  2150. virtualName,
  2151. hostingDCName,
  2152. hr);
  2153. }
  2154. } // end: the object wasn't found
  2155. } // if the domain wide search succeeded
  2156. else {
  2157. //
  2158. // our domain wide search failed - continue on
  2159. //
  2160. (NetNameLogEvent)(resourceHandle,
  2161. LOG_WARNING,
  2162. L"Domain wide search for computer account %1!ws! failed. status %2!08X!.\n",
  2163. virtualName,
  2164. hr);
  2165. }
  2166. //
  2167. // get a buffer to hold the machine Pwd
  2168. //
  2169. machinePwd = (PWCHAR)LocalAlloc( LMEM_FIXED, pwdBufferByteLength );
  2170. if ( machinePwd == NULL ) {
  2171. status = ERROR_NOT_ENOUGH_MEMORY;
  2172. (NetNameLogEvent)(resourceHandle,
  2173. LOG_ERROR,
  2174. L"Unable to allocate memory for resource data. status %1!u!.\n",
  2175. status);
  2176. addtlErrTextID = RES_NETNAME_RESDATA_ALLOC_FAILED;
  2177. goto cleanup;
  2178. }
  2179. //
  2180. // Extract the password from the ResourceData property.
  2181. //
  2182. ASSERT( Resource->Params.ResourceData != NULL );
  2183. status = DecryptNNResourceData(Resource,
  2184. Resource->Params.ResourceData,
  2185. Resource->ResDataSize,
  2186. machinePwd);
  2187. if ( status != ERROR_SUCCESS ) {
  2188. (NetNameLogEvent)(resourceHandle,
  2189. LOG_ERROR,
  2190. L"Unable to decrypt resource data. status %1!u!\n",
  2191. status );
  2192. addtlErrTextID = RES_NETNAME_DECRYPT_RESDATA_FAILED;
  2193. goto cleanup;
  2194. }
  2195. if ( !objectFound ) {
  2196. //
  2197. // no point in proceeding with the rest of the routine since we don't
  2198. // have an object on which to check the attributes and trying to
  2199. // generate a token is going to fail (most likely).
  2200. //
  2201. goto cleanup;
  2202. }
  2203. //
  2204. // get info about our selected DC and bind to its DS
  2205. //
  2206. (NetNameLogEvent)(resourceHandle,
  2207. LOG_INFORMATION,
  2208. L"Found computer account %1!ws! on domain controller %2!ws!.\n",
  2209. virtualName,
  2210. hostingDCName);
  2211. status = FindDomainForServer( resourceHandle, Resource->NodeName, hostingDCName, 0, &dcInfo );
  2212. if ( ClusWorkerCheckTerminate( Worker )) {
  2213. status = ERROR_OPERATION_ABORTED;
  2214. goto cleanup;
  2215. }
  2216. if ( status == ERROR_SUCCESS ) {
  2217. status = DsBindW( dcInfo->DomainControllerName, NULL, &dsHandle );
  2218. if ( status != NO_ERROR ) {
  2219. (NetNameLogEvent)(resourceHandle,
  2220. LOG_WARNING,
  2221. L"Unable to bind to domain controller %1!ws! (status %2!u!). "
  2222. L"Cannot proceed with computer account attribute check.\n",
  2223. hostingDCName,
  2224. status);
  2225. }
  2226. }
  2227. else {
  2228. (NetNameLogEvent)(resourceHandle,
  2229. LOG_WARNING,
  2230. L"Unable to get information about domain controller %1!ws! "
  2231. L"(status %2!u!). Cannot proceed with computer account attribute "
  2232. L"check.\n",
  2233. hostingDCName,
  2234. status);
  2235. }
  2236. if ( status != ERROR_SUCCESS ) {
  2237. //
  2238. // we couldn't get the DC info or we couldn't bind - non-fatal
  2239. //
  2240. status = ERROR_SUCCESS;
  2241. goto cleanup;
  2242. }
  2243. //
  2244. // add a $ to the end of the name. I don't know why we need to do this;
  2245. // computer accounts have always had a $ at the end.
  2246. //
  2247. virtualDollarName[ COUNT_OF( virtualDollarName ) - 1 ] = UNICODE_NULL;
  2248. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", virtualName );
  2249. //
  2250. // now check if it is disabled
  2251. //
  2252. status = NetUserGetInfo(dcInfo->DomainControllerName,
  2253. virtualDollarName,
  2254. 20,
  2255. (LPBYTE *)&netUI20);
  2256. if ( ClusWorkerCheckTerminate( Worker )) {
  2257. status = ERROR_OPERATION_ABORTED;
  2258. goto cleanup;
  2259. }
  2260. if ( status == NERR_Success ) {
  2261. if ( netUI20->usri20_flags & UF_ACCOUNTDISABLE ) {
  2262. USER_INFO_1008 netUI1008;
  2263. //
  2264. // try to re-enable; fail if we can't. Kerb on W2K will continue
  2265. // to give out tickets to disabled objects. In order to perform
  2266. // updates on the object, it needs to be enabled.
  2267. //
  2268. netUI1008.usri1008_flags = netUI20->usri20_flags & ~UF_ACCOUNTDISABLE;
  2269. status = NetUserSetInfo(dcInfo->DomainControllerName,
  2270. virtualDollarName,
  2271. 1008,
  2272. (LPBYTE)&netUI1008,
  2273. &paramInError);
  2274. if ( status != NERR_Success ) {
  2275. (NetNameLogEvent)(resourceHandle,
  2276. LOG_ERROR,
  2277. L"Computer account %1!ws! is disabled and couldn't be re-enabled. "
  2278. L"status %2!u!\n",
  2279. virtualName,
  2280. status);
  2281. addtlErrTextID = RES_NETNAME_CO_CANT_BE_REENABLED;
  2282. }
  2283. } // else the CO was enabled
  2284. NetApiBufferFree( netUI20 );
  2285. netUI20 = NULL;
  2286. if ( status != NERR_Success ) {
  2287. goto cleanup;
  2288. }
  2289. } else {
  2290. (NetNameLogEvent)(resourceHandle,
  2291. LOG_WARNING,
  2292. L"Couldn't determine if computer account %1!ws! "
  2293. L"is already disabled. status %2!u!\n",
  2294. virtualName,
  2295. status);
  2296. }
  2297. //
  2298. // see if our password is good by trying to do a network logon with this
  2299. // account. Get a token while we're at it.
  2300. //
  2301. status = NNLogonUser(resourceHandle,
  2302. virtualDollarName,
  2303. dcInfo->DomainName,
  2304. machinePwd,
  2305. &Resource->VSToken,
  2306. &tokenStatus);
  2307. if ( status == SEC_E_LOGON_DENIED ) {
  2308. (NetNameLogEvent)(resourceHandle,
  2309. LOG_WARNING,
  2310. L"Password for computer account %1!ws! is incorrect "
  2311. L"(status 0x%2!08X!). Updating computer account with "
  2312. L"stored password.\n",
  2313. virtualName,
  2314. status);
  2315. //
  2316. // now we need the PDC in write mode; rebind (if necessary). If that
  2317. // fails, fall back to the authenticating DC. See comments in
  2318. // AddComputerObject about choosing a DC.
  2319. //
  2320. if (( dcInfo->Flags & DS_WRITABLE_FLAG ) == 0 || ( dcInfo->Flags & DS_PDC_FLAG ) == 0 ) {
  2321. LPWSTR dcName = NULL;
  2322. DWORD nlStatus;
  2323. DWORD dsFlags = DS_PDC_REQUIRED | DS_WRITABLE_REQUIRED;
  2324. DsUnBind( &dsHandle );
  2325. dsHandle = NULL;
  2326. NetApiBufferFree( dcInfo );
  2327. dcInfo = NULL;
  2328. retry_dc_search:
  2329. status = FindDomainForServer( resourceHandle,
  2330. Resource->NodeName,
  2331. dcName,
  2332. dsFlags,
  2333. &dcInfo );
  2334. if ( status != ERROR_SUCCESS ) {
  2335. if ( dsFlags & DS_PDC_REQUIRED ) {
  2336. //
  2337. // retry with authenticating DC. remove the PDC flag so if
  2338. // we fail again, we'll fall through to the bailout case
  2339. //
  2340. nlStatus = NNGetAuthenticatingDCName( &dcName );
  2341. if ( nlStatus == ERROR_SUCCESS ) {
  2342. dsFlags &= ~DS_PDC_REQUIRED;
  2343. (NetNameLogEvent)(resourceHandle,
  2344. LOG_WARNING,
  2345. L"Unable to find the PDC in the domain (status: %1!u!). "
  2346. L"retying with the authenticating DC (%2!ws!).\n",
  2347. status,
  2348. dcName);
  2349. goto retry_dc_search;
  2350. }
  2351. else {
  2352. (NetNameLogEvent)(resourceHandle,
  2353. LOG_ERROR,
  2354. L"Unable to determine the name of the authenticating DC, status %1!u!.\n",
  2355. nlStatus);
  2356. }
  2357. } // else couldn't find the PDC and NL's DC wasn't available
  2358. (NetNameLogEvent)(resourceHandle,
  2359. LOG_ERROR,
  2360. L"No domain controller available to update password for computer "
  2361. L"account %1!ws!, status %2!u!.\n",
  2362. virtualName,
  2363. status);
  2364. } // else found the PDC
  2365. if ( dcName != NULL ) {
  2366. LocalFree( dcName );
  2367. }
  2368. } // else DC we had was PDC and writable
  2369. else {
  2370. status = ERROR_SUCCESS;
  2371. }
  2372. if ( status != ERROR_SUCCESS ) {
  2373. //
  2374. // we lost our original DC and we couldn't get one to the PDC or
  2375. // the auth DC and the password doesn't work. bail out.
  2376. //
  2377. addtlErrTextID = RES_NETNAME_NO_DC_FOR_PASSWORD_UPDATE;
  2378. goto cleanup;
  2379. }
  2380. //
  2381. // if the DC handling the update is different from the original
  2382. // creator, then it becomes the new synchronization DC while waiting
  2383. // for attribute changes to replicate.
  2384. //
  2385. if ( ClRtlStrICmp( dcInfo->DomainControllerName, Resource->Params.CreatingDC ) != 0 ) {
  2386. LPWSTR newDCName;
  2387. newDCName = ResUtilDupString( dcInfo->DomainControllerName );
  2388. if ( newDCName == NULL ) {
  2389. status = GetLastError();
  2390. (NetNameLogEvent)(resourceHandle,
  2391. LOG_ERROR,
  2392. L"Unable to allocate memory for CreatingDC property value, "
  2393. L"status %1!u!.\n",
  2394. status);
  2395. addtlErrTextID = RES_NETNAME_CREATINDC_ALLOC_FAILED;
  2396. goto cleanup;
  2397. }
  2398. status = ResUtilSetSzValue(Resource->ParametersKey,
  2399. PARAM_NAME__CREATING_DC,
  2400. newDCName,
  2401. NULL);
  2402. if ( status != ERROR_SUCCESS ) {
  2403. (NetNameLogEvent)(resourceHandle,
  2404. LOG_ERROR,
  2405. L"Unable to set CreatingDC property, status %1!u!.\n",
  2406. status);
  2407. addtlErrTextID = RES_NETNAME_CREATINGDC_UPDATE_FAILED;
  2408. LocalFree( newDCName );
  2409. goto cleanup;
  2410. }
  2411. //
  2412. // successfully committed change to cluster hive, now update the
  2413. // resource struct
  2414. //
  2415. LocalFree( Resource->Params.CreatingDC );
  2416. Resource->Params.CreatingDC = newDCName;
  2417. } // else the DC we are using is the original creating DC
  2418. //
  2419. // try setting our password on the object
  2420. //
  2421. netUI1003.usri1003_password = (PWCHAR)machinePwd;
  2422. status = NetUserSetInfo(dcInfo->DomainControllerName,
  2423. virtualDollarName,
  2424. 1003,
  2425. (PBYTE)&netUI1003,
  2426. &paramInError );
  2427. if ( ClusWorkerCheckTerminate( Worker )) {
  2428. status = ERROR_OPERATION_ABORTED;
  2429. goto cleanup;
  2430. }
  2431. if ( status == NERR_Success ) {
  2432. (NetNameLogEvent)(resourceHandle,
  2433. LOG_INFORMATION,
  2434. L"Updated password for computer account "
  2435. L"%1!ws! on DC %2!ws!.\n",
  2436. virtualName,
  2437. dcInfo->DomainControllerName);
  2438. //
  2439. // now that password is fixed up, try to get a token; if object
  2440. // hasn't replicated to authenticating DC, this could fail.
  2441. //
  2442. status = NNLogonUser(resourceHandle,
  2443. virtualDollarName,
  2444. dcInfo->DomainName,
  2445. machinePwd,
  2446. &Resource->VSToken,
  2447. &tokenStatus);
  2448. if ( status != ERROR_SUCCESS ) {
  2449. (NetNameLogEvent)(resourceHandle,
  2450. LOG_WARNING,
  2451. L"Couldn't log the computer account onto the domain. status 0x%1!08X!\n",
  2452. status);
  2453. }
  2454. } else {
  2455. (NetNameLogEvent)(resourceHandle,
  2456. LOG_ERROR,
  2457. L"Unable to update password for computer account "
  2458. L"%1!ws! on DC %2!ws!, status %3!u!.\n",
  2459. virtualName,
  2460. dcInfo->DomainControllerName,
  2461. status);
  2462. addtlErrTextID = RES_NETNAME_CO_PASSWORD_UPDATE_FAILED;
  2463. goto cleanup;
  2464. }
  2465. } // else logon failed for some reason other than logon denied
  2466. else if ( status != ERROR_SUCCESS ) {
  2467. //
  2468. // can't tell if password is good; continue on hoping that it is ok.
  2469. //
  2470. (NetNameLogEvent)(resourceHandle,
  2471. LOG_WARNING,
  2472. L"Couldn't log the computer account onto the domain (status 0x%1!08X!). "
  2473. L"Validity of locally stored password is unknown.\n",
  2474. status);
  2475. } // else NNLogonUser succeeded
  2476. if ( tokenStatus != ERROR_SUCCESS ) {
  2477. (NetNameLogEvent)(resourceHandle,
  2478. LOG_WARNING,
  2479. L"Unable to get token for computer account - status %1!u!\n",
  2480. tokenStatus);
  2481. }
  2482. //
  2483. // make sure residual non-fatal error doesn't kill us later on
  2484. //
  2485. status = ERROR_SUCCESS;
  2486. //
  2487. // use the Object GUID for binding during CheckComputerObjectAttributes so
  2488. // we don't have to track changes to the DN. If the object moved in the
  2489. // DS, its DN will change but not its GUID. We use this code instead of
  2490. // GetComputerObjectGuid because we want to target a specific DC.
  2491. //
  2492. // If we fail to get the GUID, that in itself is not a failure. If later
  2493. // on, we need to update the DNS hostname attribute, then we fail since we
  2494. // need a pointer to the computer object which is a side effect of getting
  2495. // the GUID.
  2496. //
  2497. {
  2498. IADs * pADs = NULL;
  2499. hr = GetComputerObjectViaFQDN( virtualFQDN, dcInfo->DomainControllerName, &compObj );
  2500. if ( SUCCEEDED( hr )) {
  2501. hr = compObj->QueryInterface(IID_IADs, (void**) &pADs);
  2502. if ( SUCCEEDED( hr )) {
  2503. BSTR guidStr = NULL;
  2504. hr = pADs->get_GUID( &guidStr );
  2505. if ( SUCCEEDED( hr )) {
  2506. if ( Resource->ObjectGUID != NULL ) {
  2507. LocalFree( Resource->ObjectGUID );
  2508. Resource->ObjectGUID = NULL;
  2509. }
  2510. Resource->ObjectGUID = ResUtilDupString( guidStr );
  2511. if ( Resource->ObjectGUID == NULL ) {
  2512. hr = HRESULT_FROM_WIN32( GetLastError());
  2513. }
  2514. }
  2515. else {
  2516. }
  2517. if ( guidStr ) {
  2518. SysFreeString( guidStr );
  2519. }
  2520. } // else QI for IADs object failed
  2521. if ( pADs != NULL ) {
  2522. pADs->Release();
  2523. }
  2524. if ( FAILED( hr )) {
  2525. (NetNameLogEvent)(resourceHandle,
  2526. LOG_WARNING,
  2527. L"Failed to get object GUID for computer account %1!ws!, "
  2528. L"status %2!08X!\n",
  2529. virtualName,
  2530. hr );
  2531. }
  2532. }
  2533. else {
  2534. (NetNameLogEvent)(resourceHandle,
  2535. LOG_WARNING,
  2536. L"Failed to obtain IDirectoryObject pointer to computer "
  2537. L"account %1!ws!, status %2!08X!\n",
  2538. virtualName,
  2539. hr );
  2540. }
  2541. } // local block to get IDirectoryObject pointer
  2542. //
  2543. // now check the attributes on the object we care about are correct
  2544. //
  2545. hr = CheckComputerObjectAttributes( Resource, dcInfo->DomainControllerName );
  2546. if ( FAILED( hr ) && compObj != NULL ) {
  2547. (NetNameLogEvent)(resourceHandle,
  2548. LOG_INFORMATION,
  2549. L"Updating attributes for computer account %1!ws!\n",
  2550. virtualName);
  2551. //
  2552. // add the DnsHostName and ServicePrincipalName attributes
  2553. //
  2554. hr = AddDnsHostNameAttribute( resourceHandle, compObj, virtualName, FALSE );
  2555. if ( FAILED( hr )) {
  2556. status = hr;
  2557. (NetNameLogEvent)(resourceHandle,
  2558. LOG_ERROR,
  2559. L"Unable to set DnsHostName attribute for computer account %1!ws!, "
  2560. L"status %2!08X!.\n",
  2561. virtualName,
  2562. status);
  2563. addtlErrTextID = RES_NETNAME_DNSHOSTNAME_UPDATE_FAILED;
  2564. goto cleanup;
  2565. }
  2566. if ( ClusWorkerCheckTerminate( Worker )) {
  2567. status = ERROR_OPERATION_ABORTED;
  2568. goto cleanup;
  2569. }
  2570. status = AddServicePrincipalNames( dsHandle,
  2571. virtualFQDN,
  2572. virtualName,
  2573. dcInfo->DomainName,
  2574. Resource->dwFlags & CLUS_FLAG_CORE );
  2575. if ( status != ERROR_SUCCESS ) {
  2576. (NetNameLogEvent)(resourceHandle,
  2577. LOG_ERROR,
  2578. L"Unable to set ServicePrincipalName attribute for computer account %1!ws!, "
  2579. L"status %2!u!.\n",
  2580. virtualName,
  2581. status);
  2582. addtlErrTextID = RES_NETNAME_SPN_UPDATE_FAILED;
  2583. goto cleanup;
  2584. }
  2585. } // else attributes are correct
  2586. cleanup:
  2587. //
  2588. // always free these
  2589. //
  2590. if ( dsHandle != NULL ) {
  2591. DsUnBind( &dsHandle );
  2592. }
  2593. if ( compObj != NULL ) {
  2594. compObj->Release();
  2595. }
  2596. if ( virtualFQDN != NULL ) {
  2597. LocalFree( virtualFQDN );
  2598. }
  2599. if ( hostingDCName != NULL && hostingDCName != Resource->Params.CreatingDC ) {
  2600. //
  2601. // hostingDCName was allocated by the search routine
  2602. //
  2603. LocalFree( hostingDCName );
  2604. }
  2605. if ( netUI20 != NULL ) {
  2606. NetApiBufferFree( netUI20 );
  2607. }
  2608. if ( status == ERROR_SUCCESS ) {
  2609. *MachinePwd = machinePwd;
  2610. } else {
  2611. if ( status != ERROR_OPERATION_ABORTED ) {
  2612. LPWSTR msgBuff;
  2613. DWORD msgBytes;
  2614. LPWSTR addtlErrText;
  2615. LPWSTR domainName;
  2616. WCHAR domainNameBuffer[ DNS_MAX_NAME_BUFFER_LENGTH ];
  2617. DWORD domainNameChars = COUNT_OF( domainNameBuffer );
  2618. msgBytes = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
  2619. FORMAT_MESSAGE_FROM_SYSTEM,
  2620. NULL,
  2621. status,
  2622. 0,
  2623. (LPWSTR)&msgBuff,
  2624. 0,
  2625. NULL);
  2626. //
  2627. // get the additional error text to go with the generic failed message
  2628. //
  2629. addtlErrText = ClusResLoadMessage( addtlErrTextID );
  2630. success = GetComputerNameEx(ComputerNameDnsDomain,
  2631. domainNameBuffer,
  2632. &domainNameChars);
  2633. if ( success ) {
  2634. domainName = domainNameBuffer;
  2635. }
  2636. else {
  2637. domainName = NULL;
  2638. }
  2639. if ( msgBytes > 0 ) {
  2640. ClusResLogSystemEventByKey3(Resource->ResKey,
  2641. LOG_CRITICAL,
  2642. RES_NETNAME_UPDATE_COMPUTER_ACCOUNT_FAILED,
  2643. domainName,
  2644. addtlErrText,
  2645. msgBuff);
  2646. LocalFree( msgBuff );
  2647. } else {
  2648. ClusResLogSystemEventByKeyData2(Resource->ResKey,
  2649. LOG_CRITICAL,
  2650. RES_NETNAME_UPDATE_COMPUTER_ACCOUNT_FAILED_STATUS,
  2651. sizeof( status ),
  2652. &status,
  2653. domainName,
  2654. addtlErrText);
  2655. }
  2656. if ( addtlErrText ) {
  2657. LocalFree( addtlErrText );
  2658. }
  2659. }
  2660. if ( machinePwd != NULL ) {
  2661. //
  2662. // zero out the string
  2663. //
  2664. RtlSecureZeroMemory( machinePwd, pwdBufferByteLength );
  2665. LocalFree( machinePwd );
  2666. }
  2667. if ( Resource->VSToken ) {
  2668. CloseHandle( Resource->VSToken );
  2669. Resource->VSToken = NULL;
  2670. }
  2671. }
  2672. if ( dcInfo != NULL ) {
  2673. NetApiBufferFree( dcInfo );
  2674. }
  2675. return status;
  2676. } // UpdateComputerObject
  2677. DWORD
  2678. DisableComputerObject(
  2679. PNETNAME_RESOURCE Resource
  2680. )
  2681. /*++
  2682. Routine Description:
  2683. Disable the CO associated with the resource.
  2684. Arguments:
  2685. Resource
  2686. Return Value:
  2687. success otherwise Win32 error
  2688. --*/
  2689. {
  2690. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  2691. PWCHAR virtualName;
  2692. LPWSTR dcName = NULL;
  2693. NET_API_STATUS status;
  2694. PUSER_INFO_20 netUI20;
  2695. USER_INFO_1008 netUI1008;
  2696. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  2697. PDOMAIN_CONTROLLER_INFO dcInfo = NULL;
  2698. //
  2699. // use the name stored in the registry since the name property could have
  2700. // been changed multiple times while offline
  2701. //
  2702. virtualName = ResUtilGetSzValue( Resource->ParametersKey, PARAM_NAME__NAME );
  2703. if ( virtualName == NULL ) {
  2704. return ERROR_SUCCESS;
  2705. }
  2706. //
  2707. // find a writable DC; 2nd time through, we try the creating DC...
  2708. //
  2709. retry_findDC:
  2710. status = FindDomainForServer( resourceHandle, Resource->NodeName, dcName, DS_WRITABLE_REQUIRED, &dcInfo );
  2711. if ( status != ERROR_SUCCESS ) {
  2712. (NetNameLogEvent)(resourceHandle,
  2713. LOG_ERROR,
  2714. L"Can't connect to DC %1!ws! to disable computer account %2!ws!. "
  2715. L"status %3!u!\n",
  2716. Resource->Params.CreatingDC,
  2717. virtualName,
  2718. status);
  2719. return status;
  2720. }
  2721. virtualDollarName[ COUNT_OF( virtualDollarName ) - 1 ] = UNICODE_NULL;
  2722. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", virtualName );
  2723. //
  2724. // get the current flags associated with the account
  2725. //
  2726. status = NetUserGetInfo(dcInfo->DomainControllerName,
  2727. virtualDollarName,
  2728. 20,
  2729. (LPBYTE *)&netUI20);
  2730. if ( status == NERR_Success ) {
  2731. DWORD paramInError;
  2732. if ( netUI20->usri20_flags & UF_ACCOUNTDISABLE ) {
  2733. status = ERROR_SUCCESS;
  2734. } else {
  2735. netUI1008.usri1008_flags = netUI20->usri20_flags | UF_ACCOUNTDISABLE;
  2736. status = NetUserSetInfo(dcInfo->DomainControllerName,
  2737. virtualDollarName,
  2738. 1008,
  2739. (LPBYTE)&netUI1008,
  2740. &paramInError);
  2741. if ( status != NERR_Success ) {
  2742. (NetNameLogEvent)(resourceHandle,
  2743. LOG_ERROR,
  2744. L"Failed to disable computer account %1!ws!. status %2!u!\n",
  2745. virtualName,
  2746. status);
  2747. }
  2748. }
  2749. NetApiBufferFree( netUI20 );
  2750. } else if ( status == NERR_UserNotFound && dcName == NULL && Resource->Params.CreatingDC != NULL ) {
  2751. //
  2752. // object must have not replicated just yet; retry on our synch point
  2753. // DC
  2754. //
  2755. (NetNameLogEvent)(resourceHandle,
  2756. LOG_WARNING,
  2757. L"Failed to find computer account %1!ws! in order "
  2758. L"to disable. Retrying on DC %2!ws!.\n",
  2759. virtualName,
  2760. Resource->Params.CreatingDC);
  2761. NetApiBufferFree( dcInfo );
  2762. dcInfo = NULL;
  2763. dcName = Resource->Params.CreatingDC;
  2764. goto retry_findDC;
  2765. } else if ( status == NERR_UserNotFound ) {
  2766. //
  2767. // apparently nothing to disable
  2768. //
  2769. (NetNameLogEvent)(resourceHandle,
  2770. LOG_INFORMATION,
  2771. L"Failed to find computer account %1!ws! in order "
  2772. L"to disable.\n",
  2773. virtualName);
  2774. } else {
  2775. (NetNameLogEvent)(resourceHandle,
  2776. LOG_WARNING,
  2777. L"Couldn't determine if computer account %1!ws! "
  2778. L"is already disabled. status %2!u!\n",
  2779. virtualName,
  2780. status);
  2781. }
  2782. if ( status == ERROR_SUCCESS ) {
  2783. (NetNameLogEvent)(resourceHandle,
  2784. LOG_INFORMATION,
  2785. L"Computer account %1!ws! is disabled.\n",
  2786. virtualName);
  2787. }
  2788. LocalFree( virtualName );
  2789. if ( dcInfo != NULL ) {
  2790. NetApiBufferFree( dcInfo );
  2791. }
  2792. return status;
  2793. } // DisableComputerObject
  2794. HRESULT
  2795. GetComputerObjectGuid(
  2796. IN PNETNAME_RESOURCE Resource,
  2797. IN LPWSTR Name OPTIONAL
  2798. )
  2799. /*++
  2800. Routine Description:
  2801. For the given resource, find its computer object's GUID in the DS
  2802. Arguments:
  2803. Resource - pointer to netname resource context block
  2804. Name - optional pointer to name to use. If not specified, use name in resource block
  2805. Return Value:
  2806. ERROR_SUCCESS, otherwise appropriate Win32 error
  2807. --*/
  2808. {
  2809. LPWSTR virtualFQDN = NULL;
  2810. LPWSTR nameToFind;
  2811. HRESULT hr;
  2812. DWORD status;
  2813. BOOL objectExists;
  2814. //
  2815. // use the optional name if specified.
  2816. //
  2817. if ( ARGUMENT_PRESENT( Name )) {
  2818. nameToFind = Name;
  2819. } else {
  2820. nameToFind = Resource->Params.NetworkName;
  2821. }
  2822. //
  2823. // get the FQ Distinguished Name of the object
  2824. //
  2825. hr = IsComputerObjectInDS( Resource->ResourceHandle,
  2826. Resource->NodeName,
  2827. nameToFind,
  2828. NULL,
  2829. &objectExists,
  2830. &virtualFQDN,
  2831. NULL); // don't need HostingDCName
  2832. if ( SUCCEEDED( hr )) {
  2833. if ( objectExists ) {
  2834. IDirectoryObject * compObj = NULL;
  2835. //
  2836. // get a COM pointer to the computer object
  2837. //
  2838. hr = GetComputerObjectViaFQDN( virtualFQDN, NULL, &compObj );
  2839. if ( SUCCEEDED( hr )) {
  2840. IADs * pADs = NULL;
  2841. //
  2842. // get a pointer to the generic IADs interface so we can get the
  2843. // GUID
  2844. //
  2845. hr = compObj->QueryInterface(IID_IADs, (void**) &pADs);
  2846. if ( SUCCEEDED( hr )) {
  2847. BSTR guidStr = NULL;
  2848. hr = pADs->get_GUID( &guidStr );
  2849. if ( SUCCEEDED( hr )) {
  2850. if ( Resource->ObjectGUID != NULL ) {
  2851. LocalFree( Resource->ObjectGUID );
  2852. }
  2853. Resource->ObjectGUID = ResUtilDupString( guidStr );
  2854. }
  2855. if ( guidStr ) {
  2856. SysFreeString( guidStr );
  2857. }
  2858. }
  2859. if ( pADs != NULL ) {
  2860. pADs->Release();
  2861. }
  2862. }
  2863. if ( compObj != NULL ) {
  2864. compObj->Release();
  2865. }
  2866. }
  2867. else {
  2868. hr = HRESULT_FROM_WIN32( ERROR_DS_NO_SUCH_OBJECT );
  2869. }
  2870. }
  2871. if ( virtualFQDN ) {
  2872. LocalFree( virtualFQDN );
  2873. }
  2874. return hr;
  2875. } // GetComputerObjectGuid
  2876. HRESULT
  2877. CheckComputerObjectAttributes(
  2878. IN PNETNAME_RESOURCE Resource,
  2879. IN LPWSTR DCName OPTIONAL
  2880. )
  2881. /*++
  2882. Routine Description:
  2883. LooksAlive routine for computer object. Using an IDirectoryObject pointer
  2884. to the virtual CO, check its DnsHostName and SPN attributes
  2885. Arguments:
  2886. Resource - pointer to netname resource context structure
  2887. DCName - optional pointer to a DC to bind to
  2888. Return Value:
  2889. S_OK if everything worked ok...
  2890. --*/
  2891. {
  2892. HRESULT hr;
  2893. ADS_ATTR_INFO * attributeInfo = NULL;
  2894. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  2895. IDirectoryObject * compObj = NULL;
  2896. //
  2897. // get a pointer to our CO
  2898. //
  2899. hr = GetComputerObjectViaGUID( Resource->ObjectGUID, DCName, &compObj );
  2900. if ( SUCCEEDED( hr )) {
  2901. LPWSTR attributeNames[2] = { L"DnsHostName", L"ServicePrincipalName" };
  2902. DWORD numAttributes = COUNT_OF( attributeNames );
  2903. DWORD countOfAttrs;;
  2904. hr = compObj->GetObjectAttributes(attributeNames,
  2905. numAttributes,
  2906. &attributeInfo,
  2907. &countOfAttrs );
  2908. if ( SUCCEEDED( hr )) {
  2909. DWORD i;
  2910. WCHAR fqDnsName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  2911. DWORD nodeCharCount;
  2912. DWORD fqDnsSize;
  2913. BOOL setUnexpected = FALSE;
  2914. BOOL success;
  2915. ADS_ATTR_INFO * attrInfo;
  2916. //
  2917. // check that we found our attributes
  2918. //
  2919. if ( countOfAttrs != numAttributes ) {
  2920. (NetNameLogEvent)(resourceHandle,
  2921. LOG_WARNING,
  2922. L"DnsHostName and/or ServicePrincipalName attributes are "
  2923. L"missing from computer account in DS.\n");
  2924. hr = E_UNEXPECTED;
  2925. goto cleanup;
  2926. }
  2927. //
  2928. // build our FQDnsName using the primary DNS domain for this
  2929. // node. Add 1 for the dot.
  2930. //
  2931. nodeCharCount = wcslen( Resource->Params.NetworkName ) + 1;
  2932. wcscpy( fqDnsName, Resource->Params.NetworkName );
  2933. wcscat( fqDnsName, L"." );
  2934. fqDnsSize = COUNT_OF( fqDnsName ) - nodeCharCount;
  2935. success = GetComputerNameEx( ComputerNameDnsDomain,
  2936. &fqDnsName[ nodeCharCount ],
  2937. &fqDnsSize );
  2938. ASSERT( success );
  2939. attrInfo = attributeInfo;
  2940. for( i = 0; i < countOfAttrs; i++, attrInfo++ ) {
  2941. if ( ClRtlStrICmp( attrInfo->pszAttrName, L"DnsHostName" ) == 0 ) {
  2942. //
  2943. // should only be one entry and it should match our constructed FQDN
  2944. //
  2945. if ( attrInfo->dwNumValues == 1 ) {
  2946. if ( ClRtlStrICmp( attrInfo->pADsValues->CaseIgnoreString,
  2947. fqDnsName ) != 0 )
  2948. {
  2949. (NetNameLogEvent)(resourceHandle,
  2950. LOG_ERROR,
  2951. L"DnsHostName attribute in DS doesn't match. "
  2952. L"Expected: %1!ws! Actual: %2!ws!\n",
  2953. fqDnsName,
  2954. attrInfo->pADsValues->CaseIgnoreString);
  2955. setUnexpected = TRUE;
  2956. }
  2957. }
  2958. else {
  2959. (NetNameLogEvent)(resourceHandle,
  2960. LOG_ERROR,
  2961. L"Found more than one string for DnsHostName attribute in DS.\n");
  2962. setUnexpected = TRUE;
  2963. }
  2964. }
  2965. else {
  2966. //
  2967. // SPNs require more work since we publish a bunch of them
  2968. // as well as other services may have added their
  2969. // SPNs. The core resource should have more SPNs than the
  2970. // normal VS resource.
  2971. //
  2972. BOOL isCoreResource = ( Resource->dwFlags & CLUS_FLAG_CORE );
  2973. DWORD spnCount = isCoreResource ? SPN_CORE_SPN_COUNT : SPN_VS_SPN_COUNT;
  2974. PNN_SPN_LIST spnList;
  2975. if ( attrInfo->dwNumValues >= spnCount ) {
  2976. DWORD countOfOurSPNs = 0;
  2977. DWORD value;
  2978. DWORD status;
  2979. status = BuildNetNameSPNs( Resource->Params.NetworkName,
  2980. isCoreResource,
  2981. &spnList);
  2982. if ( status == ERROR_SUCCESS ) {
  2983. //
  2984. // examine each SPN and count the number that match the list
  2985. //
  2986. for ( value = 0; value < attrInfo->dwNumValues; value++, attrInfo->pADsValues++) {
  2987. DWORD spnIndex;
  2988. for ( spnIndex = 0; spnIndex < spnCount; ++spnIndex ) {
  2989. if ( ClRtlStrICmp(attrInfo->pADsValues->CaseIgnoreString,
  2990. spnList->Spn[ spnIndex ]) == 0 )
  2991. {
  2992. ++countOfOurSPNs;
  2993. break;
  2994. }
  2995. } // SPN list loop
  2996. } // end of for each SPN value in the DS
  2997. if ( countOfOurSPNs != spnCount ) {
  2998. (NetNameLogEvent)(resourceHandle,
  2999. LOG_WARNING,
  3000. L"There are missing entries in the ServicePrincipalName "
  3001. L"attribute.\n");
  3002. setUnexpected = TRUE;
  3003. }
  3004. LocalFree( spnList );
  3005. } else {
  3006. (NetNameLogEvent)(resourceHandle,
  3007. LOG_WARNING,
  3008. L"Unable to build list of SPNs to check, status %1!u!\n");
  3009. }
  3010. }
  3011. else {
  3012. (NetNameLogEvent)(resourceHandle,
  3013. LOG_WARNING,
  3014. L"There are missing entries in the ServicePrincipalName "
  3015. L"attribute.\n");
  3016. setUnexpected = TRUE;
  3017. }
  3018. }
  3019. } // for each attribute info entry
  3020. if ( setUnexpected ) {
  3021. hr = E_UNEXPECTED;
  3022. }
  3023. } // if GetObjectAttributes succeeded
  3024. else {
  3025. (NetNameLogEvent)(resourceHandle,
  3026. LOG_ERROR,
  3027. L"Unable to find attributes for computer object in DS. status %1!08X!.\n",
  3028. hr);
  3029. }
  3030. } // if GetComputerObjectViaFQDN succeeded
  3031. else {
  3032. (NetNameLogEvent)(resourceHandle,
  3033. LOG_WARNING,
  3034. L"Computer account attribute check: Unable to find computer account %1!ws! with GUID "
  3035. L"{%2!.2ws!%3!.2ws!%4!.2ws!%5!.2ws!-%6!.2ws!%7!.2ws!-%8!.2ws!%9!.2ws!-%10!.4ws!-%11!ws!} "
  3036. L"in Active Directory. status %12!08X!.\n",
  3037. Resource->Params.NetworkName,
  3038. Resource->ObjectGUID + 6,
  3039. Resource->ObjectGUID + 4,
  3040. Resource->ObjectGUID + 2,
  3041. Resource->ObjectGUID,
  3042. Resource->ObjectGUID + 10,
  3043. Resource->ObjectGUID + 8,
  3044. Resource->ObjectGUID + 14,
  3045. Resource->ObjectGUID + 12,
  3046. Resource->ObjectGUID + 16,
  3047. Resource->ObjectGUID + 20,
  3048. hr);
  3049. }
  3050. cleanup:
  3051. if ( attributeInfo != NULL ) {
  3052. FreeADsMem( attributeInfo );
  3053. }
  3054. if ( compObj != NULL ) {
  3055. compObj->Release();
  3056. }
  3057. return hr;
  3058. } // CheckComputerObjectAttributes
  3059. HRESULT
  3060. IsComputerObjectInDS(
  3061. IN RESOURCE_HANDLE ResourceHandle,
  3062. IN LPWSTR NodeName,
  3063. IN LPWSTR NewObjectName,
  3064. IN LPWSTR DCName OPTIONAL,
  3065. OUT PBOOL ObjectExists,
  3066. OUT LPWSTR * DistinguishedName, OPTIONAL
  3067. OUT LPWSTR * HostingDCName OPTIONAL
  3068. )
  3069. /*++
  3070. Routine Description:
  3071. See if the specified name has a computer object in the DS. We do this by:
  3072. 1) binding to a domain controller in the domain and QI'ing for an
  3073. IDirectorySearch object
  3074. 2) specifying (&(objectCategory=computer)(cn=<new name>)) as the search
  3075. string
  3076. 3) examining result count of search; 1 means it exists.
  3077. Arguments:
  3078. ResourceHandle - used to log domain lookup into cluster log
  3079. NodeName - our name; used to to find DS with which to bind
  3080. NewObjectName - requested new name of object
  3081. DCName - name of DC where search is performed. If none supplied,
  3082. then domain is searched
  3083. ObjectExists - TRUE if object already exists; only valid if function
  3084. status is success
  3085. DistinguishedName - optional address of pointer that receives the
  3086. LDAP FQDN of the object
  3087. HostingDCName - optional address of pointer to receive name of DC where
  3088. object was found. Used when DCName is NULL.
  3089. Return Value:
  3090. ERROR_SUCCESS if everything worked
  3091. --*/
  3092. {
  3093. BOOL objectExists;
  3094. HRESULT hr;
  3095. DWORD charsFormatted;
  3096. LPWSTR distName = L"distinguishedName";
  3097. WCHAR buffer[ DNS_MAX_NAME_BUFFER_LENGTH + COUNT_OF( LdapHeader ) ];
  3098. PWCHAR bindingString = buffer;
  3099. DWORD bindingChars;
  3100. DWORD status;
  3101. LPWSTR targetName;
  3102. WCHAR searchLeader[] = L"(&(objectCategory=computer)(name=";
  3103. WCHAR searchTrailer[] = L"))";
  3104. WCHAR searchFilter[ COUNT_OF( searchLeader ) + MAX_COMPUTERNAME_LENGTH + COUNT_OF( searchTrailer )];
  3105. ADS_SEARCH_COLUMN searchCol;
  3106. ADS_SEARCHPREF_INFO searchPrefs[2];
  3107. IDirectorySearch * pDSSearch = NULL;
  3108. ADS_SEARCH_HANDLE searchHandle;
  3109. PDOMAIN_CONTROLLER_INFO dcInfo = NULL;
  3110. //
  3111. // bind to the domain if no DC was specified
  3112. //
  3113. if ( ARGUMENT_PRESENT( DCName )) {
  3114. if ( *DCName == L'\\' && *(DCName+1) == L'\\' ) { // skip over double backslashes
  3115. DCName += 2;
  3116. }
  3117. targetName = DCName;
  3118. }
  3119. else {
  3120. //
  3121. // get any DC
  3122. //
  3123. status = FindDomainForServer( ResourceHandle, NodeName, NULL, 0, &dcInfo );
  3124. if ( status != ERROR_SUCCESS ) {
  3125. return HRESULT_FROM_WIN32( status );
  3126. }
  3127. //
  3128. // format an LDAP binding string for DNS suffix of the domain.
  3129. //
  3130. targetName = dcInfo->DomainName;
  3131. }
  3132. bindingChars = (DWORD)( COUNT_OF( LdapHeader ) + wcslen( targetName ));
  3133. if ( bindingChars > COUNT_OF( buffer )) {
  3134. bindingString = (PWCHAR)LocalAlloc( LMEM_FIXED, bindingChars * sizeof( WCHAR ));
  3135. if ( bindingString == NULL ) {
  3136. hr = HRESULT_FROM_WIN32( GetLastError());
  3137. goto cleanup;
  3138. }
  3139. }
  3140. wcscpy( bindingString, LdapHeader );
  3141. wcscat( bindingString, targetName );
  3142. hr = ADsGetObject( bindingString, IID_IDirectorySearch, (VOID **)&pDSSearch );
  3143. if ( FAILED( hr )) {
  3144. goto cleanup;
  3145. }
  3146. //
  3147. // build search preference array. we limit the size to one and we want to
  3148. // scope the search to check all subtrees.
  3149. //
  3150. searchPrefs[0].dwSearchPref = ADS_SEARCHPREF_SIZE_LIMIT;
  3151. searchPrefs[0].vValue.dwType = ADSTYPE_INTEGER;
  3152. searchPrefs[0].vValue.Integer = 1;
  3153. searchPrefs[1].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
  3154. searchPrefs[1].vValue.dwType = ADSTYPE_INTEGER;
  3155. searchPrefs[1].vValue.Integer = ADS_SCOPE_SUBTREE;
  3156. hr = pDSSearch->SetSearchPreference( searchPrefs, COUNT_OF( searchPrefs ));
  3157. if ( FAILED( hr )) {
  3158. goto cleanup;
  3159. }
  3160. //
  3161. // build the search filter and execute the search; constrain the
  3162. // attributes to the distinguished name.
  3163. //
  3164. searchFilter[ COUNT_OF( searchFilter ) - 1 ] = UNICODE_NULL;
  3165. charsFormatted = _snwprintf(searchFilter,
  3166. COUNT_OF( searchFilter ) - 1,
  3167. L"%ws%ws%ws",
  3168. searchLeader,
  3169. NewObjectName,
  3170. searchTrailer);
  3171. ASSERT( charsFormatted > COUNT_OF( searchLeader ));
  3172. hr = pDSSearch->ExecuteSearch(searchFilter,
  3173. &distName,
  3174. 1,
  3175. &searchHandle);
  3176. if ( FAILED( hr )) {
  3177. goto cleanup;
  3178. }
  3179. //
  3180. // try to get the first row. Anything but S_OK returns FALSE
  3181. //
  3182. hr = pDSSearch->GetFirstRow( searchHandle );
  3183. *ObjectExists = (hr == S_OK);
  3184. if ( hr == S_ADS_NOMORE_ROWS ) {
  3185. hr = S_OK;
  3186. }
  3187. if ( *ObjectExists ) {
  3188. if ( ARGUMENT_PRESENT( DistinguishedName )) {
  3189. hr = pDSSearch->GetColumn( searchHandle, distName, &searchCol );
  3190. if ( SUCCEEDED( hr )) {
  3191. DWORD fqdnChars;
  3192. #if DBG
  3193. (NetNameLogEvent)(ResourceHandle,
  3194. LOG_INFORMATION,
  3195. L"IsComputerObjectInDS: found %1!ws!\n",
  3196. searchCol.pADsValues->DNString);
  3197. #endif
  3198. fqdnChars = wcslen( searchCol.pADsValues->DNString ) + 1;
  3199. *DistinguishedName = (LPWSTR)LocalAlloc( LMEM_FIXED, fqdnChars * sizeof(WCHAR));
  3200. if ( *DistinguishedName ) {
  3201. wcscpy( *DistinguishedName, searchCol.pADsValues->DNString );
  3202. } else {
  3203. hr = HRESULT_FROM_WIN32( GetLastError());
  3204. }
  3205. pDSSearch->FreeColumn( &searchCol );
  3206. }
  3207. } // else FQDN wasn't needed
  3208. if ( ARGUMENT_PRESENT( HostingDCName )) {
  3209. IADsObjectOptions * objOpt = NULL;
  3210. VARIANT serverName;
  3211. VariantInit( &serverName );
  3212. hr = pDSSearch->QueryInterface( IID_IADsObjectOptions, (void**)&objOpt );
  3213. if ( SUCCEEDED( hr )) {
  3214. hr = objOpt->GetOption( ADS_OPTION_SERVERNAME, &serverName );
  3215. if ( SUCCEEDED( hr )) {
  3216. *HostingDCName = ResUtilDupString( V_BSTR( &serverName ));
  3217. }
  3218. VariantClear( &serverName );
  3219. }
  3220. if ( objOpt != NULL ) {
  3221. objOpt->Release();
  3222. }
  3223. } // else hosting DC name wasn't needed
  3224. }
  3225. pDSSearch->CloseSearchHandle( searchHandle );
  3226. cleanup:
  3227. if ( pDSSearch != NULL ) {
  3228. pDSSearch->Release();
  3229. }
  3230. if ( dcInfo != NULL ) {
  3231. NetApiBufferFree( dcInfo );
  3232. }
  3233. if ( bindingString != buffer && bindingString != NULL ) {
  3234. LocalFree( bindingString );
  3235. }
  3236. return hr;
  3237. } // IsComputerObjectInDS
  3238. HRESULT
  3239. RenameComputerObject(
  3240. IN PNETNAME_RESOURCE Resource,
  3241. IN LPWSTR OriginalName,
  3242. IN LPWSTR NewName
  3243. )
  3244. /*++
  3245. Routine Description:
  3246. Rename the computer object at the DS. Do this by:
  3247. 1) Check if we have to recover from an interrupted rename attempt.
  3248. 2) Find the object in the DS on the CreatingDC. If that fails, try to find
  3249. the object on another DC. If that succeeds, get the DC that provided
  3250. the binding and update CreatingDC property.
  3251. 3) Write the old and new names to the resource's GUID reg area (not
  3252. Parameters). These are used to recover from a botched rename attempt if
  3253. resmon fails in the middle of renaming.
  3254. 4) use NetUserSetInfo, level 0 to change the name. This updates a number
  3255. of name specific attributes of the object as well as all Netbios based
  3256. SPNs
  3257. 5) Clear just the old name from the resource's reg area. This will
  3258. indicate that the rename was successful. When the Name property is
  3259. successfully updated in the registry, the RenameNewName value is
  3260. deleted.
  3261. 6) update the DnsHostName attribute. This updates the DNS based SPNs
  3262. Renaming must be a recoverable operation. If the DnsHostName update fails
  3263. after the object is renamed, the rename must be backed out. Even though
  3264. online tries to fixup the attribute, if it can't be changed here, the
  3265. fixup will fail during online.
  3266. In addition, if resmon should die while in the middle of a rename, netname
  3267. has to recover from that as well. Two keys, RenameOriginalName and
  3268. RenameNewName, hold those names prior to the update. If resmon dies before
  3269. the Name property is updated, the orignal name will be kept. If necessary,
  3270. the object must be renamed back to the original name.
  3271. Arguments:
  3272. Resource - pointer to the netname context block
  3273. OriginalName - pointer to the current name
  3274. NewName - pointer to new name
  3275. Return Value:
  3276. ERROR_SUCCESS if it worked...
  3277. --*/
  3278. {
  3279. HRESULT hr = S_OK;
  3280. DWORD status;
  3281. WCHAR originalDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  3282. WCHAR newDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  3283. DWORD paramInError;
  3284. LPWSTR renameOriginalName;
  3285. LPWSTR renameNewName;
  3286. BOOL performRename = TRUE;
  3287. BOOL freeOriginalName = FALSE;
  3288. LPWSTR nameForGuidLookup = OriginalName;
  3289. USER_INFO_0 netUI0;
  3290. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  3291. IDirectoryObject * compObj = NULL;
  3292. IADsObjectOptions * objOpts = NULL;
  3293. //
  3294. // see if we need to recover from an interrupted rename attempt. If a
  3295. // rename failed in the middle of a previous attempt, then the astute
  3296. // admin will check on the name before bringing it online (there is
  3297. // recovery code in the online path as well). If we're here (as opposed to
  3298. // going online), we need to figure out how much work was done by the last
  3299. // attempt. There will be two scenarios: 1) the rename didn't get far
  3300. // enough to rename the object in which case, we can continue or 2) it did
  3301. // rename the object but failed before the Name property could be
  3302. // stored. In that case, we can skip the rename op providing the NewName
  3303. // matches RenameNewName. If it doesn't, then we set OriginalName to
  3304. // RenameNewName so we can rename the object to NewName.
  3305. //
  3306. //
  3307. // read the rename keys from the registry. If these fail for reasons other
  3308. // than file not found, then something is wrong at the service level (like
  3309. // it's dead) so no need to log an error.
  3310. //
  3311. renameOriginalName = ResUtilGetSzValue( Resource->ResKey, PARAM_NAME__RENAMEORIGINALNAME );
  3312. if ( renameOriginalName == NULL ) {
  3313. status = GetLastError();
  3314. if ( status != ERROR_FILE_NOT_FOUND ) {
  3315. return HRESULT_FROM_WIN32( status );
  3316. }
  3317. }
  3318. renameNewName = ResUtilGetSzValue( Resource->ResKey, PARAM_NAME__RENAMENEWNAME );
  3319. if ( renameNewName == NULL ) {
  3320. status = GetLastError();
  3321. if ( status != ERROR_FILE_NOT_FOUND ) {
  3322. return HRESULT_FROM_WIN32( status );
  3323. }
  3324. }
  3325. if ( renameNewName && renameOriginalName ) {
  3326. BOOL originalNameObjectExists;
  3327. BOOL newNameObjectExists;
  3328. //
  3329. // since both keys exist, we don't know if the object rename
  3330. // happened. Go find it now.
  3331. //
  3332. // ISSUE - 11/11/01. charlwi (Charlie Wickham)
  3333. //
  3334. // We've also constrained the searching to the creating DC. If this DC
  3335. // were demoted before recovery could take place, then the object will
  3336. // never be found. For Windows Server 2003, disabling and re-enabling Kerb
  3337. // support will fix this since it will find the object on another DC and that
  3338. // will become the creating DC. When there is no option to disable
  3339. // Kerb, this routine will have to be fixed to look on other DCs.
  3340. //
  3341. hr = IsComputerObjectInDS( resourceHandle,
  3342. Resource->NodeName,
  3343. renameOriginalName,
  3344. Resource->Params.CreatingDC,
  3345. &originalNameObjectExists,
  3346. NULL, // FQDN not needed
  3347. NULL); // Hosting DC Name not needed
  3348. if ( SUCCEEDED( hr )) {
  3349. if ( !originalNameObjectExists ) {
  3350. //
  3351. // couldn't find an object with the original (old) name; try
  3352. // with the new name.
  3353. //
  3354. hr = IsComputerObjectInDS( resourceHandle,
  3355. Resource->NodeName,
  3356. renameNewName,
  3357. Resource->Params.CreatingDC,
  3358. &newNameObjectExists,
  3359. NULL, // FQDN not needed
  3360. NULL); // Hosting DC Name not needed
  3361. if ( SUCCEEDED( hr )) {
  3362. if ( newNameObjectExists ) {
  3363. (NetNameLogEvent)(resourceHandle,
  3364. LOG_INFORMATION,
  3365. L"Recovering computer account %1!ws! on DC %2!ws! from "
  3366. L"interrupted rename operation.\n",
  3367. renameNewName,
  3368. Resource->Params.CreatingDC);
  3369. nameForGuidLookup = renameNewName;
  3370. //
  3371. // now check if NewName is different from the old
  3372. // "NewName". If so, then set OriginalName to the old
  3373. // "NewName" and continue with the rename op
  3374. //
  3375. if ( ClRtlStrICmp( renameNewName, NewName ) != 0 ) {
  3376. OriginalName = ResUtilDupString( renameNewName );
  3377. if ( OriginalName == NULL ) {
  3378. hr = HRESULT_FROM_WIN32 ( GetLastError());
  3379. } else {
  3380. freeOriginalName = TRUE;
  3381. }
  3382. }
  3383. else {
  3384. performRename = FALSE;
  3385. }
  3386. }
  3387. else {
  3388. //
  3389. // this is very bad: can't find the object by either name.
  3390. //
  3391. (NetNameLogEvent)(resourceHandle,
  3392. LOG_ERROR,
  3393. L"The computer account for this resource could not be "
  3394. L"found on DC %1!ws! during rename recovery. The original "
  3395. L"name (%2!ws!) was being renamed to %3!ws! and "
  3396. L"a computer account by either name could not be found. "
  3397. L"The resource cannot go online until the object is recreated. This "
  3398. L"can be accomplished by disabling and re-enabling Kerberos "
  3399. L"Authentication for this resource.\n",
  3400. Resource->Params.CreatingDC,
  3401. renameOriginalName,
  3402. renameNewName);
  3403. hr = HRESULT_FROM_WIN32( ERROR_DS_NO_SUCH_OBJECT );
  3404. }
  3405. }
  3406. else {
  3407. (NetNameLogEvent)(resourceHandle,
  3408. LOG_ERROR,
  3409. L"AD search for computer account %1!ws! on DC %2!ws! failed "
  3410. L"during rename recovery operation - status %2!08X!.\n",
  3411. renameNewName,
  3412. Resource->Params.CreatingDC,
  3413. hr);
  3414. }
  3415. }
  3416. else {
  3417. //
  3418. // object with original name exists. Nothing to do.
  3419. //
  3420. nameForGuidLookup = renameOriginalName;
  3421. }
  3422. }
  3423. else {
  3424. (NetNameLogEvent)(resourceHandle,
  3425. LOG_ERROR,
  3426. L"AD search for computer account %1!ws! on DC %2!ws! failed "
  3427. L"during rename recovery operation - status %2!08X!.\n",
  3428. renameOriginalName,
  3429. Resource->Params.CreatingDC,
  3430. hr);
  3431. }
  3432. }
  3433. else if ( renameNewName && renameOriginalName == NULL ) {
  3434. //
  3435. // could be we made it past writing the Name property but not far
  3436. // enough to delete the RenameNewName key. If the names are aligned,
  3437. // then nothing to do.
  3438. //
  3439. if ( ClRtlStrICmp( renameNewName, OriginalName ) != 0 ) {
  3440. (NetNameLogEvent)(resourceHandle,
  3441. LOG_INFORMATION,
  3442. L"Found partially renamed computer account %1!ws! on "
  3443. L"DC %2!ws!. Attempting recovery.\n",
  3444. renameNewName,
  3445. Resource->Params.CreatingDC);
  3446. nameForGuidLookup = renameNewName;
  3447. //
  3448. // now we need to check if NewName equals RenameNewName. If they don't
  3449. // then we change OriginalName to RenameNewName and proceed with the
  3450. // rename.
  3451. //
  3452. if ( ClRtlStrICmp( renameNewName, NewName ) != 0 ) {
  3453. OriginalName = ResUtilDupString( renameNewName );
  3454. if ( OriginalName == NULL ) {
  3455. hr = HRESULT_FROM_WIN32 ( GetLastError());
  3456. } else {
  3457. freeOriginalName = TRUE;
  3458. }
  3459. }
  3460. else {
  3461. performRename = FALSE;
  3462. }
  3463. }
  3464. }
  3465. if ( FAILED( hr )) {
  3466. goto cleanup;
  3467. }
  3468. //
  3469. // get the CO GUID if necessary
  3470. //
  3471. if ( Resource->ObjectGUID == NULL ) {
  3472. //
  3473. // GUID isn't set in param block if we haven't been online yet. do it
  3474. // here since we need it to get a computer object pointer.
  3475. //
  3476. (NetNameLogEvent)(resourceHandle,
  3477. LOG_INFORMATION,
  3478. L"Getting Computer Object GUID.\n");
  3479. hr = GetComputerObjectGuid( Resource, nameForGuidLookup );
  3480. if ( FAILED( hr )) {
  3481. (NetNameLogEvent)(resourceHandle,
  3482. LOG_ERROR,
  3483. L"Failed to find the object GUID for computer account %1!ws! "
  3484. L"(status %2!08X!). This error can occur if the Network Name "
  3485. L"was changed and the name change of the corresponding computer "
  3486. L"account hasn't replicated to other domain controllers. The rename of "
  3487. L"the computer account occurs on the DC named in the CreatingDC "
  3488. L"property. Ensure that this DC is availalbe and attempt the "
  3489. L"rename again.\n",
  3490. nameForGuidLookup,
  3491. hr);
  3492. goto cleanup;
  3493. }
  3494. }
  3495. //
  3496. // get a IDirectoryObject pointer on the creating DC; if that fails, then
  3497. // try to find the object on another DC and update the CreatingDC
  3498. // property.
  3499. //
  3500. hr = GetComputerObjectViaGUID( Resource->ObjectGUID, Resource->Params.CreatingDC, &compObj );
  3501. if ( FAILED( hr )) {
  3502. (NetNameLogEvent)(resourceHandle,
  3503. LOG_WARNING,
  3504. L"Failed to find computer account %1!ws! on DC %2!ws! (status %3!08X!). "
  3505. L"Trying on another DC.\n",
  3506. OriginalName,
  3507. Resource->Params.CreatingDC,
  3508. hr);
  3509. hr = GetComputerObjectViaGUID( Resource->ObjectGUID, NULL, &compObj );
  3510. if ( SUCCEEDED( hr )) {
  3511. LPWSTR attributeNames[1] = { L"cn" };
  3512. DWORD numAttributes = COUNT_OF( attributeNames );
  3513. DWORD countOfAttrs;;
  3514. ADS_ATTR_INFO * attributeInfo = NULL;
  3515. //
  3516. // Check that the common name of the object matches the current
  3517. // network name of the resource. If the name is changed on DC A
  3518. // which becomes unavailable soon thereafter and doesn't replicate
  3519. // the change to DC B and DC B becomes available before DC A and
  3520. // another rename operation is attempted then we can get a pointer
  3521. // to the object since the GUID hasn't changed but the name of the
  3522. // object on DC A and the network name won't match. Disallow the
  3523. // rename in that case.
  3524. //
  3525. hr = compObj->GetObjectAttributes(attributeNames,
  3526. numAttributes,
  3527. &attributeInfo,
  3528. &countOfAttrs );
  3529. if ( SUCCEEDED( hr )) {
  3530. if ( countOfAttrs == 1 ) {
  3531. if ( ClRtlStrICmp( OriginalName, attributeInfo->pADsValues->CaseIgnoreString ) != 0 ) {
  3532. (NetNameLogEvent)(resourceHandle,
  3533. LOG_ERROR,
  3534. L"The Network Name (%1!ws!) does not match the name of the "
  3535. L"corresponding computer account in AD (%2!ws!). This is "
  3536. L"usually due to the name change of the computer account "
  3537. L"not replicating to other domain controllers in the domain. "
  3538. L"A subsequent rename cannot be performed until the name "
  3539. L"change has replicated to other DCs. If the Network Name "
  3540. L"has not been changed, then it is out synch with the name of "
  3541. L"the computer account. This problem can be fixed by renaming "
  3542. L"the computer account and letting that change replicate "
  3543. L"to other DCs. Once that has occurred, the Network Name for this "
  3544. L"resource can be changed.\n",
  3545. OriginalName,
  3546. attributeInfo->pADsValues->CaseIgnoreString);
  3547. ClusResLogSystemEventByKey2(Resource->ResKey,
  3548. LOG_CRITICAL,
  3549. RES_NETNAME_RENAME_OUT_OF_SYNCH_WITH_COMPOBJ,
  3550. OriginalName,
  3551. attributeInfo->pADsValues->CaseIgnoreString);
  3552. hr = HRESULT_FROM_WIN32( ERROR_CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME );
  3553. }
  3554. } else {
  3555. (NetNameLogEvent)(resourceHandle,
  3556. LOG_ERROR,
  3557. L"The cn attribute is missing for computer account %1!ws!.\n",
  3558. attributeInfo->pADsValues->CaseIgnoreString);
  3559. hr = E_ADS_PROPERTY_NOT_FOUND;
  3560. }
  3561. FreeADsMem( attributeInfo );
  3562. } else {
  3563. (NetNameLogEvent)(resourceHandle,
  3564. LOG_ERROR,
  3565. L"Failed to get information on computer account %1!ws! on DC %2!ws! "
  3566. L"(status %3!08X!). Trying on another DC.\n",
  3567. OriginalName,
  3568. Resource->Params.CreatingDC,
  3569. hr);
  3570. }
  3571. if ( FAILED( hr )) {
  3572. goto cleanup;
  3573. }
  3574. hr = compObj->QueryInterface( IID_IADsObjectOptions, (void **)&objOpts );
  3575. if ( SUCCEEDED( hr )) {
  3576. VARIANT serverName;
  3577. VariantInit( &serverName );
  3578. hr = objOpts->GetOption( ADS_OPTION_SERVERNAME, &serverName );
  3579. if ( SUCCEEDED( hr )) {
  3580. DWORD dcNameChars;
  3581. LPWSTR creatingDC;
  3582. //
  3583. // CreatingDC property needs leading backslashes; add them if they
  3584. // are not there.
  3585. //
  3586. dcNameChars = wcslen( serverName.bstrVal ) + 1;
  3587. if ( serverName.bstrVal[0] != L'\\' && serverName.bstrVal[1] != L'\\' ) {
  3588. dcNameChars += 2;
  3589. }
  3590. creatingDC = (LPWSTR)LocalAlloc( LMEM_FIXED, dcNameChars * sizeof( WCHAR ));
  3591. if ( creatingDC ) {
  3592. PWCHAR p = creatingDC;
  3593. if ( serverName.bstrVal[0] != L'\\' && serverName.bstrVal[1] != L'\\' ) {
  3594. *p++ = L'\\';
  3595. *p++ = L'\\';
  3596. }
  3597. wcscpy( p, serverName.bstrVal );
  3598. status = ResUtilSetSzValue(Resource->ParametersKey,
  3599. PARAM_NAME__CREATING_DC,
  3600. creatingDC,
  3601. NULL);
  3602. if ( status != ERROR_SUCCESS ) {
  3603. (NetNameLogEvent)(resourceHandle,
  3604. LOG_ERROR,
  3605. L"Unable to set CreatingDC property, status %1!u!.\n",
  3606. status);
  3607. hr = HRESULT_FROM_WIN32( status );
  3608. LocalFree( creatingDC );
  3609. } else {
  3610. LocalFree( Resource->Params.CreatingDC );
  3611. Resource->Params.CreatingDC = creatingDC;
  3612. }
  3613. }
  3614. else {
  3615. hr = HRESULT_FROM_WIN32( GetLastError());
  3616. (NetNameLogEvent)(resourceHandle,
  3617. LOG_ERROR,
  3618. L"Failed to allocate memory for rename operation. status %1!08X!\n",
  3619. hr);
  3620. }
  3621. VariantClear( &serverName );
  3622. }
  3623. else {
  3624. (NetNameLogEvent)(resourceHandle,
  3625. LOG_ERROR,
  3626. L"Failed to get name of DC for computer account %1!ws!. status %2!08X!\n",
  3627. OriginalName,
  3628. hr);
  3629. }
  3630. }
  3631. else {
  3632. (NetNameLogEvent)(resourceHandle,
  3633. LOG_ERROR,
  3634. L"Failed to get object options for computer account %1!ws!. "
  3635. L"status %2!08X!\n",
  3636. OriginalName,
  3637. hr);
  3638. }
  3639. }
  3640. else {
  3641. (NetNameLogEvent)(resourceHandle,
  3642. LOG_ERROR,
  3643. L"Failed to find computer account %1!ws! in Active Directory "
  3644. L"(status %2!08X!).\n",
  3645. OriginalName,
  3646. hr);
  3647. }
  3648. }
  3649. if ( FAILED( hr )) {
  3650. goto cleanup;
  3651. }
  3652. //
  3653. // write the original and new names to the resource's registry area in case
  3654. // we need to recover from a resmon crash.
  3655. //
  3656. status = ClusterRegSetValue(Resource->ResKey,
  3657. PARAM_NAME__RENAMEORIGINALNAME,
  3658. REG_SZ,
  3659. (LPBYTE)OriginalName,
  3660. ( wcslen(OriginalName) + 1 ) * sizeof(WCHAR) );
  3661. if ( status != ERROR_SUCCESS ) {
  3662. hr = HRESULT_FROM_WIN32( status );
  3663. (NetNameLogEvent)(resourceHandle,
  3664. LOG_ERROR,
  3665. L"Failed to write original name to registry for rename recovery - status %1!08X!\n",
  3666. hr);
  3667. goto cleanup;
  3668. }
  3669. status = ClusterRegSetValue(Resource->ResKey,
  3670. PARAM_NAME__RENAMENEWNAME,
  3671. REG_SZ,
  3672. (LPBYTE)NewName,
  3673. ( wcslen(NewName) + 1 ) * sizeof(WCHAR) );
  3674. if ( status != ERROR_SUCCESS ) {
  3675. hr = HRESULT_FROM_WIN32( status );
  3676. (NetNameLogEvent)(resourceHandle,
  3677. LOG_ERROR,
  3678. L"Failed to write new name to registry for rename recovery - status %1!08X!\n",
  3679. hr);
  3680. goto cleanup;
  3681. }
  3682. //
  3683. // build the dollar sign names
  3684. //
  3685. originalDollarName[ COUNT_OF( originalDollarName ) - 1 ] = UNICODE_NULL;
  3686. _snwprintf( originalDollarName, COUNT_OF( originalDollarName ) - 1, L"%ws$", OriginalName );
  3687. newDollarName[ COUNT_OF( newDollarName ) - 1 ] = UNICODE_NULL;
  3688. _snwprintf( newDollarName, COUNT_OF( newDollarName ) - 1, L"%ws$", NewName );
  3689. if ( performRename ) {
  3690. //
  3691. // Since we've successfully bound to the object on a DC, we give renaming
  3692. // one chance, i.e., if this should fail, we don't try to find yet another
  3693. // DC and retry.
  3694. //
  3695. (NetNameLogEvent)(resourceHandle,
  3696. LOG_INFORMATION,
  3697. L"Attempting rename of computer account %1!ws! to %2!ws! with DC %3!ws!.\n",
  3698. OriginalName,
  3699. NewName,
  3700. Resource->Params.CreatingDC);
  3701. netUI0.usri0_name = newDollarName;
  3702. status = NetUserSetInfo( Resource->Params.CreatingDC,
  3703. originalDollarName,
  3704. 0,
  3705. (LPBYTE)&netUI0,
  3706. &paramInError);
  3707. }
  3708. if ( status == NERR_Success ) {
  3709. (NetNameLogEvent)(resourceHandle,
  3710. LOG_INFORMATION,
  3711. L"Updating DnsHostName attribute.\n");
  3712. //
  3713. // remove RenameOriginalName from the registry; we leave RenameNewName
  3714. // in place since resmon could still croak before writing the Name
  3715. // property back to the registry. Once the Name property has been
  3716. // updated, the RenameNewName value can be deleted.
  3717. //
  3718. status = ClusterRegDeleteValue(Resource->ResKey,
  3719. PARAM_NAME__RENAMEORIGINALNAME);
  3720. if ( status != ERROR_SUCCESS ) {
  3721. hr = HRESULT_FROM_WIN32( status );
  3722. (NetNameLogEvent)(resourceHandle,
  3723. LOG_ERROR,
  3724. L"Failed to delete "
  3725. PARAM_NAME__RENAMEORIGINALNAME
  3726. L" from registry - status %1!08X!\n",
  3727. hr);
  3728. goto cleanup;
  3729. }
  3730. //
  3731. // if resmon crashes now, the recovery routines will find the object
  3732. // with the new name and try to update DnsHostName then.
  3733. //
  3734. hr = AddDnsHostNameAttribute( resourceHandle, compObj, NewName, TRUE );
  3735. if ( SUCCEEDED( hr )) {
  3736. (NetNameLogEvent)(resourceHandle,
  3737. LOG_INFORMATION,
  3738. L"DnsHostName attribute update succeeded.\n");
  3739. } else {
  3740. (NetNameLogEvent)(resourceHandle,
  3741. LOG_ERROR,
  3742. L"Unable to set DnsHostName attribute for Computer account %1!ws!, "
  3743. L"status %2!08X!.\n",
  3744. NewName,
  3745. hr);
  3746. (NetNameLogEvent)(resourceHandle,
  3747. LOG_INFORMATION,
  3748. L"Attempting rename back to original name (%1!ws!) with DC %2!ws!.\n",
  3749. OriginalName,
  3750. Resource->Params.CreatingDC);
  3751. netUI0.usri0_name = originalDollarName;
  3752. status = NetUserSetInfo( Resource->Params.CreatingDC,
  3753. newDollarName,
  3754. 0,
  3755. (LPBYTE)&netUI0,
  3756. &paramInError);
  3757. if ( status == NERR_Success ) {
  3758. (NetNameLogEvent)(resourceHandle,
  3759. LOG_INFORMATION,
  3760. L"Rename back to original name (%1!ws!) succeeded.\n",
  3761. OriginalName);
  3762. }
  3763. else {
  3764. //
  3765. // since we were able to rename the object in the first place,
  3766. // I can't imagine why this would fail. If it does, it is bad
  3767. // since the name of the object is now out of synch with the
  3768. // network name.
  3769. //
  3770. // We'll log to the system event log that something bad happened.
  3771. //
  3772. hr = HRESULT_FROM_WIN32( status );
  3773. (NetNameLogEvent)(resourceHandle,
  3774. LOG_ERROR,
  3775. L"Rename back to original name (%1!ws!) with DC %2!ws! failed, status %3!08X!.\n",
  3776. OriginalName,
  3777. Resource->Params.CreatingDC,
  3778. hr);
  3779. ClusResLogSystemEventByKeyData2(Resource->ResKey,
  3780. LOG_CRITICAL,
  3781. RES_NETNAME_RENAME_RESTORE_FAILED,
  3782. sizeof( status ),
  3783. &status,
  3784. NewName,
  3785. OriginalName);
  3786. goto cleanup;
  3787. }
  3788. }
  3789. }
  3790. else {
  3791. //
  3792. // failed; log status and exit
  3793. //
  3794. hr = HRESULT_FROM_WIN32( status );
  3795. (NetNameLogEvent)(resourceHandle,
  3796. LOG_ERROR,
  3797. L"Failed to rename computer account %1!ws! to %2!ws! using DC %3!ws! "
  3798. L"(status %4!08X!).\n",
  3799. OriginalName,
  3800. NewName,
  3801. Resource->Params.CreatingDC,
  3802. hr);
  3803. goto cleanup;
  3804. }
  3805. cleanup:
  3806. if ( compObj != NULL ) {
  3807. compObj->Release();
  3808. }
  3809. if ( objOpts != NULL ) {
  3810. objOpts->Release();
  3811. }
  3812. if ( freeOriginalName ) {
  3813. LocalFree( OriginalName );
  3814. }
  3815. if ( renameNewName ) {
  3816. LocalFree( renameNewName );
  3817. }
  3818. if ( renameOriginalName ) {
  3819. LocalFree( renameOriginalName );
  3820. }
  3821. return hr;
  3822. } // RenameComputerObject
  3823. DWORD
  3824. DuplicateVSToken(
  3825. PNETNAME_RESOURCE Resource,
  3826. PCLUS_NETNAME_VS_TOKEN_INFO TokenInfo,
  3827. PHANDLE DuplicatedToken
  3828. )
  3829. /*++
  3830. Routine Description:
  3831. Duplicate the VS Token for this resource into the process identified in
  3832. the TokenInfo structure.
  3833. Arguments:
  3834. Resource - pointer to netname resource context struct
  3835. TokenInfo - pointer to struct containing info about where and how to dup
  3836. the token
  3837. DuplicatedToken - pointer to handle that receives the dup'ed token
  3838. Return Value:
  3839. Win32 error code...
  3840. --*/
  3841. {
  3842. DWORD status = ERROR_SUCCESS;
  3843. HANDLE targetProcess;
  3844. BOOL success;
  3845. RESOURCE_HANDLE resourceHandle = Resource->ResourceHandle;
  3846. //
  3847. // do some validation
  3848. //
  3849. if ( Resource->Params.RequireKerberos == 0 ) {
  3850. return ERROR_NO_TOKEN;
  3851. }
  3852. //
  3853. // do we have a otoken already?
  3854. //
  3855. if ( Resource->VSToken == NULL ) {
  3856. DWORD tokenStatus;
  3857. LPWSTR machinePwd;
  3858. DWORD pwdBufferByteLength = ((LM20_PWLEN + 1) * sizeof( WCHAR ));
  3859. WCHAR virtualDollarName[ DNS_MAX_LABEL_BUFFER_LENGTH ];
  3860. WCHAR domainName[ DNS_MAX_NAME_BUFFER_LENGTH ];
  3861. DWORD domainNameChars = COUNT_OF( domainName );
  3862. (NetNameLogEvent)(resourceHandle,
  3863. LOG_INFORMATION,
  3864. L"Getting a virtual computer account token.\n");
  3865. //
  3866. // didn't get one during online. get the password and try to log in
  3867. // the account. get a buffer to hold the machine Pwd
  3868. //
  3869. virtualDollarName[ COUNT_OF( virtualDollarName ) - 1 ] = UNICODE_NULL;
  3870. _snwprintf( virtualDollarName, COUNT_OF( virtualDollarName ) - 1, L"%ws$", Resource->Params.NetworkName );
  3871. success = GetComputerNameEx( ComputerNameDnsDomain, domainName, &domainNameChars );
  3872. if ( success ) {
  3873. machinePwd = (PWCHAR)LocalAlloc( LMEM_FIXED, pwdBufferByteLength );
  3874. if ( machinePwd != NULL ) {
  3875. //
  3876. // Extract the password from the ResourceData property.
  3877. //
  3878. status = DecryptNNResourceData(Resource,
  3879. Resource->Params.ResourceData,
  3880. Resource->ResDataSize,
  3881. machinePwd);
  3882. if ( status == ERROR_SUCCESS ) {
  3883. //
  3884. // get the token
  3885. //
  3886. status = NNLogonUser(resourceHandle,
  3887. virtualDollarName,
  3888. domainName,
  3889. machinePwd,
  3890. &Resource->VSToken,
  3891. &tokenStatus);
  3892. if ( status == ERROR_SUCCESS ) {
  3893. if ( tokenStatus != ERROR_SUCCESS ) {
  3894. (NetNameLogEvent)(resourceHandle,
  3895. LOG_ERROR,
  3896. L"Unable to get token for computer account - status %1!u!\n",
  3897. tokenStatus);
  3898. status = tokenStatus;
  3899. }
  3900. }
  3901. else {
  3902. (NetNameLogEvent)(resourceHandle,
  3903. LOG_WARNING,
  3904. L"Couldn't log the computer account into the domain. status 0x%1!08X!\n",
  3905. status);
  3906. }
  3907. } // was able to decrypt the password
  3908. else {
  3909. (NetNameLogEvent)(resourceHandle,
  3910. LOG_ERROR,
  3911. L"Unable to decrypt resource data. status %1!u!\n",
  3912. status );
  3913. }
  3914. RtlSecureZeroMemory( machinePwd, pwdBufferByteLength );
  3915. LocalFree( machinePwd );
  3916. } // allocated buffer for password
  3917. else {
  3918. status = GetLastError();
  3919. (NetNameLogEvent)(resourceHandle,
  3920. LOG_ERROR,
  3921. L"Unable to allocate memory for resource data. status %1!u!.\n",
  3922. status);
  3923. }
  3924. } // got DNS domain name
  3925. else {
  3926. status = GetLastError();
  3927. }
  3928. if ( status != ERROR_SUCCESS ) {
  3929. return status;
  3930. }
  3931. }
  3932. //
  3933. // get a handle to the target process
  3934. //
  3935. targetProcess = OpenProcess(PROCESS_DUP_HANDLE,
  3936. FALSE, // no inherit
  3937. TokenInfo->ProcessID);
  3938. if ( targetProcess == NULL ) {
  3939. NTSTATUS ntStatus;
  3940. BOOLEAN debugWasEnabled;
  3941. DWORD openProcessStatus;
  3942. openProcessStatus = GetLastError();
  3943. //
  3944. // enable debug priv and try again
  3945. //
  3946. ntStatus = ClRtlEnableThreadPrivilege( SE_DEBUG_PRIVILEGE, &debugWasEnabled );
  3947. if ( NT_SUCCESS( ntStatus )) {
  3948. targetProcess = OpenProcess(PROCESS_DUP_HANDLE,
  3949. FALSE, // no inherit
  3950. TokenInfo->ProcessID);
  3951. if ( targetProcess == NULL ) {
  3952. openProcessStatus = GetLastError();
  3953. }
  3954. ntStatus = ClRtlRestoreThreadPrivilege( SE_DEBUG_PRIVILEGE, debugWasEnabled );
  3955. if ( !NT_SUCCESS( ntStatus )) {
  3956. (NetNameLogEvent)(resourceHandle,
  3957. LOG_ERROR,
  3958. L"Failed to disable DEBUG privilege, status %1!08X!.\n",
  3959. ntStatus);
  3960. }
  3961. }
  3962. else {
  3963. (NetNameLogEvent)(resourceHandle,
  3964. LOG_ERROR,
  3965. L"Failed to enable DEBUG privilege, status %1!08X!.\n",
  3966. ntStatus);
  3967. }
  3968. if ( targetProcess == NULL ) {
  3969. SetLastError( openProcessStatus );
  3970. }
  3971. }
  3972. if ( targetProcess != NULL ) {
  3973. DWORD options = 0;
  3974. //
  3975. // if no specific access was requested, then dup the same access that
  3976. // our impersonation token has
  3977. //
  3978. if ( TokenInfo->DesiredAccess == 0 ) {
  3979. options = DUPLICATE_SAME_ACCESS;
  3980. }
  3981. success = DuplicateHandle(GetCurrentProcess(),
  3982. Resource->VSToken,
  3983. targetProcess,
  3984. DuplicatedToken,
  3985. TokenInfo->DesiredAccess,
  3986. TokenInfo->InheritHandle,
  3987. options);
  3988. if ( !success ) {
  3989. status = GetLastError();
  3990. }
  3991. CloseHandle( targetProcess );
  3992. }
  3993. else {
  3994. status = GetLastError();
  3995. }
  3996. return status;
  3997. } // DuplicateVSToken
  3998. #ifdef PASSWORD_ROTATION
  3999. DWORD
  4000. UpdateCompObjPassword(
  4001. IN PNETNAME_RESOURCE Resource
  4002. )
  4003. /*++
  4004. Routine Description:
  4005. Description
  4006. Arguments:
  4007. None
  4008. Return Value:
  4009. None
  4010. --*/
  4011. {
  4012. return ERROR_SUCCESS;
  4013. } // UpdateCompObjPassword
  4014. #endif // PASSWORD_ROTATION