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.

3133 lines
90 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. msvpaswd.c
  5. Abstract:
  6. This file contains the MSV1_0 Authentication Package password routines.
  7. Author:
  8. Dave Hart (davehart) 12-Mar-1992
  9. Revision History:
  10. Chandana Surlu 21-Jul-96 Stolen from \\kernel\razzle3\src\security\msv1_0\msvpaswd.c
  11. --*/
  12. #include <global.h>
  13. #include "msp.h"
  14. #include "nlp.h"
  15. #include <lmcons.h>
  16. #include <lmerr.h>
  17. #include <lmapibuf.h>
  18. #include <lmremutl.h>
  19. #include <lmwksta.h>
  20. #include "msvwow.h" // MsvConvertWOWChangePasswordBuffer()
  21. NTSTATUS
  22. MspDisableAdminsAlias (
  23. VOID
  24. )
  25. /*++
  26. Routine Description:
  27. Remove the current thread from the Administrators alias. This
  28. is accomplished by impersonating our own thread, then removing
  29. the Administrators alias membership from the impersonation
  30. token. Use MspStopImpersonating() to stop impersonating and
  31. thereby restore the thread to the Administrators alias.
  32. Arguments:
  33. None.
  34. Return Value:
  35. STATUS_SUCCESS - Indicates the service completed successfully.
  36. --*/
  37. {
  38. NTSTATUS Status;
  39. HANDLE TokenHandle = NULL;
  40. HANDLE FilteredToken = NULL;
  41. SID_IDENTIFIER_AUTHORITY IdentifierAuthority = SECURITY_NT_AUTHORITY;
  42. PSID AdminSid = NULL;
  43. SID LocalSystemSid = {SID_REVISION, 1, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID};
  44. BYTE GroupBuffer[sizeof(TOKEN_GROUPS) + sizeof(SID_AND_ATTRIBUTES)];
  45. PTOKEN_GROUPS TokenGroups = (PTOKEN_GROUPS) GroupBuffer;
  46. //
  47. // Make sure we aren't impersonating anyone else
  48. // (that will prevent the RtlImpersonateSelf() call from succeeding).
  49. //
  50. RevertToSelf();
  51. //
  52. // Open our process token so we can filter it to disable the
  53. // Administrators and LocalSystem SIDs
  54. //
  55. Status = RtlImpersonateSelf(SecurityDelegation);
  56. if (!NT_SUCCESS(Status)) {
  57. goto Cleanup;
  58. }
  59. Status = NtOpenThreadToken(
  60. NtCurrentThread(),
  61. TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY,
  62. TRUE, // open as self
  63. &TokenHandle
  64. );
  65. if ( !NT_SUCCESS(Status) ) {
  66. goto Cleanup;
  67. }
  68. //
  69. // Build the SID for the Administrators alias. The Administrators
  70. // alias SID is well known, S-1-5-32-544.
  71. //
  72. Status = RtlAllocateAndInitializeSid(
  73. &IdentifierAuthority, // SECURITY_NT_AUTHORITY (5)
  74. 2, // SubAuthorityCount
  75. SECURITY_BUILTIN_DOMAIN_RID, // 32
  76. DOMAIN_ALIAS_RID_ADMINS, // 544
  77. 0,0,0,0,0,0,
  78. &AdminSid
  79. );
  80. if ( !NT_SUCCESS(Status) ) {
  81. KdPrint(("MspDisableAdminsAlias: RtlAllocateAndInitializeSid returns %x\n",
  82. Status));
  83. goto Cleanup;
  84. }
  85. //
  86. // Disable the Administrators and LocalSystem aliases.
  87. //
  88. TokenGroups->GroupCount = 2;
  89. TokenGroups->Groups[0].Sid = AdminSid;
  90. TokenGroups->Groups[0].Attributes = 0; // SE_GROUP_ENABLED not on.
  91. TokenGroups->Groups[1].Sid = &LocalSystemSid;
  92. TokenGroups->Groups[1].Attributes = 0; // SE_GROUP_ENABLED not on.
  93. Status = NtFilterToken(
  94. TokenHandle,
  95. 0, // no flags
  96. TokenGroups,
  97. NULL, // no privileges
  98. NULL, // no restricted sids
  99. &FilteredToken
  100. );
  101. if ( !NT_SUCCESS(Status) ) {
  102. KdPrint(("MspDisableAdminsAlias: NtFilter returns %x\n",
  103. Status));
  104. goto Cleanup;
  105. }
  106. Status = NtSetInformationThread(
  107. NtCurrentThread(),
  108. ThreadImpersonationToken,
  109. &FilteredToken,
  110. sizeof(HANDLE)
  111. );
  112. if (!NT_SUCCESS(Status)) {
  113. goto Cleanup;
  114. }
  115. Cleanup:
  116. if (AdminSid) {
  117. RtlFreeSid(AdminSid);
  118. }
  119. if (TokenHandle) {
  120. NtClose(TokenHandle);
  121. }
  122. if (FilteredToken) {
  123. NtClose(FilteredToken);
  124. }
  125. return Status;
  126. }
  127. NTSTATUS
  128. MspImpersonateAnonymous(
  129. VOID
  130. )
  131. /*++
  132. Routine Description:
  133. Remove the current thread from the Administrators alias. This
  134. is accomplished by impersonating our own thread, then removing
  135. the Administrators alias membership from the impersonation
  136. token. Use RevertToSelf() to stop impersonating and
  137. thereby restore the thread to the Administrators alias.
  138. Arguments:
  139. None.
  140. Return Value:
  141. STATUS_SUCCESS - Indicates the service completed successfully.
  142. --*/
  143. {
  144. RevertToSelf();
  145. if(!ImpersonateAnonymousToken( GetCurrentThread() ))
  146. {
  147. return STATUS_CANNOT_IMPERSONATE;
  148. }
  149. return STATUS_SUCCESS;
  150. }
  151. NTSTATUS
  152. MspAddBackslashesComputerName(
  153. IN PUNICODE_STRING ComputerName,
  154. OUT PUNICODE_STRING UncComputerName
  155. )
  156. /*++
  157. Routine Description:
  158. This function makes a copy of a Computer Name, prepending backslashes
  159. if they are not already present.
  160. Arguments:
  161. ComputerName - Pointer to Computer Name without backslashes.
  162. UncComputerName - Pointer to Unicode String structure that will be
  163. initialized to reference the computerName with backslashes
  164. prepended if not already present. The Unicode Buffer will be
  165. terminated with a Unicode NULL, so that it can be passed as
  166. a parameter to routines expecting a null terminated Wide String.
  167. When this string is finished with, the caller must free its
  168. memory via RtlFreeHeap.
  169. --*/
  170. {
  171. NTSTATUS Status = STATUS_SUCCESS;
  172. BOOLEAN HasBackslashes = FALSE;
  173. BOOLEAN IsNullTerminated = FALSE;
  174. USHORT OutputNameLength;
  175. USHORT OutputNameMaximumLength;
  176. PWSTR StartBuffer = NULL;
  177. //
  178. // If the computername is NULL, a zero length string, or the name already begins with
  179. // backslashes and is wide char null terminated, just use it unmodified.
  180. //
  181. if( (!ARGUMENT_PRESENT(ComputerName)) || ComputerName->Length == 0 ) {
  182. UncComputerName->Buffer = NULL;
  183. UncComputerName->Length = 0;
  184. UncComputerName->MaximumLength = 0;
  185. goto AddBackslashesComputerNameFinish;
  186. }
  187. //
  188. // Name is not NULL or zero length. Check if name already has
  189. // backslashes and a trailing Unicode Null
  190. //
  191. OutputNameLength = ComputerName->Length + (2 * sizeof(WCHAR));
  192. OutputNameMaximumLength = OutputNameLength + sizeof(WCHAR);
  193. if ((ComputerName && ComputerName->Length >= 2 * sizeof(WCHAR)) &&
  194. (ComputerName->Buffer[0] == L'\\') &&
  195. (ComputerName->Buffer[1] == L'\\')) {
  196. HasBackslashes = TRUE;
  197. OutputNameLength -= (2 * sizeof(WCHAR));
  198. OutputNameMaximumLength -= (2 * sizeof(WCHAR));
  199. }
  200. if ((ComputerName->Length + (USHORT) sizeof(WCHAR) <= ComputerName->MaximumLength) &&
  201. (ComputerName->Buffer[ComputerName->Length/sizeof(WCHAR)] == UNICODE_NULL)) {
  202. IsNullTerminated = TRUE;
  203. }
  204. if (HasBackslashes && IsNullTerminated) {
  205. *UncComputerName = *ComputerName;
  206. goto AddBackslashesComputerNameFinish;
  207. }
  208. //
  209. // Name either does not have backslashes or is not NULL terminated.
  210. // Make a copy with leading backslashes and a wide NULL terminator.
  211. //
  212. UncComputerName->Length = OutputNameLength;
  213. UncComputerName->MaximumLength = OutputNameMaximumLength;
  214. UncComputerName->Buffer = I_NtLmAllocate(
  215. OutputNameMaximumLength
  216. );
  217. if (UncComputerName->Buffer == NULL) {
  218. KdPrint(("MspAddBackslashes...: Out of memory copying ComputerName.\n"));
  219. Status = STATUS_NO_MEMORY;
  220. goto AddBackslashesComputerNameError;
  221. }
  222. StartBuffer = UncComputerName->Buffer;
  223. if (!HasBackslashes) {
  224. UncComputerName->Buffer[0] = UncComputerName->Buffer[1] = L'\\';
  225. StartBuffer +=2;
  226. }
  227. RtlCopyMemory(
  228. StartBuffer,
  229. ComputerName->Buffer,
  230. ComputerName->Length
  231. );
  232. UncComputerName->Buffer[UncComputerName->Length / sizeof(WCHAR)] = UNICODE_NULL;
  233. AddBackslashesComputerNameFinish:
  234. return(Status);
  235. AddBackslashesComputerNameError:
  236. goto AddBackslashesComputerNameFinish;
  237. }
  238. #ifndef DONT_LOG_PASSWORD_CHANGES
  239. #include <stdio.h>
  240. HANDLE MsvPaswdLogFile = NULL;
  241. #define MSVPASWD_LOGNAME L"\\debug\\PASSWD.LOG"
  242. #define MSVPASWD_BAKNAME L"\\debug\\PASSWD.BAK"
  243. ULONG
  244. MsvPaswdInitializeLog(
  245. VOID
  246. )
  247. /*++
  248. Routine Description:
  249. Initializes the debugging log file used by DCPROMO and the dssetup apis
  250. Arguments:
  251. None
  252. Returns:
  253. ERROR_SUCCESS - Success
  254. --*/
  255. {
  256. ULONG dwErr = ERROR_SUCCESS;
  257. WCHAR LogFileName[ MAX_PATH + 1 ], BakFileName[ MAX_PATH + 1 ];
  258. if ( !GetWindowsDirectoryW( LogFileName,
  259. sizeof( LogFileName )/sizeof( WCHAR ) ) ) {
  260. dwErr = GetLastError();
  261. } else {
  262. wcscpy( BakFileName, LogFileName );
  263. wcscat( LogFileName, MSVPASWD_LOGNAME );
  264. wcscat( BakFileName, MSVPASWD_BAKNAME );
  265. //
  266. // Copy the existing (maybe) log file to a backup
  267. //
  268. //if ( CopyFile( LogFileName, BakFileName, FALSE ) == FALSE ) {
  269. //
  270. // }
  271. MsvPaswdLogFile = CreateFileW( LogFileName,
  272. GENERIC_WRITE,
  273. FILE_SHARE_READ | FILE_SHARE_WRITE,
  274. NULL,
  275. CREATE_ALWAYS,
  276. FILE_ATTRIBUTE_NORMAL,
  277. NULL );
  278. if ( MsvPaswdLogFile == INVALID_HANDLE_VALUE ) {
  279. dwErr = GetLastError();
  280. MsvPaswdLogFile = NULL;
  281. } else {
  282. if( SetFilePointer( MsvPaswdLogFile,
  283. 0, 0,
  284. FILE_END ) == 0xFFFFFFFF ) {
  285. dwErr = GetLastError();
  286. CloseHandle( MsvPaswdLogFile );
  287. MsvPaswdLogFile = NULL;
  288. }
  289. }
  290. }
  291. return( dwErr );
  292. }
  293. ULONG
  294. MsvPaswdCloseLog(
  295. VOID
  296. )
  297. /*++
  298. Routine Description:
  299. Closes the debugging log file used by DCPROMO and the dssetup apis
  300. Arguments:
  301. None
  302. Returns:
  303. ERROR_SUCCESS - Success
  304. --*/
  305. {
  306. ULONG dwErr = ERROR_SUCCESS;
  307. if ( MsvPaswdLogFile != NULL ) {
  308. CloseHandle( MsvPaswdLogFile );
  309. MsvPaswdLogFile = NULL;
  310. }
  311. return( dwErr );
  312. }
  313. //
  314. // Stolen and hacked up from netlogon code
  315. //
  316. VOID
  317. MsvPaswdDebugDumpRoutine(
  318. IN LPSTR Format,
  319. va_list arglist
  320. )
  321. {
  322. char OutputBuffer[2049];
  323. ULONG length;
  324. ULONG BytesWritten;
  325. SYSTEMTIME SystemTime;
  326. static BeginningOfLine = TRUE;
  327. int cbUsed = 0;
  328. //
  329. // If we don't have an open log file, just bail
  330. //
  331. if ( MsvPaswdLogFile == NULL ) {
  332. return;
  333. }
  334. length = 0;
  335. OutputBuffer[sizeof(OutputBuffer) - 1] = '\0';
  336. //
  337. // Handle the beginning of a new line.
  338. //
  339. //
  340. if ( BeginningOfLine ) {
  341. //
  342. // If we're writing to the debug terminal,
  343. // indicate this is a Netlogon message.
  344. //
  345. //
  346. // Put the timestamp at the begining of the line.
  347. //
  348. GetLocalTime( &SystemTime );
  349. length += (ULONG) sprintf( &OutputBuffer[length],
  350. "%02u/%02u %02u:%02u:%02u ",
  351. SystemTime.wMonth,
  352. SystemTime.wDay,
  353. SystemTime.wHour,
  354. SystemTime.wMinute,
  355. SystemTime.wSecond );
  356. }
  357. //
  358. // Put a the information requested by the caller onto the line
  359. //
  360. // save two chars of spaces for the EOLs
  361. //
  362. cbUsed = (ULONG) _vsnprintf(&OutputBuffer[length], sizeof(OutputBuffer) - length - 1 - 2, Format, arglist);
  363. if (cbUsed >= 0)
  364. {
  365. length += cbUsed;
  366. }
  367. BeginningOfLine = (length > 0 && OutputBuffer[length-1] == '\n' );
  368. if ( BeginningOfLine ) {
  369. OutputBuffer[length-1] = '\r';
  370. OutputBuffer[length] = '\n';
  371. OutputBuffer[length+1] = '\0';
  372. length++;
  373. }
  374. ASSERT( length <= sizeof( OutputBuffer ) / sizeof( CHAR ) );
  375. //
  376. // Write the debug info to the log file.
  377. //
  378. if ( !WriteFile( MsvPaswdLogFile,
  379. OutputBuffer,
  380. length,
  381. &BytesWritten,
  382. NULL ) ) {
  383. }
  384. }
  385. VOID
  386. MsvPaswdLogPrintRoutine(
  387. IN LPSTR Format,
  388. ...
  389. )
  390. {
  391. va_list arglist;
  392. va_start(arglist, Format);
  393. MsvPaswdDebugDumpRoutine( Format, arglist );
  394. va_end(arglist);
  395. }
  396. ULONG
  397. MsvPaswdSetAndClearLog(
  398. VOID
  399. )
  400. /*++
  401. Routine Description:
  402. Flushes the log and seeks to the end of the file
  403. Arguments:
  404. None
  405. Returns:
  406. ERROR_SUCCESS - Success
  407. --*/
  408. {
  409. ULONG dwErr = ERROR_SUCCESS;
  410. if ( MsvPaswdLogFile != NULL ) {
  411. if( FlushFileBuffers( MsvPaswdLogFile ) == FALSE ) {
  412. dwErr = GetLastError();
  413. }
  414. }
  415. return( dwErr );
  416. }
  417. #endif // DONT_LOG_PASSWORD_CHANGES
  418. NTSTATUS
  419. MspChangePasswordSam(
  420. IN PUNICODE_STRING UncComputerName,
  421. IN PUNICODE_STRING UserName,
  422. IN PUNICODE_STRING OldPassword,
  423. IN PUNICODE_STRING NewPassword,
  424. IN PLSA_CLIENT_REQUEST ClientRequest,
  425. IN BOOLEAN Impersonating,
  426. OUT PDOMAIN_PASSWORD_INFORMATION *DomainPasswordInfo,
  427. OUT PPOLICY_PRIMARY_DOMAIN_INFO *PrimaryDomainInfo OPTIONAL,
  428. OUT PBOOLEAN Authoritative
  429. )
  430. /*++
  431. Routine Description:
  432. This routine is called by MspChangePassword to change the password
  433. on a Windows NT machine.
  434. Arguments:
  435. UncComputerName - Name of the target machine. This name must begin with
  436. two backslashes.
  437. UserName - Name of the user to change password for.
  438. OldPassword - Plaintext current password.
  439. NewPassword - Plaintext replacement password.
  440. ClientRequest - Is a pointer to an opaque data structure
  441. representing the client's request.
  442. DomainPasswordInfo - Password restriction information (returned only if
  443. status is STATUS_PASSWORD_RESTRICTION).
  444. PrimaryDomainInfo - DomainNameInformation (returned only if status is
  445. STATUS_BACKUP_CONTROLLER).
  446. Authoritative - The failure was authoritative and no retries should be
  447. made.
  448. Return Value:
  449. STATUS_SUCCESS - Indicates the service completed successfully.
  450. ...
  451. --*/
  452. {
  453. NTSTATUS Status;
  454. OBJECT_ATTRIBUTES ObjectAttributes;
  455. SECURITY_QUALITY_OF_SERVICE SecurityQos;
  456. SAM_HANDLE SamHandle = NULL;
  457. SAM_HANDLE DomainHandle = NULL;
  458. LSA_HANDLE LSAPolicyHandle = NULL;
  459. OBJECT_ATTRIBUTES LSAObjectAttributes;
  460. PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL;
  461. BOOLEAN ImpersonatingAnonymous = FALSE;
  462. BOOLEAN RetryAnonymous = FALSE;
  463. UNREFERENCED_PARAMETER(ClientRequest);
  464. //
  465. // Assume all failures are authoritative
  466. //
  467. *Authoritative = TRUE;
  468. //
  469. // If we're impersonating (ie, winlogon impersonated its caller before calling us),
  470. // impersonate again. This allows us to get the name of the caller for auditing.
  471. //
  472. if ( Impersonating ) {
  473. Status = Lsa.ImpersonateClient();
  474. } else {
  475. UNICODE_STRING ComputerName;
  476. BOOLEAN AvoidAnonymous = FALSE;
  477. BOOLEAN LocalMachine = FALSE;
  478. //
  479. // Since the System context is a member of the Administrators alias,
  480. // when we connect with the local SAM we come in as an Administrator.
  481. // (When it's remote, we go over the null session and so have very
  482. // low access). We don't want to be an Administrator because that
  483. // would allow the user to change the password on an account whose
  484. // ACL prohibits the user from changing the password. So we'll
  485. // temporarily impersonate ourself and disable the Administrators
  486. // alias in the impersonation token.
  487. //
  488. //
  489. // find out if the referenced computer is the local machine.
  490. //
  491. ComputerName = *UncComputerName;
  492. if( ComputerName.Length > 4 &&
  493. ComputerName.Buffer[0] == L'\\' &&
  494. ComputerName.Buffer[1] == L'\\' )
  495. {
  496. ComputerName.Buffer += 2;
  497. ComputerName.Length -= 2 * sizeof(WCHAR);
  498. }
  499. if( NlpSamDomainName.Buffer )
  500. {
  501. LocalMachine = RtlEqualUnicodeString(
  502. &ComputerName,
  503. &NlpSamDomainName,
  504. TRUE
  505. );
  506. }
  507. if( !LocalMachine )
  508. {
  509. RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
  510. LocalMachine = RtlEqualUnicodeString(
  511. &ComputerName,
  512. &NtLmGlobalUnicodeComputerNameString,
  513. TRUE
  514. );
  515. RtlReleaseResource(&NtLmGlobalCritSect);
  516. }
  517. //
  518. // Don't impersonateAnonymous if BLANKPWD flag is set
  519. // AND the change is for the local machine.
  520. //
  521. if( (BOOLEAN) ((NtLmCheckProcessOption( MSV1_0_OPTION_ALLOW_BLANK_PASSWORD ) & MSV1_0_OPTION_ALLOW_BLANK_PASSWORD) != 0))
  522. {
  523. AvoidAnonymous = LocalMachine;
  524. }
  525. if( AvoidAnonymous )
  526. {
  527. Status = STATUS_SUCCESS;
  528. ImpersonatingAnonymous = FALSE;
  529. //
  530. // allow a retry as anonymous on failure.
  531. //
  532. RetryAnonymous = TRUE;
  533. } else {
  534. //
  535. // if the call is against the local machine, impersonate anonymous
  536. // otherwise, impersonate a crippled SYSTEM token, so the call
  537. // leaves the box as SYSTEM/machine creds.
  538. //
  539. if( !LocalMachine )
  540. {
  541. Status = MspDisableAdminsAlias ();
  542. RetryAnonymous = TRUE;
  543. } else {
  544. Status = MspImpersonateAnonymous();
  545. }
  546. ImpersonatingAnonymous = TRUE;
  547. }
  548. }
  549. if (!NT_SUCCESS( Status )) {
  550. goto Cleanup;
  551. }
  552. try
  553. {
  554. Status = SamChangePasswordUser2(
  555. UncComputerName,
  556. UserName,
  557. OldPassword,
  558. NewPassword
  559. );
  560. }
  561. except (EXCEPTION_EXECUTE_HANDLER)
  562. {
  563. Status = GetExceptionCode();
  564. }
  565. MsvPaswdLogPrint(("SamChangePasswordUser2 on machine %wZ for user %wZ returned 0x%x\n",
  566. UncComputerName,
  567. UserName,
  568. Status
  569. ));
  570. if ( !NT_SUCCESS(Status) ) {
  571. #ifdef COMPILED_BY_DEVELOPER
  572. KdPrint(("MspChangePasswordSam: SamChangePasswordUser2(%wZ) failed, status %x\n",
  573. UncComputerName, Status));
  574. #endif // COMPILED_BY_DEVELOPER
  575. //
  576. // If we failed to connect and we were impersonating a client
  577. // then we may want to try again using the NULL session.
  578. // Only try this if we found a server last try. Otherwise,
  579. // we'll subject our user to another long timeout.
  580. //
  581. if (( Impersonating || RetryAnonymous ) &&
  582. ( Status != STATUS_WRONG_PASSWORD ) &&
  583. ( Status != STATUS_PASSWORD_RESTRICTION ) &&
  584. ( Status != STATUS_ACCOUNT_RESTRICTION ) &&
  585. ( Status != RPC_NT_SERVER_UNAVAILABLE) &&
  586. ( Status != STATUS_INVALID_DOMAIN_ROLE) ) {
  587. Status = MspImpersonateAnonymous();
  588. if (!NT_SUCCESS(Status)) {
  589. goto Cleanup;
  590. }
  591. ImpersonatingAnonymous = TRUE;
  592. Status = SamChangePasswordUser2(
  593. UncComputerName,
  594. UserName,
  595. OldPassword,
  596. NewPassword
  597. );
  598. MsvPaswdLogPrint(("SamChangePasswordUser2 retry on machine %wZ for user %wZ returned 0x%x\n",
  599. UncComputerName,
  600. UserName,
  601. Status
  602. ));
  603. #ifdef COMPILED_BY_DEVELOPER
  604. if ( !NT_SUCCESS(Status) ) {
  605. KdPrint(("MspChangePasswordSam: SamChangePasswordUser2(%wZ) (2nd attempt) failed, status %x\n",
  606. UncComputerName, Status));
  607. }
  608. #endif // COMPILED_BY_DEVELOPER
  609. }
  610. }
  611. //
  612. // if we are impersonating Anonymous, RevertToSelf, so the password policy
  613. // fetch attempt occurs using machine/system creds.
  614. //
  615. if( ImpersonatingAnonymous )
  616. {
  617. RevertToSelf();
  618. }
  619. if ( !NT_SUCCESS(Status) ) {
  620. #ifdef COMPILED_BY_DEVELOPER
  621. KdPrint(("MspChangePasswordSam: Cannot change password for %wZ, status %x\n",
  622. UserName, Status));
  623. #endif // COMPILED_BY_DEVELOPER
  624. if (Status == RPC_NT_SERVER_UNAVAILABLE ||
  625. Status == RPC_S_SERVER_UNAVAILABLE ) {
  626. Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
  627. } else if (Status == STATUS_PASSWORD_RESTRICTION) {
  628. //
  629. // don't whack the original status code.
  630. //
  631. NTSTATUS TempStatus;
  632. //
  633. // Get the password restrictions for this domain and return them
  634. //
  635. //
  636. // Get the SID of the account domain from LSA
  637. //
  638. InitializeObjectAttributes( &LSAObjectAttributes,
  639. NULL, // Name
  640. 0, // Attributes
  641. NULL, // Root
  642. NULL ); // Security Descriptor
  643. TempStatus = LsaOpenPolicy( UncComputerName,
  644. &LSAObjectAttributes,
  645. POLICY_VIEW_LOCAL_INFORMATION,
  646. &LSAPolicyHandle );
  647. if( !NT_SUCCESS(TempStatus) ) {
  648. KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
  649. UncComputerName, TempStatus));
  650. LSAPolicyHandle = NULL;
  651. goto Cleanup;
  652. }
  653. TempStatus = LsaQueryInformationPolicy(
  654. LSAPolicyHandle,
  655. PolicyAccountDomainInformation,
  656. (PVOID *) &AccountDomainInfo );
  657. if( !NT_SUCCESS(TempStatus) ) {
  658. KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
  659. UncComputerName, TempStatus));
  660. AccountDomainInfo = NULL;
  661. goto Cleanup;
  662. }
  663. //
  664. // Setup ObjectAttributes for SamConnect call.
  665. //
  666. InitializeObjectAttributes(&ObjectAttributes, NULL, 0, 0, NULL);
  667. ObjectAttributes.SecurityQualityOfService = &SecurityQos;
  668. SecurityQos.Length = sizeof(SecurityQos);
  669. SecurityQos.ImpersonationLevel = SecurityIdentification;
  670. SecurityQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
  671. SecurityQos.EffectiveOnly = FALSE;
  672. TempStatus = SamConnect(
  673. UncComputerName,
  674. &SamHandle,
  675. SAM_SERVER_LOOKUP_DOMAIN,
  676. &ObjectAttributes
  677. );
  678. if ( !NT_SUCCESS(TempStatus) ) {
  679. KdPrint(("MspChangePasswordSam: Cannot open sam on %wZ, status %x\n",
  680. UncComputerName, TempStatus));
  681. DomainHandle = NULL;
  682. goto Cleanup;
  683. }
  684. //
  685. // Open the Account domain in SAM.
  686. //
  687. TempStatus = SamOpenDomain(
  688. SamHandle,
  689. DOMAIN_READ_PASSWORD_PARAMETERS,
  690. AccountDomainInfo->DomainSid,
  691. &DomainHandle
  692. );
  693. if ( !NT_SUCCESS(TempStatus) ) {
  694. KdPrint(("MspChangePasswordSam: Cannot open domain on %wZ, status %x\n",
  695. UncComputerName, TempStatus));
  696. DomainHandle = NULL;
  697. goto Cleanup;
  698. }
  699. TempStatus = SamQueryInformationDomain(
  700. DomainHandle,
  701. DomainPasswordInformation,
  702. (PVOID *)DomainPasswordInfo );
  703. if (!NT_SUCCESS(TempStatus)) {
  704. KdPrint(("MspChangePasswordSam: Cannot queryinformationdomain on %wZ, status %x\n",
  705. UncComputerName, TempStatus));
  706. *DomainPasswordInfo = NULL;
  707. } else {
  708. Status = STATUS_PASSWORD_RESTRICTION;
  709. }
  710. }
  711. goto Cleanup;
  712. }
  713. Cleanup:
  714. //
  715. // If the only problem is that this is a BDC,
  716. // Return the domain name back to the caller.
  717. //
  718. if ( (Status == STATUS_BACKUP_CONTROLLER ||
  719. Status == STATUS_INVALID_DOMAIN_ROLE) &&
  720. PrimaryDomainInfo != NULL ) {
  721. NTSTATUS TempStatus;
  722. //
  723. // Open the LSA if we haven't already.
  724. //
  725. if (LSAPolicyHandle == NULL) {
  726. InitializeObjectAttributes( &LSAObjectAttributes,
  727. NULL, // Name
  728. 0, // Attributes
  729. NULL, // Root
  730. NULL ); // Security Descriptor
  731. TempStatus = LsaOpenPolicy( UncComputerName,
  732. &LSAObjectAttributes,
  733. POLICY_VIEW_LOCAL_INFORMATION,
  734. &LSAPolicyHandle );
  735. if( !NT_SUCCESS(TempStatus) ) {
  736. KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
  737. UncComputerName, TempStatus));
  738. LSAPolicyHandle = NULL;
  739. }
  740. }
  741. if (LSAPolicyHandle != NULL) {
  742. TempStatus = LsaQueryInformationPolicy(
  743. LSAPolicyHandle,
  744. PolicyPrimaryDomainInformation,
  745. (PVOID *) PrimaryDomainInfo );
  746. if( !NT_SUCCESS(TempStatus) ) {
  747. KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
  748. UncComputerName, TempStatus));
  749. *PrimaryDomainInfo = NULL;
  750. #ifdef COMPILED_BY_DEVELOPER
  751. } else {
  752. KdPrint(("MspChangePasswordSam: %wZ is really a BDC in domain %wZ\n",
  753. UncComputerName, &(*PrimaryDomainInfo)->Name));
  754. #endif // COMPILED_BY_DEVELOPER
  755. }
  756. }
  757. Status = STATUS_BACKUP_CONTROLLER;
  758. }
  759. //
  760. // Check for non-authoritative failures
  761. //
  762. if (( Status != STATUS_ACCESS_DENIED) &&
  763. ( Status != STATUS_WRONG_PASSWORD ) &&
  764. ( Status != STATUS_NO_SUCH_USER ) &&
  765. ( Status != STATUS_PASSWORD_RESTRICTION ) &&
  766. ( Status != STATUS_ACCOUNT_RESTRICTION ) &&
  767. ( Status != STATUS_INVALID_DOMAIN_ROLE ) &&
  768. ( Status != STATUS_ACCOUNT_LOCKED_OUT ) ) {
  769. *Authoritative = FALSE;
  770. }
  771. //
  772. // Stop impersonating.
  773. //
  774. RevertToSelf();
  775. //
  776. // Free Locally used resources
  777. //
  778. if (SamHandle) {
  779. SamCloseHandle(SamHandle);
  780. }
  781. if (DomainHandle) {
  782. SamCloseHandle(DomainHandle);
  783. }
  784. if ( LSAPolicyHandle != NULL ) {
  785. LsaClose( LSAPolicyHandle );
  786. }
  787. if ( AccountDomainInfo != NULL ) {
  788. (VOID) LsaFreeMemory( AccountDomainInfo );
  789. }
  790. return Status;
  791. }
  792. NTSTATUS
  793. MspChangePasswordDownlevel(
  794. IN PUNICODE_STRING UncComputerName,
  795. IN PUNICODE_STRING UserNameU,
  796. IN PUNICODE_STRING OldPasswordU,
  797. IN PUNICODE_STRING NewPasswordU,
  798. OUT PBOOLEAN Authoritative
  799. )
  800. /*++
  801. Routine Description:
  802. This routine is called by MspChangePassword to change the password
  803. on an OS/2 User-level server. First we try sending an encrypted
  804. request to the server, failing that we fall back on plaintext.
  805. Arguments:
  806. UncComputerName - Pointer to Unicode String containing the Name of the
  807. target machine. This name must begin with two backslashes and
  808. must be null terminated.
  809. UserNameU - Name of the user to change password for.
  810. OldPasswordU - Plaintext current password.
  811. NewPasswordU - Plaintext replacement password.
  812. Authoritative - If the attempt failed with an error that would
  813. otherwise cause the password attempt to fail, this flag, if false,
  814. indicates that the error was not authoritative and the attempt
  815. should proceed.
  816. Return Value:
  817. STATUS_SUCCESS - Indicates the service completed successfully.
  818. --*/
  819. {
  820. NTSTATUS Status;
  821. NET_API_STATUS NetStatus;
  822. DWORD Length;
  823. LPWSTR UserName = NULL;
  824. LPWSTR OldPassword = NULL;
  825. LPWSTR NewPassword = NULL;
  826. *Authoritative = TRUE;
  827. //
  828. // Convert UserName from UNICODE_STRING to null-terminated wide string
  829. // for use by RxNetUserPasswordSet.
  830. //
  831. Length = UserNameU->Length;
  832. UserName = I_NtLmAllocate(
  833. Length + sizeof(TCHAR)
  834. );
  835. if ( NULL == UserName ) {
  836. Status = STATUS_NO_MEMORY;
  837. goto Cleanup;
  838. }
  839. RtlCopyMemory( UserName, UserNameU->Buffer, Length );
  840. UserName[ Length / sizeof(TCHAR) ] = 0;
  841. //
  842. // Convert OldPassword from UNICODE_STRING to null-terminated wide string.
  843. //
  844. Length = OldPasswordU->Length;
  845. OldPassword = I_NtLmAllocate( Length + sizeof(TCHAR) );
  846. if ( NULL == OldPassword ) {
  847. Status = STATUS_NO_MEMORY;
  848. goto Cleanup;
  849. }
  850. RtlCopyMemory( OldPassword, OldPasswordU->Buffer, Length );
  851. OldPassword[ Length / sizeof(TCHAR) ] = 0;
  852. //
  853. // Convert NewPassword from UNICODE_STRING to null-terminated wide string.
  854. //
  855. Length = NewPasswordU->Length;
  856. NewPassword = I_NtLmAllocate( Length + sizeof(TCHAR) );
  857. if ( NULL == NewPassword ) {
  858. Status = STATUS_NO_MEMORY;
  859. goto Cleanup;
  860. }
  861. RtlCopyMemory( NewPassword, NewPasswordU->Buffer, Length );
  862. NewPassword[ Length / sizeof(TCHAR) ] = 0;
  863. #ifdef COMPILED_BY_DEVELOPER
  864. KdPrint(("MSV1_0: Changing password on downlevel server:\n"
  865. "\tUncComputerName: %wZ\n"
  866. "\tUserName: %ws\n"
  867. "\tOldPassword: %ws\n"
  868. "\tNewPassword: %ws\n",
  869. UncComputerName,
  870. UserName,
  871. OldPassword,
  872. NewPassword
  873. ));
  874. #endif // COMPILED_BY_DEVELOPER
  875. //
  876. // Attempt to change password on downlevel server.
  877. //
  878. NetStatus = RxNetUserPasswordSet(
  879. UncComputerName->Buffer,
  880. UserName,
  881. OldPassword,
  882. NewPassword);
  883. MsvPaswdLogPrint(("RxNetUserPasswordSet on machine %ws for user %ws returned 0x%x\n",
  884. UncComputerName->Buffer,
  885. UserName,
  886. NetStatus
  887. ));
  888. #ifdef COMPILED_BY_DEVELOPER
  889. KdPrint(("MSV1_0: RxNUserPasswordSet returns %d.\n", NetStatus));
  890. #endif // COMPILED_BY_DEVELOPER
  891. // Since we overload the computername as the domain name,
  892. // map NERR_InvalidComputer to STATUS_NO_SUCH_DOMAIN, since
  893. // that will give the user a nice error message.
  894. //
  895. // ERROR_PATH_NOT_FOUND is returned on a standalone workstation that
  896. // doesn't have the network installed.
  897. //
  898. if (NetStatus == NERR_InvalidComputer ||
  899. NetStatus == ERROR_PATH_NOT_FOUND) {
  900. Status = STATUS_NO_SUCH_DOMAIN;
  901. // ERROR_SEM_TIMEOUT can be returned when the computer name doesn't
  902. // exist.
  903. //
  904. // ERROR_REM_NOT_LIST can also be returned when the computer name
  905. // doesn't exist.
  906. //
  907. } else if ( NetStatus == ERROR_SEM_TIMEOUT ||
  908. NetStatus == ERROR_REM_NOT_LIST) {
  909. Status = STATUS_BAD_NETWORK_PATH;
  910. } else if ( (NetStatus == ERROR_INVALID_PARAMETER) &&
  911. ((wcslen(NewPassword) > LM20_PWLEN) ||
  912. (wcslen(OldPassword) > LM20_PWLEN)) ) {
  913. //
  914. // The net api returns ERROR_INVALID_PARAMETER if the password
  915. // could not be converted to the LM OWF password. Return
  916. // STATUS_PASSWORD_RESTRICTION for this.
  917. //
  918. Status = STATUS_PASSWORD_RESTRICTION;
  919. //
  920. // We never made it to the other machine, so we should continue
  921. // trying to change the password.
  922. //
  923. *Authoritative = FALSE;
  924. } else {
  925. Status = NetpApiStatusToNtStatus( NetStatus );
  926. }
  927. Cleanup:
  928. //
  929. // Free UserName if used.
  930. //
  931. if (UserName) {
  932. I_NtLmFree(UserName);
  933. }
  934. //
  935. // Free OldPassword if used. (Don't let password make it to page file)
  936. //
  937. if (OldPassword) {
  938. RtlZeroMemory( OldPassword, wcslen(OldPassword) * sizeof(WCHAR) );
  939. I_NtLmFree(OldPassword);
  940. }
  941. //
  942. // Free NewPassword if used. (Don't let password make it to page file)
  943. //
  944. if (NewPassword) {
  945. RtlZeroMemory( NewPassword, wcslen(NewPassword) * sizeof(WCHAR) );
  946. I_NtLmFree(NewPassword);
  947. }
  948. return Status;
  949. }
  950. NTSTATUS
  951. MspChangePassword(
  952. IN OUT PUNICODE_STRING ComputerName,
  953. IN PUNICODE_STRING UserName,
  954. IN PUNICODE_STRING OldPassword,
  955. IN PUNICODE_STRING NewPassword,
  956. IN PLSA_CLIENT_REQUEST ClientRequest,
  957. IN BOOLEAN Impersonating,
  958. OUT PDOMAIN_PASSWORD_INFORMATION *DomainPasswordInfo,
  959. OUT PPOLICY_PRIMARY_DOMAIN_INFO *PrimaryDomainInfo OPTIONAL,
  960. OUT PBOOLEAN Authoritative
  961. )
  962. /*++
  963. Routine Description:
  964. This routine is called by MspLM20ChangePassword to change the password
  965. on the specified server. The server may be either NT or Downlevel.
  966. Arguments:
  967. ComputerName - Name of the target machine. This name may or may not
  968. begin with two backslashes.
  969. UserName - Name of the user to change password for.
  970. OldPassword - Plaintext current password.
  971. NewPassword - Plaintext replacement password.
  972. ClientRequest - Is a pointer to an opaque data structure
  973. representing the client's request.
  974. DomainPasswordInfo - Password restriction information (returned only if
  975. status is STATUS_PASSWORD_RESTRICTION).
  976. PrimaryDomainInfo - DomainNameInformation (returned only if status is
  977. STATUS_BACKUP_CONTROLLER).
  978. Authoritative - Indicates that the error code is authoritative
  979. and it indicates that password changing should stop. If false,
  980. password changing should continue.
  981. Return Value:
  982. STATUS_SUCCESS - Indicates the service completed successfully.
  983. STATUS_PASSWORD_RESTRICTION - Password changing is restricted.
  984. --*/
  985. {
  986. NTSTATUS Status;
  987. UNICODE_STRING UncComputerName;
  988. *Authoritative = TRUE;
  989. //
  990. // Ensure the server name is a UNC server name.
  991. //
  992. Status = MspAddBackslashesComputerName( ComputerName, &UncComputerName );
  993. if (!NT_SUCCESS(Status)) {
  994. KdPrint(("MspChangePassword: MspAddBackslashes..(%wZ) failed, status %x\n",
  995. ComputerName, Status));
  996. return(Status);
  997. }
  998. //
  999. // Assume the Server is an NT server and try to change the password.
  1000. //
  1001. Status = MspChangePasswordSam(
  1002. &UncComputerName,
  1003. UserName,
  1004. OldPassword,
  1005. NewPassword,
  1006. ClientRequest,
  1007. Impersonating,
  1008. DomainPasswordInfo,
  1009. PrimaryDomainInfo,
  1010. Authoritative );
  1011. //
  1012. // If MspChangePasswordSam returns anything other than
  1013. // STATUS_CANT_ACCESS_DOMAIN_INFO, it was able to connect
  1014. // to the remote computer so we won't try downlevel.
  1015. //
  1016. if (Status == STATUS_CANT_ACCESS_DOMAIN_INFO) {
  1017. NET_API_STATUS NetStatus;
  1018. DWORD OptionsSupported;
  1019. //
  1020. // only if target machine doesn't support SAM protocol do we attempt
  1021. // downlevel.
  1022. // MspAddBackslashesComputerName() NULL terminates the buffer.
  1023. //
  1024. NetStatus = NetRemoteComputerSupports(
  1025. (LPWSTR)UncComputerName.Buffer,
  1026. SUPPORTS_RPC | SUPPORTS_LOCAL | SUPPORTS_SAM_PROTOCOL,
  1027. &OptionsSupported
  1028. );
  1029. if ( NetStatus == NERR_Success && !(OptionsSupported & SUPPORTS_SAM_PROTOCOL) ) {
  1030. Status = MspChangePasswordDownlevel(
  1031. &UncComputerName,
  1032. UserName,
  1033. OldPassword,
  1034. NewPassword,
  1035. Authoritative );
  1036. }
  1037. }
  1038. //
  1039. // Free UncComputerName.Buffer if different from ComputerName.
  1040. //
  1041. if ( UncComputerName.Buffer != ComputerName->Buffer ) {
  1042. I_NtLmFree(UncComputerName.Buffer);
  1043. }
  1044. return(Status);
  1045. }
  1046. //
  1047. // the following structures are used to map win32 errors to NTSTATUS
  1048. //
  1049. typedef LONG (WINAPI * I_RPCMAPWIN32STATUS)(
  1050. IN ULONG Win32Status
  1051. );
  1052. typedef struct _STATUS_MAPPING {
  1053. DWORD Error;
  1054. NTSTATUS NtStatus;
  1055. } STATUS_MAPPING;
  1056. NTSTATUS
  1057. MspMapNtdsApiError(
  1058. IN DWORD DsStatus,
  1059. IN NTSTATUS DefaultStatus
  1060. )
  1061. /*++
  1062. Routine Description:
  1063. This routine maps DS API error codes to appropriate NTSTATUS codes
  1064. Arguments:
  1065. DsStatus - Status code from DS APIs
  1066. DefaultStatus - Default status code if no other code is found
  1067. Return Value:
  1068. NtStatus code
  1069. --*/
  1070. {
  1071. NTSTATUS Status = DsStatus;
  1072. I_RPCMAPWIN32STATUS pFuncI_RpcMapWin32Status = NULL;
  1073. HMODULE hLib = NULL;
  1074. static const STATUS_MAPPING StatusMap[] = {
  1075. {ERROR_NO_SUCH_DOMAIN, STATUS_NO_SUCH_DOMAIN},
  1076. {ERROR_INVALID_DOMAINNAME, STATUS_INVALID_PARAMETER},
  1077. {DS_NAME_ERROR_NO_SYNTACTICAL_MAPPING, STATUS_NO_SUCH_USER},
  1078. {ERROR_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL}
  1079. };
  1080. if (SUCCEEDED(Status))
  1081. {
  1082. int i;
  1083. //
  1084. // handle expected win32 errors
  1085. //
  1086. for (i = 0; i < RTL_NUMBER_OF(StatusMap); i++)
  1087. {
  1088. if (StatusMap[i].Error == (DWORD) Status)
  1089. {
  1090. return (StatusMap[i].NtStatus);
  1091. }
  1092. }
  1093. //
  1094. // handle RPC status
  1095. //
  1096. hLib = LoadLibraryW(L"rpcrt4.dll");
  1097. if (hLib)
  1098. {
  1099. pFuncI_RpcMapWin32Status = (I_RPCMAPWIN32STATUS) GetProcAddress( hLib, "I_RpcMapWin32Status" );
  1100. if (pFuncI_RpcMapWin32Status)
  1101. {
  1102. Status = pFuncI_RpcMapWin32Status(Status);
  1103. }
  1104. FreeLibrary(hLib);
  1105. }
  1106. }
  1107. //
  1108. // not mapped? use default status
  1109. //
  1110. if (NT_SUCCESS(Status) || (Status == STATUS_UNSUCCESSFUL))
  1111. {
  1112. Status = DefaultStatus;
  1113. }
  1114. return Status;
  1115. }
  1116. NTSTATUS
  1117. MspImpersonateNetworkService(
  1118. VOID
  1119. )
  1120. /*++
  1121. Routine Description:
  1122. This routine impersonates network servcie.
  1123. Arguments:
  1124. None
  1125. Return Value:
  1126. NtStatus code
  1127. --*/
  1128. {
  1129. NTSTATUS Status;
  1130. HANDLE TokenHandle = NULL;
  1131. LUID NetworkServiceLuid = NETWORKSERVICE_LUID;
  1132. Status = LsaFunctions->OpenTokenByLogonId(&NetworkServiceLuid, &TokenHandle);
  1133. if (!NT_SUCCESS(Status))
  1134. {
  1135. goto Cleanup;
  1136. }
  1137. Status = NtSetInformationThread(
  1138. NtCurrentThread(),
  1139. ThreadImpersonationToken,
  1140. &TokenHandle,
  1141. sizeof(TokenHandle)
  1142. );
  1143. if (!NT_SUCCESS( Status))
  1144. {
  1145. goto Cleanup;
  1146. }
  1147. Cleanup:
  1148. if (TokenHandle)
  1149. {
  1150. NtClose(TokenHandle);
  1151. }
  1152. return Status;
  1153. }
  1154. BOOL
  1155. MspIsRpcServerUnavailableError(
  1156. IN DWORD Error
  1157. )
  1158. /*++
  1159. Routine Description:
  1160. This routine determines if an error code means the server is not available.
  1161. Arguments:
  1162. Error - An RPC status code or Win32 error code
  1163. Return Value:
  1164. True if the error code means the server is not avaiable and false otherwise
  1165. --*/
  1166. {
  1167. // This list of error codes blessed by MazharM on 4/20/99.
  1168. switch ( Error )
  1169. {
  1170. case RPC_S_SERVER_UNAVAILABLE: // can't get there from here
  1171. case EPT_S_NOT_REGISTERED: // demoted or in DS repair mode
  1172. case RPC_S_UNKNOWN_IF: // demoted or in DS repair mode
  1173. case RPC_S_INTERFACE_NOT_FOUND: // demoted or in DS repair mode
  1174. case RPC_S_COMM_FAILURE: // can't get there from here
  1175. return (TRUE);
  1176. }
  1177. return (FALSE);
  1178. }
  1179. NTSTATUS
  1180. MspConstructSPN(
  1181. IN PCWSTR DomainControllerName,
  1182. IN PCWSTR DnsDomainName,
  1183. OUT PWSTR * Spn
  1184. )
  1185. /*++
  1186. Routine Description:
  1187. This routine constructs a SPN with the "@domainName" suffix. The suffix
  1188. serves as a hint for kerberos.
  1189. Arguments:
  1190. DomainControllerName - Name of the domain controller
  1191. DnsDomainName - DNS domain name
  1192. Spn - Receives SPN for the domain controller at DNS domain name
  1193. Return Value:
  1194. NTSTATUS code
  1195. --*/
  1196. {
  1197. NTSTATUS Status = STATUS_UNSUCCESSFUL;
  1198. DWORD Error = ERROR_SUCCESS;
  1199. DWORD CharCount = 0;
  1200. DWORD TotalCharCount = 0;
  1201. PCWSTR ServiceName = NULL;
  1202. PCWSTR InstanceName = NULL;
  1203. PCWSTR SvcClass = L"ldap";
  1204. //
  1205. // the following temporary structures need to be free'ed
  1206. //
  1207. PWSTR TmpSpn = NULL;
  1208. PWSTR TmpService = NULL;
  1209. PWSTR TmpInstance = NULL;
  1210. if (!DomainControllerName)
  1211. {
  1212. Status = STATUS_INVALID_PARAMETER;
  1213. goto Cleanup;
  1214. }
  1215. if ( DnsDomainName )
  1216. {
  1217. // Caller gave all components needed to construct full 3-part SPN.
  1218. InstanceName = DomainControllerName;
  1219. ServiceName = DnsDomainName;
  1220. }
  1221. else
  1222. {
  1223. // Construct SPN of form: LDAP/ntdsdc4.ntdev.microsoft.com
  1224. InstanceName = DomainControllerName;
  1225. ServiceName = DomainControllerName;
  1226. }
  1227. //
  1228. // Skip past leading "\\" if present. This is not circumventing
  1229. // a client who has passed NetBIOS names mistakenly but rather
  1230. // helping the client which has passed args as returned by
  1231. // DsGetDcName which prepends "\\" even when DS_RETURN_DNS_NAME
  1232. // was requested.
  1233. //
  1234. if (0 == wcsncmp(InstanceName, L"\\\\", 2))
  1235. {
  1236. InstanceName += 2;
  1237. }
  1238. if (0 == wcsncmp(ServiceName, L"\\\\", 2))
  1239. {
  1240. ServiceName += 2;
  1241. }
  1242. //
  1243. // Strip trailing '.' if it exists. We do this as we know the server side
  1244. // registers dot-less names only. We can't whack in place as the input
  1245. // args are const.
  1246. //
  1247. CharCount = (ULONG) wcslen(InstanceName);
  1248. if ( L'.' == InstanceName[CharCount - 1] )
  1249. {
  1250. TmpInstance = (WCHAR *) NtLmAllocatePrivateHeap(CharCount * sizeof(WCHAR));
  1251. if (!TmpInstance)
  1252. {
  1253. Status = STATUS_NO_MEMORY;
  1254. goto Cleanup;
  1255. }
  1256. RtlCopyMemory(TmpInstance, InstanceName, (CharCount - 1) * sizeof(WCHAR));
  1257. TmpInstance[CharCount - 1] = L'\0';
  1258. InstanceName = TmpInstance;
  1259. }
  1260. CharCount = (ULONG) wcslen(ServiceName);
  1261. if ( L'.' == ServiceName[CharCount - 1] )
  1262. {
  1263. TmpService = (WCHAR *) NtLmAllocatePrivateHeap(CharCount * sizeof(WCHAR));
  1264. if (!TmpService)
  1265. {
  1266. Status = STATUS_NO_MEMORY;
  1267. goto Cleanup;
  1268. }
  1269. RtlCopyMemory(TmpService, ServiceName, (CharCount - 1) * sizeof(WCHAR));
  1270. TmpService[CharCount - 1] = L'\0';
  1271. ServiceName = TmpService;
  1272. }
  1273. CharCount = 0;
  1274. Error = DsMakeSpnW(SvcClass, ServiceName, InstanceName, 0,
  1275. NULL, &CharCount, NULL);
  1276. if ( Error != ERROR_SUCCESS && (ERROR_BUFFER_OVERFLOW != Error) )
  1277. {
  1278. Status = MspMapNtdsApiError(Error, STATUS_INVALID_PARAMETER);
  1279. goto Cleanup;
  1280. }
  1281. if (DnsDomainName)
  1282. {
  1283. TotalCharCount = CharCount + 1 + (ULONG) wcslen(ServiceName); // 1 char for @ sign
  1284. }
  1285. else
  1286. {
  1287. TotalCharCount = CharCount;
  1288. }
  1289. TmpSpn = (WCHAR *) NtLmAllocatePrivateHeap(sizeof(WCHAR) * TotalCharCount);
  1290. if ( !TmpSpn )
  1291. {
  1292. Status = STATUS_NO_MEMORY;
  1293. goto Cleanup;
  1294. }
  1295. Error = DsMakeSpnW(SvcClass, ServiceName, InstanceName, 0,
  1296. NULL, &CharCount, TmpSpn);
  1297. if ( Error != ERROR_SUCCESS)
  1298. {
  1299. Status = MspMapNtdsApiError(Error, STATUS_INVALID_PARAMETER);
  1300. goto Cleanup;
  1301. }
  1302. if (DnsDomainName && (CharCount < TotalCharCount))
  1303. {
  1304. wcsncat(TmpSpn, L"@", TotalCharCount - CharCount);
  1305. wcsncat(TmpSpn, ServiceName, TotalCharCount - CharCount - 1);
  1306. }
  1307. Status = STATUS_SUCCESS;
  1308. *Spn = TmpSpn;
  1309. TmpSpn = NULL; // do not free it
  1310. Cleanup:
  1311. if (TmpInstance)
  1312. {
  1313. NtLmFreePrivateHeap(TmpInstance);
  1314. }
  1315. if (TmpService)
  1316. {
  1317. NtLmFreePrivateHeap(TmpService);
  1318. }
  1319. if (TmpSpn) {
  1320. NtLmFreePrivateHeap(TmpSpn);
  1321. }
  1322. return Status;
  1323. }
  1324. //
  1325. // maximum number of DC force rediscovery retries for cracking UPNs in changepassword
  1326. //
  1327. #define MAX_DC_REDISCOVERY_RETRIES 2
  1328. //
  1329. // determine if it is an authentication error, here I leverage two CREDUI macros
  1330. //
  1331. // ISSUE: Is downgrade error fatal? (CREDUI_IS_AUTHENTICATION_ERROR includes
  1332. // downgrade errors)
  1333. //
  1334. #define IS_BAD_CREDENTIALS_ERROR(x) (CREDUI_NO_PROMPT_AUTHENTICATION_ERROR((x)) \
  1335. || CREDUI_IS_AUTHENTICATION_ERROR((x)))
  1336. NTSTATUS
  1337. MspLm20ChangePassword (
  1338. IN PLSA_CLIENT_REQUEST ClientRequest,
  1339. IN PVOID ProtocolSubmitBuffer,
  1340. IN PVOID ClientBufferBase,
  1341. IN ULONG SubmitBufferSize,
  1342. OUT PVOID *ProtocolReturnBuffer,
  1343. OUT PULONG ReturnBufferSize,
  1344. OUT PNTSTATUS ProtocolStatus
  1345. )
  1346. /*++
  1347. Routine Description:
  1348. This routine is the dispatch routine for LsaCallAuthenticationPackage()
  1349. with a message type of MsV1_0ChangePassword. This routine changes an
  1350. account's password by either calling SamXxxPassword (for NT domains) or
  1351. RxNetUserPasswordSet (for downlevel domains and standalone servers).
  1352. Arguments:
  1353. The arguments to this routine are identical to those of LsaApCallPackage.
  1354. Only the special attributes of these parameters as they apply to
  1355. this routine are mentioned here.
  1356. Return Value:
  1357. STATUS_SUCCESS - Indicates the service completed successfully.
  1358. STATUS_PASSWORD_RESTRICTION - The password change failed because the
  1359. - password doesn't meet one or more domain restrictions. The
  1360. - response buffer is allocated. If the PasswordInfoValid flag is
  1361. - set it contains valid information otherwise it contains no
  1362. - information because this was a down-level change.
  1363. STATUS_BACKUP_CONTROLLER - The named machine is a Backup Domain Controller.
  1364. Changing password is only allowed on the Primary Domain Controller.
  1365. --*/
  1366. {
  1367. PMSV1_0_CHANGEPASSWORD_REQUEST ChangePasswordRequest = NULL;
  1368. PMSV1_0_CHANGEPASSWORD_RESPONSE ChangePasswordResponse;
  1369. NTSTATUS Status = STATUS_SUCCESS;
  1370. NTSTATUS SavedStatus = STATUS_SUCCESS;
  1371. LPWSTR DomainName = NULL;
  1372. PDOMAIN_CONTROLLER_INFO DCInfo = NULL;
  1373. UNICODE_STRING DCNameString;
  1374. UNICODE_STRING ClientNetbiosDomain = {0};
  1375. PUNICODE_STRING ClientDsGetDcDomain;
  1376. UNICODE_STRING ClientDnsDomain = {0};
  1377. UNICODE_STRING ClientUpn = {0};
  1378. UNICODE_STRING ClientName = {0};
  1379. UNICODE_STRING ValidatedAccountName;
  1380. UNICODE_STRING ValidatedDomainName;
  1381. LPWSTR ValidatedOldPasswordBuffer;
  1382. LPWSTR ValidatedNewPasswordBuffer;
  1383. NET_API_STATUS NetStatus;
  1384. PPOLICY_LSA_SERVER_ROLE_INFO PolicyLsaServerRoleInfo = NULL;
  1385. PDOMAIN_PASSWORD_INFORMATION DomainPasswordInfo = NULL;
  1386. PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo = NULL;
  1387. PWKSTA_INFO_100 WkstaInfo100 = NULL;
  1388. BOOLEAN PasswordBufferValidated = FALSE;
  1389. CLIENT_BUFFER_DESC ClientBufferDesc;
  1390. PSECURITY_SEED_AND_LENGTH SeedAndLength;
  1391. PDS_NAME_RESULTW NameResult = NULL;
  1392. HANDLE DsHandle = NULL;
  1393. PWSTR SpnForDC = NULL;
  1394. UCHAR Seed;
  1395. BOOLEAN Authoritative = TRUE;
  1396. BOOLEAN AttemptRediscovery = FALSE;
  1397. BOOLEAN Validated = TRUE;
  1398. #if _WIN64
  1399. PVOID pTempSubmitBuffer = ProtocolSubmitBuffer;
  1400. SECPKG_CALL_INFO CallInfo;
  1401. BOOL fAllocatedSubmitBuffer = FALSE;
  1402. #endif
  1403. RtlInitUnicodeString(
  1404. &DCNameString,
  1405. NULL
  1406. );
  1407. //
  1408. // Sanity checks.
  1409. //
  1410. NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
  1411. #if _WIN64
  1412. //
  1413. // Expand the ProtocolSubmitBuffer to 64-bit pointers if this
  1414. // call came from a WOW client.
  1415. //
  1416. if (!LsaFunctions->GetCallInfo(&CallInfo))
  1417. {
  1418. Status = STATUS_INTERNAL_ERROR;
  1419. goto Cleanup;
  1420. }
  1421. if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
  1422. {
  1423. Status = MsvConvertWOWChangePasswordBuffer(ProtocolSubmitBuffer,
  1424. ClientBufferBase,
  1425. &SubmitBufferSize,
  1426. &pTempSubmitBuffer);
  1427. if (!NT_SUCCESS(Status))
  1428. {
  1429. goto Cleanup;
  1430. }
  1431. fAllocatedSubmitBuffer = TRUE;
  1432. //
  1433. // Some macros below expand out to use ProtocolSubmitBuffer directly.
  1434. // We've secretly replaced their usual ProtocolSubmitBuffer with
  1435. // pTempSubmitBuffer -- let's see if they can tell the difference.
  1436. //
  1437. ProtocolSubmitBuffer = pTempSubmitBuffer;
  1438. }
  1439. #endif // _WIN64
  1440. if ( SubmitBufferSize < sizeof(MSV1_0_CHANGEPASSWORD_REQUEST) ) {
  1441. Status = STATUS_INVALID_PARAMETER;
  1442. goto Cleanup;
  1443. }
  1444. ChangePasswordRequest = (PMSV1_0_CHANGEPASSWORD_REQUEST) ProtocolSubmitBuffer;
  1445. ASSERT( ChangePasswordRequest->MessageType == MsV1_0ChangePassword ||
  1446. ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword );
  1447. RELOCATE_ONE( &ChangePasswordRequest->DomainName );
  1448. RELOCATE_ONE( &ChangePasswordRequest->AccountName );
  1449. if ( ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ) {
  1450. NULL_RELOCATE_ONE( &ChangePasswordRequest->OldPassword );
  1451. } else {
  1452. RELOCATE_ONE_ENCODED( &ChangePasswordRequest->OldPassword );
  1453. }
  1454. RELOCATE_ONE_ENCODED( &ChangePasswordRequest->NewPassword );
  1455. //
  1456. // save away copies of validated buffers to check later.
  1457. //
  1458. RtlCopyMemory( &ValidatedDomainName, &ChangePasswordRequest->DomainName, sizeof(ValidatedDomainName) );
  1459. RtlCopyMemory( &ValidatedAccountName, &ChangePasswordRequest->AccountName, sizeof(ValidatedAccountName) );
  1460. ValidatedOldPasswordBuffer = ChangePasswordRequest->OldPassword.Buffer;
  1461. ValidatedNewPasswordBuffer = ChangePasswordRequest->NewPassword.Buffer;
  1462. SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangePasswordRequest->OldPassword.Length;
  1463. Seed = SeedAndLength->Seed;
  1464. SeedAndLength->Seed = 0;
  1465. if (Seed != 0) {
  1466. try {
  1467. RtlRunDecodeUnicodeString(
  1468. Seed,
  1469. &ChangePasswordRequest->OldPassword
  1470. );
  1471. } except (EXCEPTION_EXECUTE_HANDLER) {
  1472. Status = STATUS_ILL_FORMED_PASSWORD;
  1473. goto Cleanup;
  1474. }
  1475. }
  1476. SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangePasswordRequest->NewPassword.Length;
  1477. Seed = SeedAndLength->Seed;
  1478. SeedAndLength->Seed = 0;
  1479. if (Seed != 0) {
  1480. if ( ChangePasswordRequest->NewPassword.Buffer !=
  1481. ValidatedNewPasswordBuffer ) {
  1482. Status = STATUS_INVALID_PARAMETER;
  1483. goto Cleanup;
  1484. }
  1485. try {
  1486. RtlRunDecodeUnicodeString(
  1487. Seed,
  1488. &ChangePasswordRequest->NewPassword
  1489. );
  1490. } except (EXCEPTION_EXECUTE_HANDLER) {
  1491. Status = STATUS_ILL_FORMED_PASSWORD;
  1492. goto Cleanup;
  1493. }
  1494. }
  1495. //
  1496. // sanity check that we didn't whack over buffers.
  1497. //
  1498. if (!RtlCompareMemory(
  1499. &ValidatedDomainName,
  1500. &ChangePasswordRequest->DomainName,
  1501. sizeof(ValidatedDomainName)
  1502. )
  1503. ||
  1504. !RtlCompareMemory(
  1505. &ValidatedAccountName,
  1506. &ChangePasswordRequest->AccountName,
  1507. sizeof(ValidatedAccountName)
  1508. )
  1509. ||
  1510. (ValidatedOldPasswordBuffer != ChangePasswordRequest->OldPassword.Buffer)
  1511. ||
  1512. (ValidatedNewPasswordBuffer != ChangePasswordRequest->NewPassword.Buffer)
  1513. ) {
  1514. Status= STATUS_INVALID_PARAMETER;
  1515. goto Cleanup;
  1516. }
  1517. //
  1518. // validate domain name and account name. Account name allows UPN
  1519. //
  1520. if ( (ChangePasswordRequest->DomainName.Length / sizeof(WCHAR) > DNS_MAX_NAME_LENGTH)
  1521. || (ChangePasswordRequest->AccountName.Length / sizeof(WCHAR) > (UNLEN + 1 + DNS_MAX_NAME_LENGTH)) )
  1522. {
  1523. SspPrint((SSP_CRITICAL, "MspLm20ChangePassword invalid parameter: DomainName.Length %#x, AccountName.Length %#x\n",
  1524. ChangePasswordRequest->DomainName.Length, ChangePasswordRequest->AccountName.Length));
  1525. Status = STATUS_INVALID_PARAMETER;
  1526. goto Cleanup;
  1527. }
  1528. *ReturnBufferSize = 0;
  1529. *ProtocolReturnBuffer = NULL;
  1530. *ProtocolStatus = STATUS_PENDING;
  1531. PasswordBufferValidated = TRUE;
  1532. MsvPaswdLogPrint(("Attempting password change server/domain %wZ for user %wZ\n",
  1533. &ChangePasswordRequest->DomainName,
  1534. &ChangePasswordRequest->AccountName
  1535. ));
  1536. #ifdef COMPILED_BY_DEVELOPER
  1537. KdPrint(("MSV1_0:\n"
  1538. "\tDomain:\t%wZ\n"
  1539. "\tAccount:\t%wZ\n"
  1540. "\tOldPassword(%d)\n"
  1541. "\tNewPassword(%d)\n",
  1542. &ChangePasswordRequest->DomainName,
  1543. &ChangePasswordRequest->AccountName,
  1544. (int) ChangePasswordRequest->OldPassword.Length,
  1545. (int) ChangePasswordRequest->NewPassword.Length
  1546. ));
  1547. #endif // COMPILED_BY_DEVELOPER
  1548. SspPrint((SSP_UPDATES, "MspLm20ChangePassword %wZ\\%wZ, message type %#x%s, impersonating ? %s\n",
  1549. &ChangePasswordRequest->DomainName,
  1550. &ChangePasswordRequest->AccountName,
  1551. ChangePasswordRequest->MessageType,
  1552. (MsV1_0ChangeCachedPassword == ChangePasswordRequest->MessageType) ? " (cached)" : "",
  1553. ChangePasswordRequest->Impersonating ? "true" : "false"));
  1554. //
  1555. // If the client supplied a non-nt4 name, go ahead and convert it here.
  1556. //
  1557. if (ChangePasswordRequest->DomainName.Length == 0) {
  1558. DWORD DsStatus;
  1559. HANDLE NullTokenHandle = NULL;
  1560. WCHAR NameBuffer[UNLEN + 1];
  1561. ULONG Index;
  1562. BOOLEAN useSimpleCrackName = FALSE;
  1563. PWSTR DcName = NULL;
  1564. PWSTR DomainName = NULL;
  1565. DWORD DsGetDcNameFlags = DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME;
  1566. BOOLEAN StandaloneWorkstation = FALSE;
  1567. DWORD DcRediscoveryRetries = 0;
  1568. if (ChangePasswordRequest->AccountName.Length / sizeof(WCHAR) > UNLEN) {
  1569. Status = STATUS_INVALID_PARAMETER;
  1570. goto Cleanup;
  1571. }
  1572. RtlCopyMemory(
  1573. NameBuffer,
  1574. ChangePasswordRequest->AccountName.Buffer,
  1575. ChangePasswordRequest->AccountName.Length
  1576. );
  1577. NameBuffer[ChangePasswordRequest->AccountName.Length/sizeof(WCHAR)] = L'\0';
  1578. RtlInitUnicodeString( &ClientUpn, NameBuffer );
  1579. if ( NlpWorkstation ) {
  1580. RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
  1581. StandaloneWorkstation = (BOOLEAN) (NtLmGlobalTargetFlags == NTLMSSP_TARGET_TYPE_SERVER);
  1582. RtlReleaseResource(&NtLmGlobalCritSect);
  1583. }
  1584. if (StandaloneWorkstation) {
  1585. SspPrint(( SSP_WARNING, "MspLm20ChangePassword use simple crack for standalone machines\n" ));
  1586. useSimpleCrackName = TRUE;
  1587. }
  1588. //
  1589. // bind and crack names
  1590. //
  1591. // we allow the bind to fail on a member workstation, which allows
  1592. // us to attempt a 'manual' crack.
  1593. //
  1594. while (!useSimpleCrackName) {
  1595. if (DcName == NULL) {
  1596. if ( DCInfo != NULL ) {
  1597. NetApiBufferFree(DCInfo);
  1598. DCInfo = NULL;
  1599. }
  1600. SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: DsGetDcNameW for %ws, DsGetDcNameFlags %#x\n", DomainName, DsGetDcNameFlags ));
  1601. NetStatus = DsGetDcNameW(
  1602. NULL, // no computername
  1603. DomainName,
  1604. NULL, // no domain guid
  1605. NULL, // no site name
  1606. DsGetDcNameFlags,
  1607. &DCInfo
  1608. );
  1609. if (NetStatus == NERR_Success) {
  1610. DcName = DCInfo->DomainControllerName;
  1611. } else {
  1612. SspPrint(( SSP_WARNING, "MspLm20ChangePassword: did not find a DC for %ws, NetStatus %#x\n", DomainName, NetStatus ));
  1613. if (!DomainName && !NlpWorkstation) { // DC can not bind to local forest is fatal
  1614. Status = NetpApiStatusToNtStatus(NetStatus);
  1615. if ( Status == STATUS_INTERNAL_ERROR ) {
  1616. Status = STATUS_NO_SUCH_DOMAIN;
  1617. }
  1618. goto Cleanup;
  1619. }
  1620. useSimpleCrackName = TRUE;
  1621. break;
  1622. }
  1623. }
  1624. if (DsHandle) {
  1625. DsUnBindW(
  1626. &DsHandle
  1627. );
  1628. DsHandle = NULL;
  1629. }
  1630. if (SpnForDC) {
  1631. NtLmFreePrivateHeap(SpnForDC);
  1632. SpnForDC = NULL;
  1633. }
  1634. Status = MspConstructSPN(DcName, DomainName, &SpnForDC);
  1635. if (!NT_SUCCESS(Status)) {
  1636. goto Cleanup;
  1637. }
  1638. //
  1639. // impersonate for DsBind:
  1640. //
  1641. // 1) use machine credentials first, this can fail for
  1642. // workstations in resource domains with one way trust
  1643. // 2) if machine credentials does not work, try to impersonate
  1644. // the client iff asked to do so
  1645. //
  1646. // Notes:
  1647. //
  1648. // the client can have a wrong password that he/she is trying to
  1649. // change, this can happen when the workstation is unlocked with
  1650. // a new password and the unlock is validated by NTLM (which does
  1651. // not parse unlock logonId or update old logon session credentials
  1652. // at this point) or in most common cases, the password is either
  1653. // expired or must be changed at the next logon etc
  1654. //
  1655. Status = MspImpersonateNetworkService(); // note if we get here, the machine is always joined
  1656. if (!NT_SUCCESS(Status)) {
  1657. goto Cleanup;
  1658. }
  1659. SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: binding to %ws with machine identity, spn %ws\n", DcName, SpnForDC ));
  1660. DsStatus = DsBindWithSpnExW(
  1661. DcName, // DC name
  1662. DcName == NULL ? DomainName : NULL, // domain name
  1663. NULL, // no AuthIdentity
  1664. SpnForDC, // SPN
  1665. 0, // No delegation
  1666. &DsHandle
  1667. );
  1668. if ( IS_BAD_CREDENTIALS_ERROR(DsStatus) && ChangePasswordRequest->Impersonating ) {
  1669. Status = LsaFunctions->ImpersonateClient();
  1670. if (!NT_SUCCESS(Status)) {
  1671. NtSetInformationThread(
  1672. NtCurrentThread(),
  1673. ThreadImpersonationToken,
  1674. &NullTokenHandle,
  1675. sizeof(NullTokenHandle)
  1676. );
  1677. goto Cleanup;
  1678. }
  1679. SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: dsbind failed with %#x, rebinding to %ws with client identity, spn %ws\n", DsStatus, DcName, SpnForDC ));
  1680. DsStatus = DsBindWithSpnExW(
  1681. DcName, // DC name
  1682. DcName == NULL ? DomainName : NULL, // domain name
  1683. NULL, // no AuthIdentity
  1684. SpnForDC, // SPN
  1685. 0, // No delegation
  1686. &DsHandle
  1687. );
  1688. }
  1689. //
  1690. // always revert to self
  1691. //
  1692. Status = NtSetInformationThread(
  1693. NtCurrentThread(),
  1694. ThreadImpersonationToken,
  1695. &NullTokenHandle,
  1696. sizeof(NullTokenHandle)
  1697. );
  1698. if (!NT_SUCCESS(Status)) {
  1699. goto Cleanup;
  1700. }
  1701. if (DsStatus != ERROR_SUCCESS) {
  1702. SspPrint(( SSP_WARNING, "MspLm20ChangePassword: could not bind %#x\n", DsStatus ));
  1703. if ( MspIsRpcServerUnavailableError(DsStatus) ) {
  1704. if ( DCInfo != NULL ) {
  1705. NetApiBufferFree(DCInfo);
  1706. DCInfo = NULL;
  1707. }
  1708. SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: force re-dicover DCs for %ws, DsGetDcNameFlags %#x\n", DomainName, DsGetDcNameFlags | DS_FORCE_REDISCOVERY ));
  1709. NetStatus = DsGetDcNameW(
  1710. NULL, // no computername
  1711. DomainName,
  1712. NULL, // no domain guid
  1713. NULL, // no site name
  1714. DsGetDcNameFlags | DS_FORCE_REDISCOVERY,
  1715. &DCInfo
  1716. );
  1717. //
  1718. // try again if a different DC is found
  1719. //
  1720. if (NetStatus == NERR_Success ) {
  1721. ASSERT(DcName != NULL);
  1722. if (_wcsicmp(DcName, DCInfo->DomainControllerName) != 0) {
  1723. if (++DcRediscoveryRetries <= MAX_DC_REDISCOVERY_RETRIES) {
  1724. DcName = DCInfo->DomainControllerName;
  1725. continue;
  1726. } else {
  1727. SspPrint(( SSP_WARNING, "MspLm20ChangePassword: exceeded retry limits %#x\n", DcRediscoveryRetries ));
  1728. }
  1729. }
  1730. //
  1731. // use simple crack if necessary
  1732. //
  1733. } else { // pick up the new error code if rediscovery fails, use simple crack if necessary
  1734. DsStatus = NetpApiStatusToNtStatus(NetStatus);
  1735. if ( DsStatus == STATUS_INTERNAL_ERROR ) {
  1736. DsStatus = (DWORD) STATUS_NO_SUCH_DOMAIN;
  1737. }
  1738. }
  1739. SspPrint(( SSP_WARNING, "MspLm20ChangePassword: could not redicovery a DC %#x\n", NetStatus ));
  1740. }
  1741. if ( !DomainName && !NlpWorkstation ) { // DC can not bind to local forest is fatal
  1742. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsBindW returned 0x%lx.\n", DsStatus ));
  1743. Status = MspMapNtdsApiError( DsStatus, STATUS_NO_SUCH_DOMAIN );
  1744. goto Cleanup;
  1745. }
  1746. useSimpleCrackName = TRUE;
  1747. break;
  1748. }
  1749. if (NameResult) {
  1750. DsFreeNameResult(NameResult);
  1751. NameResult = NULL;
  1752. }
  1753. DsStatus = DsCrackNamesW(
  1754. DsHandle,
  1755. DomainName ? 0 : DS_NAME_FLAG_TRUST_REFERRAL, // do not follow referral in the remote forest
  1756. DS_UNKNOWN_NAME,
  1757. DS_NT4_ACCOUNT_NAME,
  1758. 1,
  1759. &ClientUpn.Buffer,
  1760. &NameResult
  1761. );
  1762. if (DsStatus != ERROR_SUCCESS) {
  1763. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW returned 0x%lx.\n", DsStatus ));
  1764. Status = MspMapNtdsApiError( DsStatus, STATUS_NO_SUCH_DOMAIN );
  1765. goto Cleanup;
  1766. }
  1767. //
  1768. // Look for the name in the result
  1769. //
  1770. if (NameResult->cItems != 1) {
  1771. ASSERT(!"Not exactly one result returned, this can not happen");
  1772. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW returned not exactly 1 item\n" ));
  1773. Status = STATUS_INTERNAL_ERROR;
  1774. goto Cleanup;
  1775. }
  1776. //
  1777. // if not cracked on DC, try GC if it is avaiable
  1778. //
  1779. if (NameResult->rItems[0].status == DS_NAME_ERROR_NOT_FOUND
  1780. || NameResult->rItems[0].status == DS_NAME_ERROR_DOMAIN_ONLY) {
  1781. if ( DCInfo != NULL ) {
  1782. if (DCInfo->Flags & DS_GC_FLAG) {
  1783. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW failed on GC %#x\n", NameResult->rItems[0].status ));
  1784. useSimpleCrackName = TRUE;
  1785. break;
  1786. }
  1787. NetApiBufferFree(DCInfo);
  1788. DCInfo = NULL;
  1789. }
  1790. DsGetDcNameFlags |= DS_GC_SERVER_REQUIRED;
  1791. NetStatus = DsGetDcNameW(
  1792. NULL, // no computername
  1793. DomainName,
  1794. NULL, // no domain guid
  1795. NULL, // no site name
  1796. DsGetDcNameFlags,
  1797. &DCInfo
  1798. );
  1799. if (NetStatus == NERR_Success) {
  1800. DcName = DCInfo->DomainControllerName;
  1801. continue; // try to crack name again with GC
  1802. } else {
  1803. SspPrint(( SSP_WARNING, "MspLm20ChangePassword: could not find GC %#x\n", NetStatus ));
  1804. useSimpleCrackName = TRUE; // crack name fails, use manual crack
  1805. break;
  1806. }
  1807. } else if (!DomainName && (NameResult->rItems[0].status == DS_NAME_ERROR_TRUST_REFERRAL)) { // follow referral only in the local forest
  1808. //
  1809. // always try GC in the remote forest, this assumes there must
  1810. // be at lest one GC in a forest
  1811. //
  1812. DcName = NULL;
  1813. DsGetDcNameFlags = DS_DIRECTORY_SERVICE_REQUIRED | DS_RETURN_DNS_NAME | DS_GC_SERVER_REQUIRED;
  1814. DomainName = NameResult->rItems[0].pDomain;
  1815. continue; // try again to follow the referral path
  1816. } else if (NameResult->rItems[0].status != DS_NAME_NO_ERROR) {
  1817. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW failed %#x\n", NameResult->rItems[0].status ));
  1818. useSimpleCrackName = TRUE; // crack name fails, use manual crack
  1819. break;
  1820. }
  1821. //
  1822. // crack name succeeded, break out
  1823. //
  1824. ASSERT(useSimpleCrackName == FALSE);
  1825. break;
  1826. }
  1827. if (DsHandle != NULL) {
  1828. DsUnBindW(
  1829. &DsHandle
  1830. );
  1831. DsHandle = NULL;
  1832. }
  1833. if ( DCInfo != NULL ) {
  1834. NetApiBufferFree(DCInfo);
  1835. DCInfo = NULL;
  1836. }
  1837. if ( useSimpleCrackName ) { // crack name failed
  1838. SspPrint(( SSP_WARNING, "MspLm20ChangePassword: using simple crack\n" ));
  1839. //
  1840. // The name wasn't mapped. Try converting it manually by
  1841. // splitting it at the '@'
  1842. //
  1843. RtlInitUnicodeString(
  1844. &ClientName,
  1845. NameBuffer
  1846. );
  1847. // shortest possible is 3 Unicode chars (eg: a@a)
  1848. if (ClientName.Length < (sizeof(WCHAR) * 3)) {
  1849. Status = STATUS_NO_SUCH_USER;
  1850. goto Cleanup;
  1851. }
  1852. for (Index = (ClientName.Length / sizeof(WCHAR)) - 1; Index != 0 ; Index-- ) {
  1853. if (ClientName.Buffer[Index] == L'@') {
  1854. RtlInitUnicodeString(
  1855. &ClientDnsDomain,
  1856. &ClientName.Buffer[Index+1]
  1857. );
  1858. ClientName.Buffer[Index] = L'\0';
  1859. ClientName.Length = (USHORT) Index * sizeof(WCHAR);
  1860. break;
  1861. }
  1862. }
  1863. //
  1864. // If the name couldn't be parsed, give up and go home
  1865. //
  1866. if (ClientDnsDomain.Length == 0) {
  1867. Status = STATUS_NO_SUCH_USER;
  1868. goto Cleanup;
  1869. }
  1870. //
  1871. // This isn't really the Netbios Domain name, but it is the best we have.
  1872. //
  1873. ClientNetbiosDomain = ClientDnsDomain;
  1874. for (Index = 0; Index < (ClientNetbiosDomain.Length / sizeof(WCHAR)) ; Index++ ) {
  1875. //
  1876. // truncate the netbios domain to the first DOT
  1877. //
  1878. if ( ClientNetbiosDomain.Buffer[Index] == L'.' ) {
  1879. ClientNetbiosDomain.Length = (USHORT)(Index * sizeof(WCHAR));
  1880. ClientNetbiosDomain.MaximumLength = ClientNetbiosDomain.Length;
  1881. break;
  1882. }
  1883. }
  1884. }
  1885. else // crack name succeeded, look for the cracked results
  1886. {
  1887. RtlInitUnicodeString(
  1888. &ClientDnsDomain,
  1889. NameResult->rItems[0].pDomain
  1890. );
  1891. RtlInitUnicodeString(
  1892. &ClientName,
  1893. NameResult->rItems[0].pName
  1894. );
  1895. RtlInitUnicodeString(
  1896. &ClientNetbiosDomain,
  1897. NameResult->rItems[0].pName
  1898. );
  1899. //
  1900. // Move the pointer for the name up to the first "\" in the name
  1901. //
  1902. for (Index = 0; Index < ClientName.Length / sizeof(WCHAR) ; Index++ ) {
  1903. if (ClientName.Buffer[Index] == L'\\') {
  1904. RtlInitUnicodeString(
  1905. &ClientName,
  1906. &ClientName.Buffer[Index+1]
  1907. );
  1908. // Set the Netbios Domain Name to the string to the left of the backslash
  1909. ClientNetbiosDomain.Length = (USHORT)(Index * sizeof(WCHAR));
  1910. break;
  1911. }
  1912. }
  1913. }
  1914. SspPrint(( SSP_UPDATES, "MspLm20ChangePassword: UPN cracked %wZ\\%wZ, %wZ\n", &ClientNetbiosDomain, &ClientName, &ClientDnsDomain ));
  1915. } else {
  1916. ClientName = ChangePasswordRequest->AccountName;
  1917. ClientNetbiosDomain = ChangePasswordRequest->DomainName;
  1918. }
  1919. //
  1920. // If we're just changing the cached password, skip changing the password
  1921. // on the domain.
  1922. //
  1923. if ( ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ) {
  1924. Status = STATUS_SUCCESS;
  1925. Validated = FALSE;
  1926. goto PasswordChangeSuccessfull;
  1927. }
  1928. // Make sure that NlpSamInitialized is TRUE. If we logon using
  1929. // Kerberos, this may not be true.
  1930. if ( !NlpSamInitialized)
  1931. {
  1932. Status = NlSamInitialize( SAM_STARTUP_TIME );
  1933. if (!NT_SUCCESS(Status))
  1934. {
  1935. goto Cleanup;
  1936. }
  1937. }
  1938. //
  1939. // Check to see if the name provided is a domain name. If it has no
  1940. // leading "\\" and does not match the name of the computer, it may be.
  1941. //
  1942. if ((( ClientNetbiosDomain.Length < 3 * sizeof(WCHAR)) ||
  1943. ( ClientNetbiosDomain.Buffer[0] != L'\\' &&
  1944. ClientNetbiosDomain.Buffer[1] != L'\\' ) ) &&
  1945. !RtlEqualDomainName(
  1946. &NlpComputerName,
  1947. &ClientNetbiosDomain )) {
  1948. //
  1949. // Check if we are a DC in this domain.
  1950. // If so, use this DC.
  1951. //
  1952. if ( !NlpWorkstation &&
  1953. RtlEqualDomainName(
  1954. &NlpSamDomainName,
  1955. &ClientNetbiosDomain )) {
  1956. DCNameString = NlpComputerName;
  1957. }
  1958. if (DCNameString.Buffer == NULL) {
  1959. if ( ClientDnsDomain.Length != 0 ) {
  1960. ClientDsGetDcDomain = &ClientDnsDomain;
  1961. } else {
  1962. ClientDsGetDcDomain = &ClientNetbiosDomain;
  1963. }
  1964. //
  1965. // Build a zero terminated domain name.
  1966. //
  1967. DomainName = I_NtLmAllocate(
  1968. ClientDsGetDcDomain->Length + sizeof(WCHAR)
  1969. );
  1970. if ( DomainName == NULL ) {
  1971. Status = STATUS_INSUFFICIENT_RESOURCES;
  1972. goto Cleanup;
  1973. }
  1974. RtlCopyMemory( DomainName,
  1975. ClientDsGetDcDomain->Buffer,
  1976. ClientDsGetDcDomain->Length );
  1977. DomainName[ClientDsGetDcDomain->Length / sizeof(WCHAR)] = 0;
  1978. NetStatus = DsGetDcNameW(
  1979. NULL,
  1980. DomainName,
  1981. NULL, // no domain guid
  1982. NULL, // no site name
  1983. DS_WRITABLE_REQUIRED,
  1984. &DCInfo );
  1985. if ( NetStatus != NERR_Success ) {
  1986. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW %ws returned %#x\n", DomainName, NetStatus ));
  1987. Status = NetpApiStatusToNtStatus( NetStatus );
  1988. if ( Status == STATUS_INTERNAL_ERROR )
  1989. Status = STATUS_NO_SUCH_DOMAIN;
  1990. } else {
  1991. RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
  1992. }
  1993. //
  1994. // ISSUE: attempting DC rediscovery when DsGetDcName failed or
  1995. // password change failed non authoritatively this seemed to be
  1996. // wrong
  1997. //
  1998. AttemptRediscovery = TRUE;
  1999. }
  2000. if (NT_SUCCESS(Status)) {
  2001. Status = MspChangePassword(
  2002. &DCNameString,
  2003. &ClientName,
  2004. &ChangePasswordRequest->OldPassword,
  2005. &ChangePasswordRequest->NewPassword,
  2006. ClientRequest,
  2007. ChangePasswordRequest->Impersonating,
  2008. &DomainPasswordInfo,
  2009. NULL,
  2010. &Authoritative );
  2011. //
  2012. // If we succeeded or got back an authoritative answer
  2013. if ( NT_SUCCESS(Status) || Authoritative) {
  2014. goto PasswordChangeSuccessfull;
  2015. }
  2016. }
  2017. }
  2018. //
  2019. // Free the DC info so we can call DsGetDcName again.
  2020. //
  2021. if ( DCInfo != NULL ) {
  2022. NetApiBufferFree(DCInfo);
  2023. DCInfo = NULL;
  2024. }
  2025. //
  2026. // attempt re-discovery.
  2027. //
  2028. if ( AttemptRediscovery ) {
  2029. NetStatus = DsGetDcNameW(
  2030. NULL,
  2031. DomainName,
  2032. NULL, // no domain guid
  2033. NULL, // no site name
  2034. DS_FORCE_REDISCOVERY | DS_WRITABLE_REQUIRED,
  2035. &DCInfo );
  2036. if ( NetStatus != NERR_Success ) {
  2037. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW (re-discover) %ws returned %#x\n", DomainName, NetStatus ));
  2038. DCInfo = NULL;
  2039. Status = NetpApiStatusToNtStatus( NetStatus );
  2040. if ( Status == STATUS_INTERNAL_ERROR )
  2041. Status = STATUS_NO_SUCH_DOMAIN;
  2042. } else {
  2043. RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
  2044. Status = MspChangePassword(
  2045. &DCNameString,
  2046. &ClientName,
  2047. &ChangePasswordRequest->OldPassword,
  2048. &ChangePasswordRequest->NewPassword,
  2049. ClientRequest,
  2050. ChangePasswordRequest->Impersonating,
  2051. &DomainPasswordInfo,
  2052. NULL,
  2053. &Authoritative );
  2054. //
  2055. // If we succeeded or got back an authoritative answer
  2056. if ( NT_SUCCESS(Status) || Authoritative) {
  2057. goto PasswordChangeSuccessfull;
  2058. }
  2059. //
  2060. // Free the DC info so we can call DsGetDcName again.
  2061. //
  2062. if ( DCInfo != NULL ) {
  2063. NetApiBufferFree(DCInfo);
  2064. DCInfo = NULL;
  2065. }
  2066. }
  2067. }
  2068. if (Status != STATUS_BACKUP_CONTROLLER) {
  2069. //
  2070. // Change the password assuming the DomainName is really a server name
  2071. //
  2072. // The domain name is overloaded to be either a domain name or a server
  2073. // name. The server name is useful when changing the password on a LM2.x
  2074. // standalone server, which is a "member" of a domain but uses a private
  2075. // account database.
  2076. //
  2077. Status = MspChangePassword(
  2078. &ClientNetbiosDomain,
  2079. &ClientName,
  2080. &ChangePasswordRequest->OldPassword,
  2081. &ChangePasswordRequest->NewPassword,
  2082. ClientRequest,
  2083. ChangePasswordRequest->Impersonating,
  2084. &DomainPasswordInfo,
  2085. &PrimaryDomainInfo,
  2086. &Authoritative );
  2087. //
  2088. // If DomainName is actually a server name,
  2089. // just return the status to the caller.
  2090. //
  2091. if ( Authoritative &&
  2092. ( Status != STATUS_BAD_NETWORK_PATH ||
  2093. ( ClientNetbiosDomain.Length >= 3 * sizeof(WCHAR) &&
  2094. ClientNetbiosDomain.Buffer[0] == L'\\' &&
  2095. ClientNetbiosDomain.Buffer[1] == L'\\' ) ) ) {
  2096. //
  2097. // If \\xxx was specified, but xxx doesn't exist,
  2098. // return the status code that the DomainName field is bad.
  2099. //
  2100. if ( Status == STATUS_BAD_NETWORK_PATH ) {
  2101. Status = STATUS_NO_SUCH_DOMAIN;
  2102. }
  2103. }
  2104. //
  2105. // If we didn't get an error that this was a backup controller,
  2106. // we are out of here.
  2107. //
  2108. if (Status != STATUS_BACKUP_CONTROLLER) {
  2109. goto PasswordChangeSuccessfull;
  2110. }
  2111. }
  2112. //
  2113. // If the specified machine was a BDC in a domain,
  2114. // Pretend the caller passed us the domain name in the first place.
  2115. //
  2116. if ( Status == STATUS_BACKUP_CONTROLLER && PrimaryDomainInfo != NULL ) {
  2117. ClientNetbiosDomain = PrimaryDomainInfo->Name;
  2118. Status = STATUS_BAD_NETWORK_PATH;
  2119. } else {
  2120. goto PasswordChangeSuccessfull;
  2121. }
  2122. //
  2123. // Build a zero terminated domain name.
  2124. //
  2125. // BUGBUG: Should really pass both names to internal version of DsGetDcName
  2126. if ( ClientDnsDomain.Length != 0 ) {
  2127. ClientDsGetDcDomain = &ClientDnsDomain;
  2128. } else {
  2129. ClientDsGetDcDomain = &ClientNetbiosDomain;
  2130. }
  2131. if ( DomainName )
  2132. {
  2133. I_NtLmFree( DomainName );
  2134. }
  2135. DomainName = I_NtLmAllocate(
  2136. ClientDsGetDcDomain->Length + sizeof(WCHAR)
  2137. );
  2138. if ( DomainName == NULL ) {
  2139. Status = STATUS_INSUFFICIENT_RESOURCES;
  2140. goto Cleanup;
  2141. }
  2142. RtlCopyMemory( DomainName,
  2143. ClientDsGetDcDomain->Buffer,
  2144. ClientDsGetDcDomain->Length );
  2145. DomainName[ClientDsGetDcDomain->Length / sizeof(WCHAR)] = 0;
  2146. AttemptRediscovery = FALSE;
  2147. retry:
  2148. {
  2149. DWORD dwGetDcFlags = 0;
  2150. if ( AttemptRediscovery )
  2151. dwGetDcFlags |= DS_FORCE_REDISCOVERY;
  2152. //
  2153. // Determine the PDC of the named domain so we can change the password there.
  2154. //
  2155. NetStatus = DsGetDcNameW(
  2156. NULL,
  2157. DomainName,
  2158. NULL, // no domain guid
  2159. NULL, // no site name
  2160. dwGetDcFlags | DS_WRITABLE_REQUIRED,
  2161. &DCInfo );
  2162. if ( NetStatus != NERR_Success ) {
  2163. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW (retry) %ws, dwGetDcFlags %#x returned %#x\n", DomainName, dwGetDcFlags, NetStatus));
  2164. Status = NetpApiStatusToNtStatus( NetStatus );
  2165. if ( Status == STATUS_INTERNAL_ERROR )
  2166. Status = STATUS_NO_SUCH_DOMAIN;
  2167. goto Cleanup;
  2168. }
  2169. RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
  2170. Status = MspChangePassword(
  2171. &DCNameString,
  2172. &ClientName,
  2173. &ChangePasswordRequest->OldPassword,
  2174. &ChangePasswordRequest->NewPassword,
  2175. ClientRequest,
  2176. ChangePasswordRequest->Impersonating,
  2177. &DomainPasswordInfo,
  2178. NULL,
  2179. &Authoritative );
  2180. if ( !NT_SUCCESS(Status) && !Authoritative && !AttemptRediscovery ) {
  2181. //
  2182. // ISSUE: only do rediscovery if the DC is not available, seem to
  2183. // be over-reactive here
  2184. //
  2185. AttemptRediscovery = TRUE;
  2186. goto retry;
  2187. }
  2188. }
  2189. PasswordChangeSuccessfull:
  2190. //
  2191. // Allocate and initialize the response buffer.
  2192. //
  2193. SavedStatus = Status;
  2194. *ReturnBufferSize = sizeof(MSV1_0_CHANGEPASSWORD_RESPONSE);
  2195. Status = NlpAllocateClientBuffer( &ClientBufferDesc,
  2196. sizeof(MSV1_0_CHANGEPASSWORD_RESPONSE),
  2197. *ReturnBufferSize );
  2198. if ( !NT_SUCCESS( Status ) ) {
  2199. KdPrint(("MSV1_0: MspLm20ChangePassword: cannot alloc client buffer\n" ));
  2200. *ReturnBufferSize = 0;
  2201. goto Cleanup;
  2202. }
  2203. ChangePasswordResponse = (PMSV1_0_CHANGEPASSWORD_RESPONSE) ClientBufferDesc.MsvBuffer;
  2204. ChangePasswordResponse->MessageType = MsV1_0ChangePassword;
  2205. //
  2206. // Copy the DomainPassword restrictions out to the caller depending on
  2207. // whether it was passed to us.
  2208. //
  2209. // Mark the buffer as valid or invalid to let the caller know.
  2210. //
  2211. // if STATUS_PASSWORD_RESTRICTION is returned. This status can be
  2212. // returned by either SAM or a down-level change. Only SAM will return
  2213. // valid data so we have a flag in the buffer that says whether the data
  2214. // is valid or not.
  2215. //
  2216. if ( DomainPasswordInfo == NULL ) {
  2217. ChangePasswordResponse->PasswordInfoValid = FALSE;
  2218. } else {
  2219. ChangePasswordResponse->DomainPasswordInfo = *DomainPasswordInfo;
  2220. ChangePasswordResponse->PasswordInfoValid = TRUE;
  2221. }
  2222. //
  2223. // Flush the buffer to the client's address space.
  2224. //
  2225. Status = NlpFlushClientBuffer( &ClientBufferDesc,
  2226. ProtocolReturnBuffer );
  2227. //
  2228. // Update cached credentials with the new password.
  2229. //
  2230. // This is done by calling NlpChangePassword,
  2231. // which takes encrypted passwords, so encrypt 'em.
  2232. //
  2233. if ( NT_SUCCESS(SavedStatus) ) {
  2234. BOOLEAN Impersonating;
  2235. NTSTATUS TempStatus;
  2236. //
  2237. // Failure of NlpChangePassword is OK, that means that the
  2238. // account we've been working with isn't the one we're
  2239. // caching credentials for.
  2240. //
  2241. TempStatus = NlpChangePassword(
  2242. Validated,
  2243. &ClientNetbiosDomain,
  2244. &ClientName,
  2245. &ChangePasswordRequest->NewPassword
  2246. );
  2247. //
  2248. // for ChangeCachedPassword, set the ProtocolStatus if an error
  2249. // occured updating.
  2250. //
  2251. if ( !NT_SUCCESS(TempStatus) &&
  2252. (ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword)
  2253. )
  2254. {
  2255. SavedStatus = TempStatus;
  2256. //
  2257. // STATUS_PRIVILEGE_NOT_HELD means the caller is not allowed to change
  2258. // cached passwords, if so bail out now
  2259. //
  2260. if (STATUS_PRIVILEGE_NOT_HELD == SavedStatus)
  2261. {
  2262. Status = SavedStatus;
  2263. goto Cleanup;
  2264. }
  2265. }
  2266. //
  2267. // Notify the LSA itself of the password change
  2268. //
  2269. Impersonating = FALSE;
  2270. if ( ChangePasswordRequest->Impersonating ) {
  2271. TempStatus = Lsa.ImpersonateClient();
  2272. if ( NT_SUCCESS(TempStatus)) {
  2273. Impersonating = TRUE;
  2274. }
  2275. }
  2276. LsaINotifyPasswordChanged(
  2277. &ClientNetbiosDomain,
  2278. &ClientName,
  2279. ClientDnsDomain.Length == 0 ? NULL : &ClientDnsDomain,
  2280. ClientUpn.Length == 0 ? NULL : &ClientUpn,
  2281. ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ?
  2282. NULL :
  2283. &ChangePasswordRequest->OldPassword,
  2284. &ChangePasswordRequest->NewPassword,
  2285. Impersonating );
  2286. if ( Impersonating ) {
  2287. RevertToSelf();
  2288. }
  2289. }
  2290. Status = SavedStatus;
  2291. Cleanup:
  2292. //
  2293. // Free Locally allocated resources
  2294. //
  2295. if (DomainName != NULL) {
  2296. I_NtLmFree(DomainName);
  2297. }
  2298. if ( DCInfo != NULL ) {
  2299. NetApiBufferFree(DCInfo);
  2300. }
  2301. if ( WkstaInfo100 != NULL ) {
  2302. NetApiBufferFree(WkstaInfo100);
  2303. }
  2304. if ( DomainPasswordInfo != NULL ) {
  2305. SamFreeMemory(DomainPasswordInfo);
  2306. }
  2307. if ( PrimaryDomainInfo != NULL ) {
  2308. (VOID) LsaFreeMemory( PrimaryDomainInfo );
  2309. }
  2310. if (NameResult) {
  2311. DsFreeNameResult(NameResult);
  2312. }
  2313. if ( DsHandle ) {
  2314. DsUnBindW(
  2315. &DsHandle
  2316. );
  2317. }
  2318. if (SpnForDC) {
  2319. NtLmFreePrivateHeap(SpnForDC);
  2320. }
  2321. //
  2322. // Free Policy Server Role Information if used.
  2323. //
  2324. if (PolicyLsaServerRoleInfo != NULL) {
  2325. I_LsaIFree_LSAPR_POLICY_INFORMATION(
  2326. PolicyLsaServerRoleInformation,
  2327. (PLSAPR_POLICY_INFORMATION) PolicyLsaServerRoleInfo
  2328. );
  2329. }
  2330. //
  2331. // Free the return buffer.
  2332. //
  2333. NlpFreeClientBuffer( &ClientBufferDesc );
  2334. //
  2335. // Don't let the password stay in the page file.
  2336. //
  2337. if ( PasswordBufferValidated ) {
  2338. RtlEraseUnicodeString( &ChangePasswordRequest->OldPassword );
  2339. RtlEraseUnicodeString( &ChangePasswordRequest->NewPassword );
  2340. }
  2341. //
  2342. // Flush the log to disk
  2343. //
  2344. MsvPaswdSetAndClearLog();
  2345. #if _WIN64
  2346. //
  2347. // Do this last since some of the cleanup code above may refer to addresses
  2348. // inside the pTempSubmitBuffer/ProtocolSubmitBuffer (e.g., erasing the old
  2349. // and new passwords, etc).
  2350. //
  2351. if (fAllocatedSubmitBuffer)
  2352. {
  2353. NtLmFreePrivateHeap( pTempSubmitBuffer );
  2354. }
  2355. #endif // _WIN64
  2356. //
  2357. // Return status to the caller.
  2358. //
  2359. *ProtocolStatus = Status;
  2360. return STATUS_SUCCESS;
  2361. }