Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2219 lines
61 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. MspImpersonateAnonymous(
  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 RevertToSelf() 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. RevertToSelf();
  39. if(!ImpersonateAnonymousToken( GetCurrentThread() ))
  40. {
  41. return STATUS_CANNOT_IMPERSONATE;
  42. }
  43. return STATUS_SUCCESS;
  44. }
  45. NTSTATUS
  46. MspAddBackslashesComputerName(
  47. IN PUNICODE_STRING ComputerName,
  48. OUT PUNICODE_STRING UncComputerName
  49. )
  50. /*++
  51. Routine Description:
  52. This function makes a copy of a Computer Name, prepending backslashes
  53. if they are not already present.
  54. Arguments:
  55. ComputerName - Pointer to Computer Name without backslashes.
  56. UncComputerName - Pointer to Unicode String structure that will be
  57. initialized to reference the computerName with backslashes
  58. prepended if not already present. The Unicode Buffer will be
  59. terminated with a Unicode NULL, so that it can be passed as
  60. a parameter to routines expecting a null terminated Wide String.
  61. When this string is finished with, the caller must free its
  62. memory via RtlFreeHeap.
  63. --*/
  64. {
  65. NTSTATUS Status = STATUS_SUCCESS;
  66. BOOLEAN HasBackslashes = FALSE;
  67. BOOLEAN IsNullTerminated = FALSE;
  68. USHORT OutputNameLength;
  69. USHORT OutputNameMaximumLength;
  70. PWSTR StartBuffer = NULL;
  71. //
  72. // If the computername is NULL, a zero length string, or the name already begins with
  73. // backslashes and is wide char null terminated, just use it unmodified.
  74. //
  75. if( (!ARGUMENT_PRESENT(ComputerName)) || ComputerName->Length == 0 ) {
  76. UncComputerName->Buffer = NULL;
  77. UncComputerName->Length = 0;
  78. UncComputerName->MaximumLength = 0;
  79. goto AddBackslashesComputerNameFinish;
  80. }
  81. //
  82. // Name is not NULL or zero length. Check if name already has
  83. // backslashes and a trailing Unicode Null
  84. //
  85. OutputNameLength = ComputerName->Length + (2 * sizeof(WCHAR));
  86. OutputNameMaximumLength = OutputNameLength + sizeof(WCHAR);
  87. if ((ComputerName && ComputerName->Length >= 2 * sizeof(WCHAR)) &&
  88. (ComputerName->Buffer[0] == L'\\') &&
  89. (ComputerName->Buffer[1] == L'\\')) {
  90. HasBackslashes = TRUE;
  91. OutputNameLength -= (2 * sizeof(WCHAR));
  92. OutputNameMaximumLength -= (2 * sizeof(WCHAR));
  93. }
  94. if ((ComputerName->Length + (USHORT) sizeof(WCHAR) <= ComputerName->MaximumLength) &&
  95. (ComputerName->Buffer[ComputerName->Length/sizeof(WCHAR)] == UNICODE_NULL)) {
  96. IsNullTerminated = TRUE;
  97. }
  98. if (HasBackslashes && IsNullTerminated) {
  99. *UncComputerName = *ComputerName;
  100. goto AddBackslashesComputerNameFinish;
  101. }
  102. //
  103. // Name either does not have backslashes or is not NULL terminated.
  104. // Make a copy with leading backslashes and a wide NULL terminator.
  105. //
  106. UncComputerName->Length = OutputNameLength;
  107. UncComputerName->MaximumLength = OutputNameMaximumLength;
  108. UncComputerName->Buffer = I_NtLmAllocate(
  109. OutputNameMaximumLength
  110. );
  111. if (UncComputerName->Buffer == NULL) {
  112. KdPrint(("MspAddBackslashes...: Out of memory copying ComputerName.\n"));
  113. Status = STATUS_NO_MEMORY;
  114. goto AddBackslashesComputerNameError;
  115. }
  116. StartBuffer = UncComputerName->Buffer;
  117. if (!HasBackslashes) {
  118. UncComputerName->Buffer[0] = UncComputerName->Buffer[1] = L'\\';
  119. StartBuffer +=2;
  120. }
  121. RtlCopyMemory(
  122. StartBuffer,
  123. ComputerName->Buffer,
  124. ComputerName->Length
  125. );
  126. UncComputerName->Buffer[UncComputerName->Length / sizeof(WCHAR)] = UNICODE_NULL;
  127. AddBackslashesComputerNameFinish:
  128. return(Status);
  129. AddBackslashesComputerNameError:
  130. goto AddBackslashesComputerNameFinish;
  131. }
  132. #ifndef DONT_LOG_PASSWORD_CHANGES
  133. #include <stdio.h>
  134. HANDLE MsvPaswdLogFile = NULL;
  135. #define MSVPASWD_LOGNAME L"\\debug\\PASSWD.LOG"
  136. #define MSVPASWD_BAKNAME L"\\debug\\PASSWD.BAK"
  137. ULONG
  138. MsvPaswdInitializeLog(
  139. VOID
  140. )
  141. /*++
  142. Routine Description:
  143. Initializes the debugging log file used by DCPROMO and the dssetup apis
  144. Arguments:
  145. None
  146. Returns:
  147. ERROR_SUCCESS - Success
  148. --*/
  149. {
  150. ULONG dwErr = ERROR_SUCCESS;
  151. WCHAR LogFileName[ MAX_PATH + 1 ], BakFileName[ MAX_PATH + 1 ];
  152. if ( !GetWindowsDirectoryW( LogFileName,
  153. sizeof( LogFileName )/sizeof( WCHAR ) ) ) {
  154. dwErr = GetLastError();
  155. } else {
  156. wcscpy( BakFileName, LogFileName );
  157. wcscat( LogFileName, MSVPASWD_LOGNAME );
  158. wcscat( BakFileName, MSVPASWD_BAKNAME );
  159. //
  160. // Copy the existing (maybe) log file to a backup
  161. //
  162. //if ( CopyFile( LogFileName, BakFileName, FALSE ) == FALSE ) {
  163. //
  164. // }
  165. MsvPaswdLogFile = CreateFileW( LogFileName,
  166. GENERIC_WRITE,
  167. FILE_SHARE_READ | FILE_SHARE_WRITE,
  168. NULL,
  169. CREATE_ALWAYS,
  170. FILE_ATTRIBUTE_NORMAL,
  171. NULL );
  172. if ( MsvPaswdLogFile == INVALID_HANDLE_VALUE ) {
  173. dwErr = GetLastError();
  174. MsvPaswdLogFile = NULL;
  175. } else {
  176. if( SetFilePointer( MsvPaswdLogFile,
  177. 0, 0,
  178. FILE_END ) == 0xFFFFFFFF ) {
  179. dwErr = GetLastError();
  180. CloseHandle( MsvPaswdLogFile );
  181. MsvPaswdLogFile = NULL;
  182. }
  183. }
  184. }
  185. return( dwErr );
  186. }
  187. ULONG
  188. MsvPaswdCloseLog(
  189. VOID
  190. )
  191. /*++
  192. Routine Description:
  193. Closes the debugging log file used by DCPROMO and the dssetup apis
  194. Arguments:
  195. None
  196. Returns:
  197. ERROR_SUCCESS - Success
  198. --*/
  199. {
  200. ULONG dwErr = ERROR_SUCCESS;
  201. if ( MsvPaswdLogFile != NULL ) {
  202. CloseHandle( MsvPaswdLogFile );
  203. MsvPaswdLogFile = NULL;
  204. }
  205. return( dwErr );
  206. }
  207. //
  208. // Stolen and hacked up from netlogon code
  209. //
  210. VOID
  211. MsvPaswdDebugDumpRoutine(
  212. IN LPSTR Format,
  213. va_list arglist
  214. )
  215. {
  216. char OutputBuffer[2049];
  217. ULONG length;
  218. ULONG BytesWritten;
  219. SYSTEMTIME SystemTime;
  220. static BeginningOfLine = TRUE;
  221. int cbUsed = 0;
  222. //
  223. // If we don't have an open log file, just bail
  224. //
  225. if ( MsvPaswdLogFile == NULL ) {
  226. return;
  227. }
  228. length = 0;
  229. OutputBuffer[sizeof(OutputBuffer) - 1] = '\0';
  230. //
  231. // Handle the beginning of a new line.
  232. //
  233. //
  234. if ( BeginningOfLine ) {
  235. //
  236. // If we're writing to the debug terminal,
  237. // indicate this is a Netlogon message.
  238. //
  239. //
  240. // Put the timestamp at the begining of the line.
  241. //
  242. GetLocalTime( &SystemTime );
  243. length += (ULONG) sprintf( &OutputBuffer[length],
  244. "%02u/%02u %02u:%02u:%02u ",
  245. SystemTime.wMonth,
  246. SystemTime.wDay,
  247. SystemTime.wHour,
  248. SystemTime.wMinute,
  249. SystemTime.wSecond );
  250. }
  251. //
  252. // Put a the information requested by the caller onto the line
  253. //
  254. // save two chars of spaces for the EOLs
  255. //
  256. cbUsed = (ULONG) _vsnprintf(&OutputBuffer[length], sizeof(OutputBuffer) - length - 1 - 2, Format, arglist);
  257. if (cbUsed >= 0)
  258. {
  259. length += cbUsed;
  260. }
  261. BeginningOfLine = (length > 0 && OutputBuffer[length-1] == '\n' );
  262. if ( BeginningOfLine ) {
  263. OutputBuffer[length-1] = '\r';
  264. OutputBuffer[length] = '\n';
  265. OutputBuffer[length+1] = '\0';
  266. length++;
  267. }
  268. ASSERT( length <= sizeof( OutputBuffer ) / sizeof( CHAR ) );
  269. //
  270. // Write the debug info to the log file.
  271. //
  272. if ( !WriteFile( MsvPaswdLogFile,
  273. OutputBuffer,
  274. length,
  275. &BytesWritten,
  276. NULL ) ) {
  277. }
  278. }
  279. VOID
  280. MsvPaswdLogPrintRoutine(
  281. IN LPSTR Format,
  282. ...
  283. )
  284. {
  285. va_list arglist;
  286. va_start(arglist, Format);
  287. MsvPaswdDebugDumpRoutine( Format, arglist );
  288. va_end(arglist);
  289. }
  290. ULONG
  291. MsvPaswdSetAndClearLog(
  292. VOID
  293. )
  294. /*++
  295. Routine Description:
  296. Flushes the log and seeks to the end of the file
  297. Arguments:
  298. None
  299. Returns:
  300. ERROR_SUCCESS - Success
  301. --*/
  302. {
  303. ULONG dwErr = ERROR_SUCCESS;
  304. if ( MsvPaswdLogFile != NULL ) {
  305. if( FlushFileBuffers( MsvPaswdLogFile ) == FALSE ) {
  306. dwErr = GetLastError();
  307. }
  308. }
  309. return( dwErr );
  310. }
  311. #endif // DONT_LOG_PASSWORD_CHANGES
  312. NTSTATUS
  313. MspChangePasswordSam(
  314. IN PUNICODE_STRING UncComputerName,
  315. IN PUNICODE_STRING UserName,
  316. IN PUNICODE_STRING OldPassword,
  317. IN PUNICODE_STRING NewPassword,
  318. IN PLSA_CLIENT_REQUEST ClientRequest,
  319. IN BOOLEAN Impersonating,
  320. OUT PDOMAIN_PASSWORD_INFORMATION *DomainPasswordInfo,
  321. OUT PPOLICY_PRIMARY_DOMAIN_INFO *PrimaryDomainInfo OPTIONAL,
  322. OUT PBOOLEAN Authoritative
  323. )
  324. /*++
  325. Routine Description:
  326. This routine is called by MspChangePassword to change the password
  327. on a Windows NT machine.
  328. Arguments:
  329. UncComputerName - Name of the target machine. This name must begin with
  330. two backslashes.
  331. UserName - Name of the user to change password for.
  332. OldPassword - Plaintext current password.
  333. NewPassword - Plaintext replacement password.
  334. ClientRequest - Is a pointer to an opaque data structure
  335. representing the client's request.
  336. DomainPasswordInfo - Password restriction information (returned only if
  337. status is STATUS_PASSWORD_RESTRICTION).
  338. PrimaryDomainInfo - DomainNameInformation (returned only if status is
  339. STATUS_BACKUP_CONTROLLER).
  340. Authoritative - The failure was authoritative and no retries should be
  341. made.
  342. Return Value:
  343. STATUS_SUCCESS - Indicates the service completed successfully.
  344. ...
  345. --*/
  346. {
  347. NTSTATUS Status;
  348. OBJECT_ATTRIBUTES ObjectAttributes;
  349. SECURITY_QUALITY_OF_SERVICE SecurityQos;
  350. SAM_HANDLE SamHandle = NULL;
  351. SAM_HANDLE DomainHandle = NULL;
  352. LSA_HANDLE LSAPolicyHandle = NULL;
  353. OBJECT_ATTRIBUTES LSAObjectAttributes;
  354. PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = NULL;
  355. BOOLEAN ImpersonatingAnonymous = FALSE;
  356. UNREFERENCED_PARAMETER(ClientRequest);
  357. //
  358. // Assume all failures are authoritative
  359. //
  360. *Authoritative = TRUE;
  361. //
  362. // If we're impersonating (ie, winlogon impersonated its caller before calling us),
  363. // impersonate again. This allows us to get the name of the caller for auditing.
  364. //
  365. if ( Impersonating ) {
  366. Status = Lsa.ImpersonateClient();
  367. } else {
  368. BOOLEAN AvoidAnonymous = FALSE;
  369. //
  370. // Since the System context is a member of the Administrators alias,
  371. // when we connect with the local SAM we come in as an Administrator.
  372. // (When it's remote, we go over the null session and so have very
  373. // low access). We don't want to be an Administrator because that
  374. // would allow the user to change the password on an account whose
  375. // ACL prohibits the user from changing the password. So we'll
  376. // temporarily impersonate ourself and disable the Administrators
  377. // alias in the impersonation token.
  378. //
  379. //
  380. // Don't impersonateAnonymous if BLANKPWD flag is set
  381. // AND the change is for the local machine.
  382. //
  383. AvoidAnonymous = ((NtLmCheckProcessOption( MSV1_0_OPTION_ALLOW_BLANK_PASSWORD ) & MSV1_0_OPTION_ALLOW_BLANK_PASSWORD) != 0);
  384. if( AvoidAnonymous )
  385. {
  386. UNICODE_STRING ComputerName;
  387. AvoidAnonymous = FALSE;
  388. ComputerName = *UncComputerName;
  389. if( ComputerName.Length > 4 &&
  390. ComputerName.Buffer[0] == L'\\' &&
  391. ComputerName.Buffer[1] == L'\\' )
  392. {
  393. ComputerName.Buffer += 2;
  394. ComputerName.Length -= 2 * sizeof(WCHAR);
  395. }
  396. if( NlpSamDomainName.Buffer )
  397. {
  398. AvoidAnonymous = RtlEqualUnicodeString(
  399. &ComputerName,
  400. &NlpSamDomainName,
  401. TRUE
  402. );
  403. }
  404. if( !AvoidAnonymous )
  405. {
  406. RtlAcquireResourceShared(&NtLmGlobalCritSect, TRUE);
  407. AvoidAnonymous = RtlEqualUnicodeString(
  408. &ComputerName,
  409. &NtLmGlobalUnicodeComputerNameString,
  410. TRUE
  411. );
  412. RtlReleaseResource(&NtLmGlobalCritSect);
  413. }
  414. }
  415. if( AvoidAnonymous )
  416. {
  417. Status = STATUS_SUCCESS;
  418. ImpersonatingAnonymous = FALSE;
  419. } else {
  420. Status = MspImpersonateAnonymous();
  421. ImpersonatingAnonymous = TRUE;
  422. }
  423. }
  424. if (!NT_SUCCESS( Status )) {
  425. goto Cleanup;
  426. }
  427. try
  428. {
  429. Status = SamChangePasswordUser2(
  430. UncComputerName,
  431. UserName,
  432. OldPassword,
  433. NewPassword
  434. );
  435. }
  436. except (EXCEPTION_EXECUTE_HANDLER)
  437. {
  438. Status = GetExceptionCode();
  439. }
  440. MsvPaswdLogPrint(("SamChangePasswordUser2 on machine %wZ for user %wZ returned 0x%x\n",
  441. UncComputerName,
  442. UserName,
  443. Status
  444. ));
  445. if ( !NT_SUCCESS(Status) ) {
  446. #ifdef COMPILED_BY_DEVELOPER
  447. KdPrint(("MspChangePasswordSam: SamChangePasswordUser2(%wZ) failed, status %x\n",
  448. UncComputerName, Status));
  449. #endif // COMPILED_BY_DEVELOPER
  450. //
  451. // If we failed to connect and we were impersonating a client
  452. // then we may want to try again using the NULL session.
  453. // Only try this if we found a server last try. Otherwise,
  454. // we'll subject our user to another long timeout.
  455. //
  456. if (( Impersonating ) &&
  457. ( Status != STATUS_WRONG_PASSWORD ) &&
  458. ( Status != STATUS_PASSWORD_RESTRICTION ) &&
  459. ( Status != STATUS_ACCOUNT_RESTRICTION ) &&
  460. ( Status != RPC_NT_SERVER_UNAVAILABLE) &&
  461. ( Status != STATUS_INVALID_DOMAIN_ROLE) ) {
  462. Status = MspImpersonateAnonymous();
  463. if (!NT_SUCCESS(Status)) {
  464. goto Cleanup;
  465. }
  466. ImpersonatingAnonymous = TRUE;
  467. Status = SamChangePasswordUser2(
  468. UncComputerName,
  469. UserName,
  470. OldPassword,
  471. NewPassword
  472. );
  473. MsvPaswdLogPrint(("SamChangePasswordUser2 retry on machine %wZ for user %wZ returned 0x%x\n",
  474. UncComputerName,
  475. UserName,
  476. Status
  477. ));
  478. #ifdef COMPILED_BY_DEVELOPER
  479. if ( !NT_SUCCESS(Status) ) {
  480. KdPrint(("MspChangePasswordSam: SamChangePasswordUser2(%wZ) (2nd attempt) failed, status %x\n",
  481. UncComputerName, Status));
  482. }
  483. #endif // COMPILED_BY_DEVELOPER
  484. }
  485. }
  486. //
  487. // if we are impersonating Anonymous, RevertToSelf, so the password policy
  488. // fetch attempt occurs using machine/system creds.
  489. //
  490. if( ImpersonatingAnonymous )
  491. {
  492. RevertToSelf();
  493. }
  494. if ( !NT_SUCCESS(Status) ) {
  495. #ifdef COMPILED_BY_DEVELOPER
  496. KdPrint(("MspChangePasswordSam: Cannot change password for %wZ, status %x\n",
  497. UserName, Status));
  498. #endif // COMPILED_BY_DEVELOPER
  499. if (Status == RPC_NT_SERVER_UNAVAILABLE ||
  500. Status == RPC_S_SERVER_UNAVAILABLE ) {
  501. Status = STATUS_CANT_ACCESS_DOMAIN_INFO;
  502. } else if (Status == STATUS_PASSWORD_RESTRICTION) {
  503. //
  504. // Get the password restrictions for this domain and return them
  505. //
  506. //
  507. // Get the SID of the account domain from LSA
  508. //
  509. InitializeObjectAttributes( &LSAObjectAttributes,
  510. NULL, // Name
  511. 0, // Attributes
  512. NULL, // Root
  513. NULL ); // Security Descriptor
  514. Status = LsaOpenPolicy( UncComputerName,
  515. &LSAObjectAttributes,
  516. POLICY_VIEW_LOCAL_INFORMATION,
  517. &LSAPolicyHandle );
  518. if( !NT_SUCCESS(Status) ) {
  519. KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
  520. UncComputerName, Status));
  521. LSAPolicyHandle = NULL;
  522. goto Cleanup;
  523. }
  524. Status = LsaQueryInformationPolicy(
  525. LSAPolicyHandle,
  526. PolicyAccountDomainInformation,
  527. (PVOID *) &AccountDomainInfo );
  528. if( !NT_SUCCESS(Status) ) {
  529. KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
  530. UncComputerName, Status));
  531. AccountDomainInfo = NULL;
  532. goto Cleanup;
  533. }
  534. //
  535. // Setup ObjectAttributes for SamConnect call.
  536. //
  537. InitializeObjectAttributes(&ObjectAttributes, NULL, 0, 0, NULL);
  538. ObjectAttributes.SecurityQualityOfService = &SecurityQos;
  539. SecurityQos.Length = sizeof(SecurityQos);
  540. SecurityQos.ImpersonationLevel = SecurityIdentification;
  541. SecurityQos.ContextTrackingMode = SECURITY_STATIC_TRACKING;
  542. SecurityQos.EffectiveOnly = FALSE;
  543. Status = SamConnect(
  544. UncComputerName,
  545. &SamHandle,
  546. SAM_SERVER_LOOKUP_DOMAIN,
  547. &ObjectAttributes
  548. );
  549. if ( !NT_SUCCESS(Status) ) {
  550. KdPrint(("MspChangePasswordSam: Cannot open sam on %wZ, status %x\n",
  551. UncComputerName, Status));
  552. DomainHandle = NULL;
  553. goto Cleanup;
  554. }
  555. //
  556. // Open the Account domain in SAM.
  557. //
  558. Status = SamOpenDomain(
  559. SamHandle,
  560. GENERIC_EXECUTE,
  561. AccountDomainInfo->DomainSid,
  562. &DomainHandle
  563. );
  564. if ( !NT_SUCCESS(Status) ) {
  565. KdPrint(("MspChangePasswordSam: Cannot open domain on %wZ, status %x\n",
  566. UncComputerName, Status));
  567. DomainHandle = NULL;
  568. goto Cleanup;
  569. }
  570. Status = SamQueryInformationDomain(
  571. DomainHandle,
  572. DomainPasswordInformation,
  573. (PVOID *)DomainPasswordInfo );
  574. if (!NT_SUCCESS(Status)) {
  575. *DomainPasswordInfo = NULL;
  576. } else {
  577. Status = STATUS_PASSWORD_RESTRICTION;
  578. }
  579. }
  580. goto Cleanup;
  581. }
  582. Cleanup:
  583. //
  584. // If the only problem is that this is a BDC,
  585. // Return the domain name back to the caller.
  586. //
  587. if ( (Status == STATUS_BACKUP_CONTROLLER ||
  588. Status == STATUS_INVALID_DOMAIN_ROLE) &&
  589. PrimaryDomainInfo != NULL ) {
  590. NTSTATUS TempStatus;
  591. //
  592. // Open the LSA if we haven't already.
  593. //
  594. if (LSAPolicyHandle == NULL) {
  595. InitializeObjectAttributes( &LSAObjectAttributes,
  596. NULL, // Name
  597. 0, // Attributes
  598. NULL, // Root
  599. NULL ); // Security Descriptor
  600. TempStatus = LsaOpenPolicy( UncComputerName,
  601. &LSAObjectAttributes,
  602. POLICY_VIEW_LOCAL_INFORMATION,
  603. &LSAPolicyHandle );
  604. if( !NT_SUCCESS(TempStatus) ) {
  605. KdPrint(("MspChangePasswordSam: LsaOpenPolicy(%wZ) failed, status %x\n",
  606. UncComputerName, TempStatus));
  607. LSAPolicyHandle = NULL;
  608. }
  609. }
  610. if (LSAPolicyHandle != NULL) {
  611. TempStatus = LsaQueryInformationPolicy(
  612. LSAPolicyHandle,
  613. PolicyPrimaryDomainInformation,
  614. (PVOID *) PrimaryDomainInfo );
  615. if( !NT_SUCCESS(TempStatus) ) {
  616. KdPrint(("MspChangePasswordSam: LsaQueryInformationPolicy(%wZ) failed, status %x\n",
  617. UncComputerName, TempStatus));
  618. *PrimaryDomainInfo = NULL;
  619. #ifdef COMPILED_BY_DEVELOPER
  620. } else {
  621. KdPrint(("MspChangePasswordSam: %wZ is really a BDC in domain %wZ\n",
  622. UncComputerName, &(*PrimaryDomainInfo)->Name));
  623. #endif // COMPILED_BY_DEVELOPER
  624. }
  625. }
  626. Status = STATUS_BACKUP_CONTROLLER;
  627. }
  628. //
  629. // Check for non-authoritative failures
  630. //
  631. if (( Status != STATUS_ACCESS_DENIED) &&
  632. ( Status != STATUS_WRONG_PASSWORD ) &&
  633. ( Status != STATUS_NO_SUCH_USER ) &&
  634. ( Status != STATUS_PASSWORD_RESTRICTION ) &&
  635. ( Status != STATUS_ACCOUNT_RESTRICTION ) &&
  636. ( Status != STATUS_INVALID_DOMAIN_ROLE )) {
  637. *Authoritative = FALSE;
  638. }
  639. //
  640. // Stop impersonating.
  641. //
  642. RevertToSelf();
  643. //
  644. // Free Locally used resources
  645. //
  646. if (SamHandle) {
  647. SamCloseHandle(SamHandle);
  648. }
  649. if (DomainHandle) {
  650. SamCloseHandle(DomainHandle);
  651. }
  652. if( LSAPolicyHandle != NULL ) {
  653. LsaClose( LSAPolicyHandle );
  654. }
  655. if ( AccountDomainInfo != NULL ) {
  656. (VOID) LsaFreeMemory( AccountDomainInfo );
  657. }
  658. return Status;
  659. }
  660. NTSTATUS
  661. MspChangePasswordDownlevel(
  662. IN PUNICODE_STRING UncComputerName,
  663. IN PUNICODE_STRING UserNameU,
  664. IN PUNICODE_STRING OldPasswordU,
  665. IN PUNICODE_STRING NewPasswordU,
  666. OUT PBOOLEAN Authoritative
  667. )
  668. /*++
  669. Routine Description:
  670. This routine is called by MspChangePassword to change the password
  671. on an OS/2 User-level server. First we try sending an encrypted
  672. request to the server, failing that we fall back on plaintext.
  673. Arguments:
  674. UncComputerName - Pointer to Unicode String containing the Name of the
  675. target machine. This name must begin with two backslashes and
  676. must be null terminated.
  677. UserNameU - Name of the user to change password for.
  678. OldPasswordU - Plaintext current password.
  679. NewPasswordU - Plaintext replacement password.
  680. Authoritative - If the attempt failed with an error that would
  681. otherwise cause the password attempt to fail, this flag, if false,
  682. indicates that the error was not authoritative and the attempt
  683. should proceed.
  684. Return Value:
  685. STATUS_SUCCESS - Indicates the service completed successfully.
  686. --*/
  687. {
  688. NTSTATUS Status;
  689. NET_API_STATUS NetStatus;
  690. DWORD Length;
  691. LPWSTR UserName = NULL;
  692. LPWSTR OldPassword = NULL;
  693. LPWSTR NewPassword = NULL;
  694. *Authoritative = TRUE;
  695. //
  696. // Convert UserName from UNICODE_STRING to null-terminated wide string
  697. // for use by RxNetUserPasswordSet.
  698. //
  699. Length = UserNameU->Length;
  700. UserName = I_NtLmAllocate(
  701. Length + sizeof(TCHAR)
  702. );
  703. if ( NULL == UserName ) {
  704. Status = STATUS_NO_MEMORY;
  705. goto Cleanup;
  706. }
  707. RtlCopyMemory( UserName, UserNameU->Buffer, Length );
  708. UserName[ Length / sizeof(TCHAR) ] = 0;
  709. //
  710. // Convert OldPassword from UNICODE_STRING to null-terminated wide string.
  711. //
  712. Length = OldPasswordU->Length;
  713. OldPassword = I_NtLmAllocate( Length + sizeof(TCHAR) );
  714. if ( NULL == OldPassword ) {
  715. Status = STATUS_NO_MEMORY;
  716. goto Cleanup;
  717. }
  718. RtlCopyMemory( OldPassword, OldPasswordU->Buffer, Length );
  719. OldPassword[ Length / sizeof(TCHAR) ] = 0;
  720. //
  721. // Convert NewPassword from UNICODE_STRING to null-terminated wide string.
  722. //
  723. Length = NewPasswordU->Length;
  724. NewPassword = I_NtLmAllocate( Length + sizeof(TCHAR) );
  725. if ( NULL == NewPassword ) {
  726. Status = STATUS_NO_MEMORY;
  727. goto Cleanup;
  728. }
  729. RtlCopyMemory( NewPassword, NewPasswordU->Buffer, Length );
  730. NewPassword[ Length / sizeof(TCHAR) ] = 0;
  731. #ifdef COMPILED_BY_DEVELOPER
  732. KdPrint(("MSV1_0: Changing password on downlevel server:\n"
  733. "\tUncComputerName: %wZ\n"
  734. "\tUserName: %ws\n"
  735. "\tOldPassword: %ws\n"
  736. "\tNewPassword: %ws\n",
  737. UncComputerName,
  738. UserName,
  739. OldPassword,
  740. NewPassword
  741. ));
  742. #endif // COMPILED_BY_DEVELOPER
  743. //
  744. // Attempt to change password on downlevel server.
  745. //
  746. NetStatus = RxNetUserPasswordSet(
  747. UncComputerName->Buffer,
  748. UserName,
  749. OldPassword,
  750. NewPassword);
  751. MsvPaswdLogPrint(("RxNetUserPasswordSet on machine %ws for user %ws returned 0x%x\n",
  752. UncComputerName->Buffer,
  753. UserName,
  754. NetStatus
  755. ));
  756. #ifdef COMPILED_BY_DEVELOPER
  757. KdPrint(("MSV1_0: RxNUserPasswordSet returns %d.\n", NetStatus));
  758. #endif // COMPILED_BY_DEVELOPER
  759. // Since we overload the computername as the domain name,
  760. // map NERR_InvalidComputer to STATUS_NO_SUCH_DOMAIN, since
  761. // that will give the user a nice error message.
  762. //
  763. // ERROR_PATH_NOT_FOUND is returned on a standalone workstation that
  764. // doesn't have the network installed.
  765. //
  766. if (NetStatus == NERR_InvalidComputer ||
  767. NetStatus == ERROR_PATH_NOT_FOUND) {
  768. Status = STATUS_NO_SUCH_DOMAIN;
  769. // ERROR_SEM_TIMEOUT can be returned when the computer name doesn't
  770. // exist.
  771. //
  772. // ERROR_REM_NOT_LIST can also be returned when the computer name
  773. // doesn't exist.
  774. //
  775. } else if ( NetStatus == ERROR_SEM_TIMEOUT ||
  776. NetStatus == ERROR_REM_NOT_LIST) {
  777. Status = STATUS_BAD_NETWORK_PATH;
  778. } else if ( (NetStatus == ERROR_INVALID_PARAMETER) &&
  779. ((wcslen(NewPassword) > LM20_PWLEN) ||
  780. (wcslen(OldPassword) > LM20_PWLEN)) ) {
  781. //
  782. // The net api returns ERROR_INVALID_PARAMETER if the password
  783. // could not be converted to the LM OWF password. Return
  784. // STATUS_PASSWORD_RESTRICTION for this.
  785. //
  786. Status = STATUS_PASSWORD_RESTRICTION;
  787. //
  788. // We never made it to the other machine, so we should continue
  789. // trying to change the password.
  790. //
  791. *Authoritative = FALSE;
  792. } else {
  793. Status = NetpApiStatusToNtStatus( NetStatus );
  794. }
  795. Cleanup:
  796. //
  797. // Free UserName if used.
  798. //
  799. if (UserName) {
  800. I_NtLmFree(UserName);
  801. }
  802. //
  803. // Free OldPassword if used. (Don't let password make it to page file)
  804. //
  805. if (OldPassword) {
  806. RtlZeroMemory( OldPassword, wcslen(OldPassword) * sizeof(WCHAR) );
  807. I_NtLmFree(OldPassword);
  808. }
  809. //
  810. // Free NewPassword if used. (Don't let password make it to page file)
  811. //
  812. if (NewPassword) {
  813. RtlZeroMemory( NewPassword, wcslen(NewPassword) * sizeof(WCHAR) );
  814. I_NtLmFree(NewPassword);
  815. }
  816. return Status;
  817. }
  818. NTSTATUS
  819. MspChangePassword(
  820. IN OUT PUNICODE_STRING ComputerName,
  821. IN PUNICODE_STRING UserName,
  822. IN PUNICODE_STRING OldPassword,
  823. IN PUNICODE_STRING NewPassword,
  824. IN PLSA_CLIENT_REQUEST ClientRequest,
  825. IN BOOLEAN Impersonating,
  826. OUT PDOMAIN_PASSWORD_INFORMATION *DomainPasswordInfo,
  827. OUT PPOLICY_PRIMARY_DOMAIN_INFO *PrimaryDomainInfo OPTIONAL,
  828. OUT PBOOLEAN Authoritative
  829. )
  830. /*++
  831. Routine Description:
  832. This routine is called by MspLM20ChangePassword to change the password
  833. on the specified server. The server may be either NT or Downlevel.
  834. Arguments:
  835. ComputerName - Name of the target machine. This name may or may not
  836. begin with two backslashes.
  837. UserName - Name of the user to change password for.
  838. OldPassword - Plaintext current password.
  839. NewPassword - Plaintext replacement password.
  840. ClientRequest - Is a pointer to an opaque data structure
  841. representing the client's request.
  842. DomainPasswordInfo - Password restriction information (returned only if
  843. status is STATUS_PASSWORD_RESTRICTION).
  844. PrimaryDomainInfo - DomainNameInformation (returned only if status is
  845. STATUS_BACKUP_CONTROLLER).
  846. Authoritative - Indicates that the error code is authoritative
  847. and it indicates that password changing should stop. If false,
  848. password changing should continue.
  849. Return Value:
  850. STATUS_SUCCESS - Indicates the service completed successfully.
  851. STATUS_PASSWORD_RESTRICTION - Password changing is restricted.
  852. --*/
  853. {
  854. NTSTATUS Status;
  855. UNICODE_STRING UncComputerName;
  856. *Authoritative = TRUE;
  857. //
  858. // Ensure the server name is a UNC server name.
  859. //
  860. Status = MspAddBackslashesComputerName( ComputerName, &UncComputerName );
  861. if (!NT_SUCCESS(Status)) {
  862. KdPrint(("MspChangePassword: MspAddBackslashes..(%wZ) failed, status %x\n",
  863. ComputerName, Status));
  864. return(Status);
  865. }
  866. //
  867. // Assume the Server is an NT server and try to change the password.
  868. //
  869. Status = MspChangePasswordSam(
  870. &UncComputerName,
  871. UserName,
  872. OldPassword,
  873. NewPassword,
  874. ClientRequest,
  875. Impersonating,
  876. DomainPasswordInfo,
  877. PrimaryDomainInfo,
  878. Authoritative );
  879. //
  880. // If MspChangePasswordSam returns anything other than
  881. // STATUS_CANT_ACCESS_DOMAIN_INFO, it was able to connect
  882. // to the remote computer so we won't try downlevel.
  883. //
  884. if (Status == STATUS_CANT_ACCESS_DOMAIN_INFO) {
  885. NET_API_STATUS NetStatus;
  886. DWORD OptionsSupported;
  887. //
  888. // only if target machine doesn't support SAM protocol do we attempt
  889. // downlevel.
  890. // MspAddBackslashesComputerName() NULL terminates the buffer.
  891. //
  892. NetStatus = NetRemoteComputerSupports(
  893. (LPWSTR)UncComputerName.Buffer,
  894. SUPPORTS_RPC | SUPPORTS_LOCAL | SUPPORTS_SAM_PROTOCOL,
  895. &OptionsSupported
  896. );
  897. if( NetStatus == NERR_Success && !(OptionsSupported & SUPPORTS_SAM_PROTOCOL) ) {
  898. Status = MspChangePasswordDownlevel(
  899. &UncComputerName,
  900. UserName,
  901. OldPassword,
  902. NewPassword,
  903. Authoritative );
  904. }
  905. }
  906. //
  907. // Free UncComputerName.Buffer if different from ComputerName.
  908. //
  909. if ( UncComputerName.Buffer != ComputerName->Buffer ) {
  910. I_NtLmFree(UncComputerName.Buffer);
  911. }
  912. return(Status);
  913. }
  914. NTSTATUS
  915. MspLm20ChangePassword (
  916. IN PLSA_CLIENT_REQUEST ClientRequest,
  917. IN PVOID ProtocolSubmitBuffer,
  918. IN PVOID ClientBufferBase,
  919. IN ULONG SubmitBufferSize,
  920. OUT PVOID *ProtocolReturnBuffer,
  921. OUT PULONG ReturnBufferSize,
  922. OUT PNTSTATUS ProtocolStatus
  923. )
  924. /*++
  925. Routine Description:
  926. This routine is the dispatch routine for LsaCallAuthenticationPackage()
  927. with a message type of MsV1_0ChangePassword. This routine changes an
  928. account's password by either calling SamXxxPassword (for NT domains) or
  929. RxNetUserPasswordSet (for downlevel domains and standalone servers).
  930. Arguments:
  931. The arguments to this routine are identical to those of LsaApCallPackage.
  932. Only the special attributes of these parameters as they apply to
  933. this routine are mentioned here.
  934. Return Value:
  935. STATUS_SUCCESS - Indicates the service completed successfully.
  936. STATUS_PASSWORD_RESTRICTION - The password change failed because the
  937. - password doesn't meet one or more domain restrictions. The
  938. - response buffer is allocated. If the PasswordInfoValid flag is
  939. - set it contains valid information otherwise it contains no
  940. - information because this was a down-level change.
  941. STATUS_BACKUP_CONTROLLER - The named machine is a Backup Domain Controller.
  942. Changing password is only allowed on the Primary Domain Controller.
  943. --*/
  944. {
  945. PMSV1_0_CHANGEPASSWORD_REQUEST ChangePasswordRequest = NULL;
  946. PMSV1_0_CHANGEPASSWORD_RESPONSE ChangePasswordResponse;
  947. NTSTATUS Status = STATUS_SUCCESS;
  948. NTSTATUS SavedStatus = STATUS_SUCCESS;
  949. LPWSTR DomainName = NULL;
  950. PDOMAIN_CONTROLLER_INFO DCInfo = NULL;
  951. UNICODE_STRING DCNameString;
  952. UNICODE_STRING ClientNetbiosDomain = {0};
  953. PUNICODE_STRING ClientDsGetDcDomain;
  954. UNICODE_STRING ClientDnsDomain = {0};
  955. UNICODE_STRING ClientUpn = {0};
  956. UNICODE_STRING ClientName = {0};
  957. UNICODE_STRING ValidatedAccountName;
  958. UNICODE_STRING ValidatedDomainName;
  959. LPWSTR ValidatedOldPasswordBuffer;
  960. LPWSTR ValidatedNewPasswordBuffer;
  961. NET_API_STATUS NetStatus;
  962. PPOLICY_LSA_SERVER_ROLE_INFO PolicyLsaServerRoleInfo = NULL;
  963. PDOMAIN_PASSWORD_INFORMATION DomainPasswordInfo = NULL;
  964. PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo = NULL;
  965. PWKSTA_INFO_100 WkstaInfo100 = NULL;
  966. BOOLEAN PasswordBufferValidated = FALSE;
  967. CLIENT_BUFFER_DESC ClientBufferDesc;
  968. PSECURITY_SEED_AND_LENGTH SeedAndLength;
  969. PDS_NAME_RESULTW NameResult = NULL;
  970. HANDLE DsHandle = NULL;
  971. UCHAR Seed;
  972. BOOLEAN Authoritative = TRUE;
  973. BOOLEAN AttemptRediscovery = FALSE;
  974. #if _WIN64
  975. PVOID pTempSubmitBuffer = ProtocolSubmitBuffer;
  976. SECPKG_CALL_INFO CallInfo;
  977. BOOL fAllocatedSubmitBuffer = FALSE;
  978. #endif
  979. RtlInitUnicodeString(
  980. &DCNameString,
  981. NULL
  982. );
  983. //
  984. // Sanity checks.
  985. //
  986. NlpInitClientBuffer( &ClientBufferDesc, ClientRequest );
  987. #if _WIN64
  988. //
  989. // Expand the ProtocolSubmitBuffer to 64-bit pointers if this
  990. // call came from a WOW client.
  991. //
  992. Status = LsaFunctions->GetCallInfo(&CallInfo);
  993. if (!NT_SUCCESS(Status))
  994. {
  995. goto Cleanup;
  996. }
  997. if (CallInfo.Attributes & SECPKG_CALL_WOWCLIENT)
  998. {
  999. Status = MsvConvertWOWChangePasswordBuffer(ProtocolSubmitBuffer,
  1000. ClientBufferBase,
  1001. &SubmitBufferSize,
  1002. &pTempSubmitBuffer);
  1003. if (!NT_SUCCESS(Status))
  1004. {
  1005. goto Cleanup;
  1006. }
  1007. fAllocatedSubmitBuffer = TRUE;
  1008. //
  1009. // Some macros below expand out to use ProtocolSubmitBuffer directly.
  1010. // We've secretly replaced their usual ProtocolSubmitBuffer with
  1011. // pTempSubmitBuffer -- let's see if they can tell the difference.
  1012. //
  1013. ProtocolSubmitBuffer = pTempSubmitBuffer;
  1014. }
  1015. #endif // _WIN64
  1016. if ( SubmitBufferSize < sizeof(MSV1_0_CHANGEPASSWORD_REQUEST) ) {
  1017. Status = STATUS_INVALID_PARAMETER;
  1018. goto Cleanup;
  1019. }
  1020. ChangePasswordRequest = (PMSV1_0_CHANGEPASSWORD_REQUEST) ProtocolSubmitBuffer;
  1021. ASSERT( ChangePasswordRequest->MessageType == MsV1_0ChangePassword ||
  1022. ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword );
  1023. RELOCATE_ONE( &ChangePasswordRequest->DomainName );
  1024. RELOCATE_ONE( &ChangePasswordRequest->AccountName );
  1025. if ( ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ) {
  1026. NULL_RELOCATE_ONE( &ChangePasswordRequest->OldPassword );
  1027. } else {
  1028. RELOCATE_ONE_ENCODED( &ChangePasswordRequest->OldPassword );
  1029. }
  1030. RELOCATE_ONE_ENCODED( &ChangePasswordRequest->NewPassword );
  1031. //
  1032. // save away copies of validated buffers to check later.
  1033. //
  1034. RtlCopyMemory( &ValidatedDomainName, &ChangePasswordRequest->DomainName, sizeof(ValidatedDomainName) );
  1035. RtlCopyMemory( &ValidatedAccountName, &ChangePasswordRequest->AccountName, sizeof(ValidatedAccountName) );
  1036. ValidatedOldPasswordBuffer = ChangePasswordRequest->OldPassword.Buffer;
  1037. ValidatedNewPasswordBuffer = ChangePasswordRequest->NewPassword.Buffer;
  1038. SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangePasswordRequest->OldPassword.Length;
  1039. Seed = SeedAndLength->Seed;
  1040. SeedAndLength->Seed = 0;
  1041. if (Seed != 0) {
  1042. try {
  1043. RtlRunDecodeUnicodeString(
  1044. Seed,
  1045. &ChangePasswordRequest->OldPassword
  1046. );
  1047. } except (EXCEPTION_EXECUTE_HANDLER) {
  1048. Status = STATUS_ILL_FORMED_PASSWORD;
  1049. goto Cleanup;
  1050. }
  1051. }
  1052. SeedAndLength = (PSECURITY_SEED_AND_LENGTH) &ChangePasswordRequest->NewPassword.Length;
  1053. Seed = SeedAndLength->Seed;
  1054. SeedAndLength->Seed = 0;
  1055. if (Seed != 0) {
  1056. if ( ChangePasswordRequest->NewPassword.Buffer !=
  1057. ValidatedNewPasswordBuffer ) {
  1058. Status = STATUS_INVALID_PARAMETER;
  1059. goto Cleanup;
  1060. }
  1061. try {
  1062. RtlRunDecodeUnicodeString(
  1063. Seed,
  1064. &ChangePasswordRequest->NewPassword
  1065. );
  1066. } except (EXCEPTION_EXECUTE_HANDLER) {
  1067. Status = STATUS_ILL_FORMED_PASSWORD;
  1068. goto Cleanup;
  1069. }
  1070. }
  1071. //
  1072. // sanity check that we didn't whack over buffers.
  1073. //
  1074. if( !RtlCompareMemory(
  1075. &ValidatedDomainName,
  1076. &ChangePasswordRequest->DomainName,
  1077. sizeof(ValidatedDomainName)
  1078. )
  1079. ||
  1080. !RtlCompareMemory(
  1081. &ValidatedAccountName,
  1082. &ChangePasswordRequest->AccountName,
  1083. sizeof(ValidatedAccountName)
  1084. )
  1085. ||
  1086. (ValidatedOldPasswordBuffer != ChangePasswordRequest->OldPassword.Buffer)
  1087. ||
  1088. (ValidatedNewPasswordBuffer != ChangePasswordRequest->NewPassword.Buffer)
  1089. ) {
  1090. Status= STATUS_INVALID_PARAMETER;
  1091. goto Cleanup;
  1092. }
  1093. *ReturnBufferSize = 0;
  1094. *ProtocolReturnBuffer = NULL;
  1095. *ProtocolStatus = STATUS_PENDING;
  1096. PasswordBufferValidated = TRUE;
  1097. MsvPaswdLogPrint(("Attempting password change server/domain %wZ for user %wZ\n",
  1098. &ChangePasswordRequest->DomainName,
  1099. &ChangePasswordRequest->AccountName
  1100. ));
  1101. #ifdef COMPILED_BY_DEVELOPER
  1102. KdPrint(("MSV1_0:\n"
  1103. "\tDomain:\t%wZ\n"
  1104. "\tAccount:\t%wZ\n"
  1105. "\tOldPassword(%d)\n"
  1106. "\tNewPassword(%d)\n",
  1107. &ChangePasswordRequest->DomainName,
  1108. &ChangePasswordRequest->AccountName,
  1109. (int) ChangePasswordRequest->OldPassword.Length,
  1110. (int) ChangePasswordRequest->NewPassword.Length
  1111. ));
  1112. #endif // COMPILED_BY_DEVELOPER
  1113. SspPrint((SSP_UPDATES, "MspLm20ChangePassword %wZ\\%wZ, message type %#x%s, impersonating ? %s\n",
  1114. &ChangePasswordRequest->DomainName,
  1115. &ChangePasswordRequest->AccountName,
  1116. ChangePasswordRequest->MessageType,
  1117. (MsV1_0ChangeCachedPassword == ChangePasswordRequest->MessageType) ? " (cached)" : "",
  1118. ChangePasswordRequest->Impersonating ? "true" : "false"));
  1119. //
  1120. // If we're just changing the cached password,
  1121. // skip changing the password on the domain.
  1122. //
  1123. if ( ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ) {
  1124. ClientName = ChangePasswordRequest->AccountName;
  1125. ClientNetbiosDomain = ChangePasswordRequest->DomainName;
  1126. Status = STATUS_SUCCESS;
  1127. goto PasswordChangeSuccessfull;
  1128. }
  1129. //
  1130. // If the client supplied a non-nt4 name, go ahead and convert it here.
  1131. //
  1132. if (ChangePasswordRequest->DomainName.Length == 0) {
  1133. DWORD DsStatus;
  1134. WCHAR NameBuffer[UNLEN+1];
  1135. LPWSTR NameString = NameBuffer;
  1136. ULONG Index;
  1137. BOOLEAN fWorkstation = FALSE;
  1138. if (ChangePasswordRequest->AccountName.Length / sizeof(WCHAR) > UNLEN)
  1139. {
  1140. Status = STATUS_INVALID_PARAMETER;
  1141. goto Cleanup;
  1142. }
  1143. RtlCopyMemory(
  1144. NameBuffer,
  1145. ChangePasswordRequest->AccountName.Buffer,
  1146. ChangePasswordRequest->AccountName.Length
  1147. );
  1148. NameBuffer[ChangePasswordRequest->AccountName.Length/sizeof(WCHAR)] = L'\0';
  1149. RtlInitUnicodeString( &ClientUpn, NameBuffer );
  1150. DsStatus = DsBindW(
  1151. NULL,
  1152. NULL,
  1153. &DsHandle
  1154. );
  1155. //
  1156. // we allow the bind to fail on a member workstation, which allows
  1157. // us to attempt a 'manual' crack.
  1158. //
  1159. if (DsStatus != ERROR_SUCCESS) {
  1160. fWorkstation = NlpWorkstation;
  1161. if( !fWorkstation ) {
  1162. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsBindW returned 0x%lx.\n",
  1163. DsStatus ));
  1164. Status = NetpApiStatusToNtStatus( DsStatus );
  1165. goto Cleanup;
  1166. }
  1167. }
  1168. if( !fWorkstation ) {
  1169. DsStatus = DsCrackNamesW(
  1170. DsHandle,
  1171. 0, // no flags
  1172. DS_UNKNOWN_NAME,
  1173. DS_NT4_ACCOUNT_NAME,
  1174. 1,
  1175. &NameString,
  1176. &NameResult
  1177. );
  1178. if (DsStatus != ERROR_SUCCESS) {
  1179. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW returned 0x%lx.\n",
  1180. DsStatus ));
  1181. Status = NetpApiStatusToNtStatus( DsStatus );
  1182. goto Cleanup;
  1183. }
  1184. //
  1185. // Look for the name in the result
  1186. //
  1187. if (NameResult->cItems < 1) {
  1188. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsCrackNamesW returned zero items.\n"));
  1189. Status = STATUS_INTERNAL_ERROR;
  1190. goto Cleanup;
  1191. }
  1192. }
  1193. //
  1194. // Check if the crack succeeded
  1195. //
  1196. if ( (fWorkstation) ||
  1197. (NameResult->rItems[0].status != DS_NAME_NO_ERROR)
  1198. ) {
  1199. //
  1200. // The name wasn't mapped. Try converting it manually by
  1201. // splitting it at the '@'
  1202. //
  1203. RtlInitUnicodeString(
  1204. &ClientName,
  1205. NameBuffer
  1206. );
  1207. // shortest possible is 3 Unicode chars (eg: a@a)
  1208. if (ClientName.Length < (sizeof(WCHAR) * 3)) {
  1209. Status = STATUS_NO_SUCH_USER;
  1210. goto Cleanup;
  1211. }
  1212. for (Index = (ClientName.Length / sizeof(WCHAR)) - 1; Index != 0 ; Index-- ) {
  1213. if (ClientName.Buffer[Index] == L'@') {
  1214. RtlInitUnicodeString(
  1215. &ClientDnsDomain,
  1216. &ClientName.Buffer[Index+1]
  1217. );
  1218. ClientName.Buffer[Index] = L'\0';
  1219. ClientName.Length = (USHORT) Index * sizeof(WCHAR);
  1220. break;
  1221. }
  1222. }
  1223. //
  1224. // If the name couldn't be parsed, give up and go home
  1225. //
  1226. if (ClientDnsDomain.Length == 0) {
  1227. Status = STATUS_NO_SUCH_USER;
  1228. goto Cleanup;
  1229. }
  1230. //
  1231. // This isn't really the Netbios Domain name, but it is the best we have.
  1232. //
  1233. ClientNetbiosDomain = ClientDnsDomain;
  1234. for (Index = 0; Index < (ClientNetbiosDomain.Length / sizeof(WCHAR)) ; Index++ ) {
  1235. //
  1236. // truncate the netbios domain to the first DOT
  1237. //
  1238. if( ClientNetbiosDomain.Buffer[Index] == L'.' ) {
  1239. ClientNetbiosDomain.Length = (USHORT)(Index * sizeof(WCHAR));
  1240. ClientNetbiosDomain.MaximumLength = ClientNetbiosDomain.Length;
  1241. break;
  1242. }
  1243. }
  1244. }
  1245. else
  1246. {
  1247. RtlInitUnicodeString(
  1248. &ClientDnsDomain,
  1249. NameResult->rItems[0].pDomain
  1250. );
  1251. RtlInitUnicodeString(
  1252. &ClientName,
  1253. NameResult->rItems[0].pName
  1254. );
  1255. RtlInitUnicodeString(
  1256. &ClientNetbiosDomain,
  1257. NameResult->rItems[0].pName
  1258. );
  1259. //
  1260. // Move the pointer for the name up to the first "\" in the name
  1261. //
  1262. for (Index = 0; Index < ClientName.Length / sizeof(WCHAR) ; Index++ ) {
  1263. if (ClientName.Buffer[Index] == L'\\') {
  1264. RtlInitUnicodeString(
  1265. &ClientName,
  1266. &ClientName.Buffer[Index+1]
  1267. );
  1268. // Set the Netbios Domain Name to the string to the left of the backslash
  1269. ClientNetbiosDomain.Length = (USHORT)(Index * sizeof(WCHAR));
  1270. break;
  1271. }
  1272. }
  1273. }
  1274. } else {
  1275. ClientName = ChangePasswordRequest->AccountName;
  1276. ClientNetbiosDomain = ChangePasswordRequest->DomainName;
  1277. }
  1278. // Make sure that NlpSamInitialized is TRUE. If we logon using
  1279. // Kerberos, this may not be true.
  1280. if ( !NlpSamInitialized)
  1281. {
  1282. Status = NlSamInitialize( SAM_STARTUP_TIME );
  1283. if (!NT_SUCCESS(Status))
  1284. {
  1285. goto Cleanup;
  1286. }
  1287. }
  1288. //
  1289. // Check to see if the name provided is a domain name. If it has no
  1290. // leading "\\" and does not match the name of the computer, it may be.
  1291. //
  1292. if ((( ClientNetbiosDomain.Length < 3 * sizeof(WCHAR)) ||
  1293. ( ClientNetbiosDomain.Buffer[0] != L'\\' &&
  1294. ClientNetbiosDomain.Buffer[1] != L'\\' ) ) &&
  1295. !RtlEqualDomainName(
  1296. &NlpComputerName,
  1297. &ClientNetbiosDomain )) {
  1298. //
  1299. // Check if we are a DC in this domain.
  1300. // If so, use this DC.
  1301. //
  1302. if ( !NlpWorkstation &&
  1303. RtlEqualDomainName(
  1304. &NlpSamDomainName,
  1305. &ClientNetbiosDomain )) {
  1306. DCNameString = NlpComputerName;
  1307. }
  1308. if (DCNameString.Buffer == NULL) {
  1309. if ( ClientDnsDomain.Length != 0 ) {
  1310. ClientDsGetDcDomain = &ClientDnsDomain;
  1311. } else {
  1312. ClientDsGetDcDomain = &ClientNetbiosDomain;
  1313. }
  1314. //
  1315. // Build a zero terminated domain name.
  1316. //
  1317. DomainName = I_NtLmAllocate(
  1318. ClientDsGetDcDomain->Length + sizeof(WCHAR)
  1319. );
  1320. if ( DomainName == NULL ) {
  1321. Status = STATUS_INSUFFICIENT_RESOURCES;
  1322. goto Cleanup;
  1323. }
  1324. RtlCopyMemory( DomainName,
  1325. ClientDsGetDcDomain->Buffer,
  1326. ClientDsGetDcDomain->Length );
  1327. DomainName[ClientDsGetDcDomain->Length / sizeof(WCHAR)] = 0;
  1328. NetStatus = DsGetDcNameW(
  1329. NULL,
  1330. DomainName,
  1331. NULL, // no domain guid
  1332. NULL, // no site name
  1333. DS_WRITABLE_REQUIRED,
  1334. &DCInfo );
  1335. if ( NetStatus != NERR_Success ) {
  1336. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW returned 0x%lx.\n",
  1337. NetStatus));
  1338. Status = NetpApiStatusToNtStatus( NetStatus );
  1339. if( Status == STATUS_INTERNAL_ERROR )
  1340. Status = STATUS_NO_SUCH_DOMAIN;
  1341. } else {
  1342. RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
  1343. }
  1344. AttemptRediscovery = TRUE;
  1345. }
  1346. if (NT_SUCCESS(Status)) {
  1347. Status = MspChangePassword(
  1348. &DCNameString,
  1349. &ClientName,
  1350. &ChangePasswordRequest->OldPassword,
  1351. &ChangePasswordRequest->NewPassword,
  1352. ClientRequest,
  1353. ChangePasswordRequest->Impersonating,
  1354. &DomainPasswordInfo,
  1355. NULL,
  1356. &Authoritative );
  1357. //
  1358. // If we succeeded or got back an authoritative answer
  1359. if ( NT_SUCCESS(Status) || Authoritative) {
  1360. goto PasswordChangeSuccessfull;
  1361. }
  1362. }
  1363. }
  1364. //
  1365. // Free the DC info so we can call DsGetDcName again.
  1366. //
  1367. if ( DCInfo != NULL ) {
  1368. NetApiBufferFree(DCInfo);
  1369. DCInfo = NULL;
  1370. }
  1371. //
  1372. // attempt re-discovery.
  1373. //
  1374. if( AttemptRediscovery ) {
  1375. NetStatus = DsGetDcNameW(
  1376. NULL,
  1377. DomainName,
  1378. NULL, // no domain guid
  1379. NULL, // no site name
  1380. DS_FORCE_REDISCOVERY | DS_WRITABLE_REQUIRED,
  1381. &DCInfo );
  1382. if ( NetStatus != NERR_Success ) {
  1383. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW (re-discover) returned 0x%lx.\n",
  1384. NetStatus));
  1385. DCInfo = NULL;
  1386. Status = NetpApiStatusToNtStatus( NetStatus );
  1387. if( Status == STATUS_INTERNAL_ERROR )
  1388. Status = STATUS_NO_SUCH_DOMAIN;
  1389. } else {
  1390. RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
  1391. Status = MspChangePassword(
  1392. &DCNameString,
  1393. &ClientName,
  1394. &ChangePasswordRequest->OldPassword,
  1395. &ChangePasswordRequest->NewPassword,
  1396. ClientRequest,
  1397. ChangePasswordRequest->Impersonating,
  1398. &DomainPasswordInfo,
  1399. NULL,
  1400. &Authoritative );
  1401. //
  1402. // If we succeeded or got back an authoritative answer
  1403. if ( NT_SUCCESS(Status) || Authoritative) {
  1404. goto PasswordChangeSuccessfull;
  1405. }
  1406. //
  1407. // Free the DC info so we can call DsGetDcName again.
  1408. //
  1409. if ( DCInfo != NULL ) {
  1410. NetApiBufferFree(DCInfo);
  1411. DCInfo = NULL;
  1412. }
  1413. }
  1414. }
  1415. if (Status != STATUS_BACKUP_CONTROLLER) {
  1416. //
  1417. // Change the password assuming the DomainName is really a server name
  1418. //
  1419. // The domain name is overloaded to be either a domain name or a server
  1420. // name. The server name is useful when changing the password on a LM2.x
  1421. // standalone server, which is a "member" of a domain but uses a private
  1422. // account database.
  1423. //
  1424. Status = MspChangePassword(
  1425. &ClientNetbiosDomain,
  1426. &ClientName,
  1427. &ChangePasswordRequest->OldPassword,
  1428. &ChangePasswordRequest->NewPassword,
  1429. ClientRequest,
  1430. ChangePasswordRequest->Impersonating,
  1431. &DomainPasswordInfo,
  1432. &PrimaryDomainInfo,
  1433. &Authoritative );
  1434. //
  1435. // If DomainName is actually a server name,
  1436. // just return the status to the caller.
  1437. //
  1438. if ( Authoritative &&
  1439. ( Status != STATUS_BAD_NETWORK_PATH ||
  1440. ( ClientNetbiosDomain.Length >= 3 * sizeof(WCHAR) &&
  1441. ClientNetbiosDomain.Buffer[0] == L'\\' &&
  1442. ClientNetbiosDomain.Buffer[1] == L'\\' ) ) ) {
  1443. //
  1444. // If \\xxx was specified, but xxx doesn't exist,
  1445. // return the status code that the DomainName field is bad.
  1446. //
  1447. if ( Status == STATUS_BAD_NETWORK_PATH ) {
  1448. Status = STATUS_NO_SUCH_DOMAIN;
  1449. }
  1450. }
  1451. //
  1452. // If we didn't get an error that this was a backup controller,
  1453. // we are out of here.
  1454. //
  1455. if (Status != STATUS_BACKUP_CONTROLLER) {
  1456. goto PasswordChangeSuccessfull;
  1457. }
  1458. }
  1459. //
  1460. // If the specified machine was a BDC in a domain,
  1461. // Pretend the caller passed us the domain name in the first place.
  1462. //
  1463. if ( Status == STATUS_BACKUP_CONTROLLER && PrimaryDomainInfo != NULL ) {
  1464. ClientNetbiosDomain = PrimaryDomainInfo->Name;
  1465. Status = STATUS_BAD_NETWORK_PATH;
  1466. } else {
  1467. goto PasswordChangeSuccessfull;
  1468. }
  1469. //
  1470. // Build a zero terminated domain name.
  1471. //
  1472. // BUGBUG: Should really pass both names to internal version of DsGetDcName
  1473. if ( ClientDnsDomain.Length != 0 ) {
  1474. ClientDsGetDcDomain = &ClientDnsDomain;
  1475. } else {
  1476. ClientDsGetDcDomain = &ClientNetbiosDomain;
  1477. }
  1478. if( DomainName )
  1479. {
  1480. I_NtLmFree( DomainName );
  1481. }
  1482. DomainName = I_NtLmAllocate(
  1483. ClientDsGetDcDomain->Length + sizeof(WCHAR)
  1484. );
  1485. if ( DomainName == NULL ) {
  1486. Status = STATUS_INSUFFICIENT_RESOURCES;
  1487. goto Cleanup;
  1488. }
  1489. RtlCopyMemory( DomainName,
  1490. ClientDsGetDcDomain->Buffer,
  1491. ClientDsGetDcDomain->Length );
  1492. DomainName[ClientDsGetDcDomain->Length / sizeof(WCHAR)] = 0;
  1493. AttemptRediscovery = FALSE;
  1494. retry:
  1495. {
  1496. DWORD dwGetDcFlags = 0;
  1497. if( AttemptRediscovery )
  1498. dwGetDcFlags |= DS_FORCE_REDISCOVERY;
  1499. //
  1500. // Determine the PDC of the named domain so we can change the password there.
  1501. //
  1502. NetStatus = DsGetDcNameW(
  1503. NULL,
  1504. DomainName,
  1505. NULL, // no domain guid
  1506. NULL, // no site name
  1507. dwGetDcFlags | DS_WRITABLE_REQUIRED,
  1508. &DCInfo );
  1509. if ( NetStatus != NERR_Success ) {
  1510. SspPrint(( SSP_CRITICAL, "MspLm20ChangePassword: DsGetDcNameW returned 0x%lx.\n",
  1511. NetStatus));
  1512. Status = NetpApiStatusToNtStatus( NetStatus );
  1513. if( Status == STATUS_INTERNAL_ERROR )
  1514. Status = STATUS_NO_SUCH_DOMAIN;
  1515. goto Cleanup;
  1516. }
  1517. RtlInitUnicodeString( &DCNameString, DCInfo->DomainControllerName );
  1518. Status = MspChangePassword(
  1519. &DCNameString,
  1520. &ClientName,
  1521. &ChangePasswordRequest->OldPassword,
  1522. &ChangePasswordRequest->NewPassword,
  1523. ClientRequest,
  1524. ChangePasswordRequest->Impersonating,
  1525. &DomainPasswordInfo,
  1526. NULL,
  1527. &Authoritative );
  1528. if( !NT_SUCCESS(Status) && !Authoritative && !AttemptRediscovery ) {
  1529. AttemptRediscovery = TRUE;
  1530. goto retry;
  1531. }
  1532. }
  1533. PasswordChangeSuccessfull:
  1534. //
  1535. // Allocate and initialize the response buffer.
  1536. //
  1537. SavedStatus = Status;
  1538. *ReturnBufferSize = sizeof(MSV1_0_CHANGEPASSWORD_RESPONSE);
  1539. Status = NlpAllocateClientBuffer( &ClientBufferDesc,
  1540. sizeof(MSV1_0_CHANGEPASSWORD_RESPONSE),
  1541. *ReturnBufferSize );
  1542. if ( !NT_SUCCESS( Status ) ) {
  1543. KdPrint(("MSV1_0: MspLm20ChangePassword: cannot alloc client buffer\n"));
  1544. *ReturnBufferSize = 0;
  1545. goto Cleanup;
  1546. }
  1547. ChangePasswordResponse = (PMSV1_0_CHANGEPASSWORD_RESPONSE) ClientBufferDesc.MsvBuffer;
  1548. ChangePasswordResponse->MessageType = MsV1_0ChangePassword;
  1549. //
  1550. // Copy the DomainPassword restrictions out to the caller depending on
  1551. // whether it was passed to us.
  1552. //
  1553. // Mark the buffer as valid or invalid to let the caller know.
  1554. //
  1555. // if STATUS_PASSWORD_RESTRICTION is returned. This status can be
  1556. // returned by either SAM or a down-level change. Only SAM will return
  1557. // valid data so we have a flag in the buffer that says whether the data
  1558. // is valid or not.
  1559. //
  1560. if ( DomainPasswordInfo == NULL ) {
  1561. ChangePasswordResponse->PasswordInfoValid = FALSE;
  1562. } else {
  1563. ChangePasswordResponse->DomainPasswordInfo = *DomainPasswordInfo;
  1564. ChangePasswordResponse->PasswordInfoValid = TRUE;
  1565. }
  1566. //
  1567. // Flush the buffer to the client's address space.
  1568. //
  1569. Status = NlpFlushClientBuffer( &ClientBufferDesc,
  1570. ProtocolReturnBuffer );
  1571. //
  1572. // Update cached credentials with the new password.
  1573. //
  1574. // This is done by calling NlpChangePassword,
  1575. // which takes encrypted passwords, so encrypt 'em.
  1576. //
  1577. if ( NT_SUCCESS(SavedStatus) ) {
  1578. BOOLEAN Impersonating;
  1579. NTSTATUS TempStatus;
  1580. //
  1581. // Failure of NlpChangePassword is OK, that means that the
  1582. // account we've been working with isn't the one we're
  1583. // caching credentials for.
  1584. //
  1585. TempStatus = NlpChangePassword(
  1586. &ClientNetbiosDomain,
  1587. &ClientName,
  1588. &ChangePasswordRequest->NewPassword
  1589. );
  1590. //
  1591. // for ChangeCachedPassword, set the ProtocolStatus if an error
  1592. // occured updating.
  1593. //
  1594. if ( !NT_SUCCESS(TempStatus) &&
  1595. (ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword)
  1596. )
  1597. {
  1598. SavedStatus = TempStatus;
  1599. }
  1600. //
  1601. // Notify the LSA itself of the password change
  1602. //
  1603. Impersonating = FALSE;
  1604. if ( ChangePasswordRequest->Impersonating ) {
  1605. TempStatus = Lsa.ImpersonateClient();
  1606. if ( NT_SUCCESS(TempStatus)) {
  1607. Impersonating = TRUE;
  1608. }
  1609. }
  1610. LsaINotifyPasswordChanged(
  1611. &ClientNetbiosDomain,
  1612. &ClientName,
  1613. ClientDnsDomain.Length == 0 ? NULL : &ClientDnsDomain,
  1614. ClientUpn.Length == 0 ? NULL : &ClientUpn,
  1615. ChangePasswordRequest->MessageType == MsV1_0ChangeCachedPassword ?
  1616. NULL :
  1617. &ChangePasswordRequest->OldPassword,
  1618. &ChangePasswordRequest->NewPassword,
  1619. Impersonating );
  1620. if ( Impersonating ) {
  1621. RevertToSelf();
  1622. }
  1623. }
  1624. Status = SavedStatus;
  1625. Cleanup:
  1626. //
  1627. // Free Locally allocated resources
  1628. //
  1629. if (DomainName != NULL) {
  1630. I_NtLmFree(DomainName);
  1631. }
  1632. if ( DCInfo != NULL ) {
  1633. NetApiBufferFree(DCInfo);
  1634. }
  1635. if ( WkstaInfo100 != NULL ) {
  1636. NetApiBufferFree(WkstaInfo100);
  1637. }
  1638. if ( DomainPasswordInfo != NULL ) {
  1639. SamFreeMemory(DomainPasswordInfo);
  1640. }
  1641. if ( PrimaryDomainInfo != NULL ) {
  1642. (VOID) LsaFreeMemory( PrimaryDomainInfo );
  1643. }
  1644. if ( DsHandle != NULL) {
  1645. DsUnBindW(
  1646. &DsHandle
  1647. );
  1648. }
  1649. //
  1650. // Free Policy Server Role Information if used.
  1651. //
  1652. if (PolicyLsaServerRoleInfo != NULL) {
  1653. I_LsaIFree_LSAPR_POLICY_INFORMATION(
  1654. PolicyLsaServerRoleInformation,
  1655. (PLSAPR_POLICY_INFORMATION) PolicyLsaServerRoleInfo
  1656. );
  1657. }
  1658. //
  1659. // Free the return buffer.
  1660. //
  1661. NlpFreeClientBuffer( &ClientBufferDesc );
  1662. //
  1663. // Don't let the password stay in the page file.
  1664. //
  1665. if ( PasswordBufferValidated ) {
  1666. RtlEraseUnicodeString( &ChangePasswordRequest->OldPassword );
  1667. RtlEraseUnicodeString( &ChangePasswordRequest->NewPassword );
  1668. }
  1669. //
  1670. // Flush the log to disk
  1671. //
  1672. MsvPaswdSetAndClearLog();
  1673. #if _WIN64
  1674. //
  1675. // Do this last since some of the cleanup code above may refer to addresses
  1676. // inside the pTempSubmitBuffer/ProtocolSubmitBuffer (e.g., erasing the old
  1677. // and new passwords, etc).
  1678. //
  1679. if (fAllocatedSubmitBuffer)
  1680. {
  1681. NtLmFreePrivateHeap( pTempSubmitBuffer );
  1682. }
  1683. #endif // _WIN64
  1684. //
  1685. // Return status to the caller.
  1686. //
  1687. *ProtocolStatus = Status;
  1688. return STATUS_SUCCESS;
  1689. }