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.

415 lines
12 KiB

  1. #include <nt.h>
  2. #include <ntrtl.h>
  3. #include <nturtl.h>
  4. #include <windows.h>
  5. #include <wincred.h>
  6. #include <align.h>
  7. #include <lm.h>
  8. #include <ntsecapi.h>
  9. #include <dsgetdc.h>
  10. #include <stdlib.h>
  11. #include <stdio.h>
  12. #include <string.h>
  13. // !!!!!
  14. // this file is a duplicate of a nearly identical file in the credui project. It should be removed when
  15. // the implementation of NetUserChangePassword() is updated to handle unc names and MIT Kerberos
  16. // realms properly. For now, it wraps NetUserChangePassword() to handle the extra cases.
  17. // Dependent libraries:
  18. // secur32.lib, netapi32.lib
  19. // external fn: NET_API_STATUS NetUserChangePasswordEy(LPCWSTR,LPCWSTR,LPCWSTR,LPCWSTR)
  20. BOOL
  21. IsMITName (
  22. LPCWSTR UserName
  23. )
  24. {
  25. BOOL fReturn = FALSE;
  26. HKEY MitKey;
  27. DWORD Index;
  28. PWSTR Realms;
  29. DWORD RealmSize;
  30. int err;
  31. DWORD NumRealms;
  32. DWORD MaxRealmLength;
  33. FILETIME KeyTime;
  34. WCHAR *szUncTail;
  35. if (NULL == UserName) return FALSE;
  36. szUncTail = wcschr(UserName,'@');
  37. if (NULL == szUncTail) return FALSE;
  38. szUncTail++; // point to char following @
  39. err = RegOpenKeyEx(
  40. HKEY_LOCAL_MACHINE,
  41. TEXT("System\\CurrentControlSet\\Control\\Lsa\\Kerberos\\Domains"),
  42. 0,
  43. KEY_READ,
  44. &MitKey );
  45. if ( err == 0 )
  46. {
  47. #ifdef LOUDLY
  48. OutputDebugString(L"Kerberos domains key opened\n");
  49. #endif
  50. err = RegQueryInfoKey( MitKey,
  51. NULL,
  52. NULL,
  53. NULL,
  54. &NumRealms,
  55. &MaxRealmLength,
  56. NULL,
  57. NULL,
  58. NULL,
  59. NULL,
  60. NULL,
  61. NULL );
  62. MaxRealmLength++ ;
  63. MaxRealmLength *= sizeof( WCHAR );
  64. Realms = (PWSTR) malloc(MaxRealmLength );
  65. if ( Realms)
  66. {
  67. #ifdef LOUDLY
  68. OutputDebugString(L"Kerberos realms found\n");
  69. #endif
  70. for ( Index = 0 ; Index < NumRealms ; Index++ )
  71. {
  72. RealmSize = MaxRealmLength ;
  73. err = RegEnumKeyEx( MitKey,
  74. Index,
  75. Realms,
  76. &RealmSize,
  77. NULL,
  78. NULL,
  79. NULL,
  80. &KeyTime );
  81. if (err == 0)
  82. {
  83. #ifdef LOUDLY
  84. OutputDebugString(L"Fetched realm: ");
  85. OutputDebugString(Realms);
  86. OutputDebugString(L"\n");
  87. OutputDebugString(L"Username suffix: ");
  88. OutputDebugString(szUncTail);
  89. OutputDebugString(L"\n");
  90. #endif
  91. if (0 == _wcsicmp(szUncTail, Realms))
  92. {
  93. #ifdef LOUDLY
  94. OutputDebugString(L"Username maps to an MIT realm\n");
  95. #endif
  96. fReturn = TRUE;
  97. break;
  98. }
  99. }
  100. }
  101. }
  102. free(Realms);
  103. }
  104. return fReturn;
  105. }
  106. NTSTATUS
  107. MitChangePasswordEy(
  108. LPCWSTR DomainName,
  109. LPCWSTR UserName,
  110. LPCWSTR OldPassword,
  111. LPCWSTR NewPassword,
  112. NTSTATUS *pSubStatus
  113. )
  114. {
  115. HANDLE hLsa = NULL;
  116. NTSTATUS Status;
  117. NTSTATUS SubStatus;
  118. STRING Name;
  119. ULONG PackageId;
  120. PVOID Response = NULL ;
  121. ULONG ResponseSize;
  122. PKERB_CHANGEPASSWORD_REQUEST ChangeRequest = NULL;
  123. ULONG ChangeSize;
  124. UNICODE_STRING User,Domain,OldPass,NewPass;
  125. Status = LsaConnectUntrusted(&hLsa);
  126. if (!SUCCEEDED(Status)) goto Cleanup;
  127. #ifdef LOUDLY
  128. OutputDebugString(L"We have an LSA handle\n");
  129. #endif
  130. RtlInitString(
  131. &Name,
  132. MICROSOFT_KERBEROS_NAME_A
  133. );
  134. Status = LsaLookupAuthenticationPackage(
  135. hLsa,
  136. &Name,
  137. &PackageId
  138. );
  139. if (!NT_SUCCESS(Status))
  140. {
  141. goto Cleanup;
  142. }
  143. #ifdef LOUDLY
  144. OutputDebugString(L"Authentication package found\n");
  145. #endif
  146. RtlInitUnicodeString(
  147. &User,
  148. UserName
  149. );
  150. RtlInitUnicodeString(
  151. &Domain,
  152. DomainName
  153. );
  154. RtlInitUnicodeString(
  155. &OldPass,
  156. OldPassword
  157. );
  158. RtlInitUnicodeString(
  159. &NewPass,
  160. NewPassword
  161. );
  162. ChangeSize = ROUND_UP_COUNT(sizeof(KERB_CHANGEPASSWORD_REQUEST),4)+
  163. User.Length +
  164. Domain.Length +
  165. OldPass.Length +
  166. NewPass.Length ;
  167. ChangeRequest = (PKERB_CHANGEPASSWORD_REQUEST) LocalAlloc(LMEM_ZEROINIT, ChangeSize );
  168. if ( ChangeRequest == NULL )
  169. {
  170. Status = STATUS_NO_MEMORY ;
  171. goto Cleanup ;
  172. }
  173. ChangeRequest->MessageType = KerbChangePasswordMessage;
  174. ChangeRequest->AccountName = User;
  175. ChangeRequest->AccountName.Buffer = (LPWSTR) ROUND_UP_POINTER(sizeof(KERB_CHANGEPASSWORD_REQUEST) + (PBYTE) ChangeRequest,4);
  176. RtlCopyMemory(
  177. ChangeRequest->AccountName.Buffer,
  178. User.Buffer,
  179. User.Length
  180. );
  181. ChangeRequest->DomainName = Domain;
  182. ChangeRequest->DomainName.Buffer = ChangeRequest->AccountName.Buffer + ChangeRequest->AccountName.Length / sizeof(WCHAR);
  183. RtlCopyMemory(
  184. ChangeRequest->DomainName.Buffer,
  185. Domain.Buffer,
  186. Domain.Length
  187. );
  188. ChangeRequest->OldPassword = OldPass;
  189. ChangeRequest->OldPassword.Buffer = ChangeRequest->DomainName.Buffer + ChangeRequest->DomainName.Length / sizeof(WCHAR);
  190. RtlCopyMemory(
  191. ChangeRequest->OldPassword.Buffer,
  192. OldPass.Buffer,
  193. OldPass.Length
  194. );
  195. ChangeRequest->NewPassword = NewPass;
  196. ChangeRequest->NewPassword.Buffer = ChangeRequest->OldPassword.Buffer + ChangeRequest->OldPassword.Length / sizeof(WCHAR);
  197. RtlCopyMemory(
  198. ChangeRequest->NewPassword.Buffer,
  199. NewPass.Buffer,
  200. NewPass.Length
  201. );
  202. //
  203. // We are running as the caller, so state we are impersonating
  204. //
  205. //ChangeRequest->Impersonating = TRUE;
  206. #ifdef LOUDLY
  207. OutputDebugString(L"Attempting to call the authentication package\n");
  208. #endif
  209. Status = LsaCallAuthenticationPackage(
  210. hLsa,
  211. PackageId,
  212. ChangeRequest,
  213. ChangeSize,
  214. &Response,
  215. &ResponseSize,
  216. &SubStatus
  217. );
  218. if (!NT_SUCCESS(Status) || !NT_SUCCESS(SubStatus))
  219. {
  220. #ifdef LOUDLY
  221. WCHAR szsz[200];
  222. swprintf(szsz,L"Call failed. Status %x SubStatus %x\n",Status, SubStatus);
  223. OutputDebugString(szsz);
  224. #endif
  225. if (NT_SUCCESS(Status))
  226. {
  227. Status = SubStatus;
  228. *pSubStatus = STATUS_UNSUCCESSFUL ;
  229. }
  230. else
  231. {
  232. *pSubStatus = SubStatus;
  233. }
  234. }
  235. Cleanup:
  236. if (hLsa) LsaDeregisterLogonProcess(hLsa);
  237. if (Response != NULL) LsaFreeReturnBuffer(Response);
  238. if (ChangeRequest != NULL) LocalFree(ChangeRequest);
  239. return(Status);
  240. }
  241. /*
  242. NetUserChangePasswordEy()
  243. A wrapper function to superset the functionality of NetUserChangePassword(), specifically
  244. by adding support for changing the account password for an MIT Kerberos principal.
  245. This routine accepts:
  246. 1. uncracked username, with NULL domain
  247. 2. cracked username, with domain portion routed to the domain argument
  248. In case 1, it handles all cases, including MIT realm password changes
  249. In case 2, it will not handle MIT realms.
  250. Case 2 is provided for backwards compatibility with NetUserChangePassword(). It is intended
  251. that callers should pass the uncracked name, and remove the cracking code from the client.
  252. */
  253. NET_API_STATUS
  254. NetUserChangePasswordEy (
  255. LPCWSTR domainname,
  256. LPCWSTR username,
  257. LPCWSTR oldpassword,
  258. LPCWSTR newpassword
  259. )
  260. {
  261. NTSTATUS ns; // status from call
  262. NET_API_STATUS nas;
  263. NTSTATUS ss; // substatus
  264. #ifdef LOUDLY
  265. OutputDebugString(L"NetUserChangePasswordEy called for ");
  266. OutputDebugString(username);
  267. OutputDebugString(L"\n");
  268. #endif
  269. // domainname may be a kerberos realm
  270. // If not a UNC name, call through to NetUserChangePassword
  271. // else
  272. // locate UNC suffix
  273. // search all domains returned by DsEnumerateDomainTrusts() for a match
  274. // On match, if is kerberos realm, call MitChangePasswordEy()
  275. // else call NetUserChangePassword
  276. if ((domainname == NULL) && IsMITName(username))
  277. {
  278. ns = MitChangePasswordEy(domainname, username, oldpassword, newpassword, &ss);
  279. // remap certain errors returned by MitChangePasswordEy to coincide with those of NetUserChangePassword
  280. if (NT_SUCCESS(ns)) nas = NERR_Success;
  281. else
  282. {
  283. switch (ns)
  284. {
  285. case STATUS_CANT_ACCESS_DOMAIN_INFO:
  286. case STATUS_NO_SUCH_DOMAIN:
  287. {
  288. nas = NERR_InvalidComputer;
  289. break;
  290. }
  291. case STATUS_NO_SUCH_USER:
  292. case STATUS_WRONG_PASSWORD_CORE:
  293. case STATUS_WRONG_PASSWORD:
  294. {
  295. nas = ERROR_INVALID_PASSWORD;
  296. break;
  297. }
  298. case STATUS_ACCOUNT_RESTRICTION:
  299. case STATUS_ACCESS_DENIED:
  300. case STATUS_BACKUP_CONTROLLER:
  301. {
  302. nas = ERROR_ACCESS_DENIED;
  303. break;
  304. }
  305. case STATUS_PASSWORD_RESTRICTION:
  306. {
  307. nas = NERR_PasswordTooShort;
  308. break;
  309. }
  310. default:
  311. nas = -1; // will produce omnibus error message when found (none of the above)
  312. break;
  313. }
  314. }
  315. }
  316. else if (NULL == domainname)
  317. {
  318. WCHAR RetUserName[CRED_MAX_USERNAME_LENGTH + 1];
  319. WCHAR RetDomainName[CRED_MAX_USERNAME_LENGTH + 1];
  320. RetDomainName[0] = 0;
  321. DWORD Status = CredUIParseUserNameW(
  322. username,
  323. RetUserName,
  324. CRED_MAX_USERNAME_LENGTH,
  325. RetDomainName,
  326. CRED_MAX_USERNAME_LENGTH);
  327. switch (Status)
  328. {
  329. case NO_ERROR:
  330. {
  331. #ifdef LOUDLY
  332. OutputDebugString(L"Non-MIT password change for ");
  333. OutputDebugString(RetUserName);
  334. OutputDebugString(L" of domain ");
  335. OutputDebugString(RetDomainName);
  336. OutputDebugString(L"\n");
  337. #endif
  338. nas = NetUserChangePassword(RetDomainName,RetUserName,oldpassword,newpassword);
  339. break;
  340. }
  341. case ERROR_INSUFFICIENT_BUFFER:
  342. nas = ERROR_INVALID_PARAMETER;
  343. break;
  344. case ERROR_INVALID_ACCOUNT_NAME:
  345. default:
  346. nas = NERR_UserNotFound;
  347. break;
  348. }
  349. }
  350. else
  351. {
  352. // both username and domainname passed.
  353. nas = NetUserChangePassword(domainname,username,oldpassword,newpassword);
  354. }
  355. #ifdef LOUDLY
  356. WCHAR szsz[200];
  357. swprintf(szsz,L"NUCPEy returns %x\n",nas);
  358. OutputDebugString(szsz);
  359. #endif
  360. return nas;
  361. }