1 /*
2 * PROJECT: Authentication Package DLL
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Security Account Manager (SAM) related functions
5 * COPYRIGHT: Copyright 2013 Eric Kohl <eric.kohl@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 #include "wine/debug.h"
11 WINE_DEFAULT_DEBUG_CHANNEL(msv1_0_sam);
12
13
14 static
15 NTSTATUS
GetAccountDomainSid(_In_ PRPC_SID * Sid)16 GetAccountDomainSid(
17 _In_ PRPC_SID *Sid)
18 {
19 LSAPR_HANDLE PolicyHandle = NULL;
20 PLSAPR_POLICY_INFORMATION PolicyInfo = NULL;
21 ULONG Length = 0;
22 NTSTATUS Status;
23
24 Status = LsaIOpenPolicyTrusted(&PolicyHandle);
25 if (!NT_SUCCESS(Status))
26 {
27 TRACE("LsaIOpenPolicyTrusted() failed (Status 0x%08lx)\n", Status);
28 return Status;
29 }
30
31 Status = LsarQueryInformationPolicy(PolicyHandle,
32 PolicyAccountDomainInformation,
33 &PolicyInfo);
34 if (!NT_SUCCESS(Status))
35 {
36 TRACE("LsarQueryInformationPolicy() failed (Status 0x%08lx)\n", Status);
37 goto done;
38 }
39
40 Length = RtlLengthSid(PolicyInfo->PolicyAccountDomainInfo.Sid);
41
42 *Sid = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
43 if (*Sid == NULL)
44 {
45 ERR("Failed to allocate SID\n");
46 Status = STATUS_INSUFFICIENT_RESOURCES;
47 goto done;
48 }
49
50 memcpy(*Sid, PolicyInfo->PolicyAccountDomainInfo.Sid, Length);
51
52 done:
53 if (PolicyInfo != NULL)
54 LsaIFree_LSAPR_POLICY_INFORMATION(PolicyAccountDomainInformation,
55 PolicyInfo);
56
57 if (PolicyHandle != NULL)
58 LsarClose(&PolicyHandle);
59
60 return Status;
61 }
62
63
64 static
65 NTSTATUS
MsvpCheckPassword(_In_ PLSA_SAM_PWD_DATA UserPwdData,_In_ PSAMPR_USER_INFO_BUFFER UserInfo)66 MsvpCheckPassword(
67 _In_ PLSA_SAM_PWD_DATA UserPwdData,
68 _In_ PSAMPR_USER_INFO_BUFFER UserInfo)
69 {
70 ENCRYPTED_NT_OWF_PASSWORD UserNtPassword;
71 ENCRYPTED_LM_OWF_PASSWORD UserLmPassword;
72 BOOLEAN UserLmPasswordPresent = FALSE;
73 BOOLEAN UserNtPasswordPresent = FALSE;
74 OEM_STRING LmPwdString;
75 CHAR LmPwdBuffer[15];
76 NTSTATUS Status;
77
78 TRACE("(%p %p)\n", UserPwdData, UserInfo);
79
80 /* Calculate the LM password and hash for the users password */
81 LmPwdString.Length = 15;
82 LmPwdString.MaximumLength = 15;
83 LmPwdString.Buffer = LmPwdBuffer;
84 ZeroMemory(LmPwdString.Buffer, LmPwdString.MaximumLength);
85
86 Status = RtlUpcaseUnicodeStringToOemString(&LmPwdString,
87 UserPwdData->PlainPwd,
88 FALSE);
89 if (NT_SUCCESS(Status))
90 {
91 /* Calculate the LM hash value of the users password */
92 Status = SystemFunction006(LmPwdString.Buffer,
93 (LPSTR)&UserLmPassword);
94 if (NT_SUCCESS(Status))
95 {
96 UserLmPasswordPresent = TRUE;
97 }
98 }
99
100 /* Calculate the NT hash of the users password */
101 Status = SystemFunction007(UserPwdData->PlainPwd,
102 (LPBYTE)&UserNtPassword);
103 if (NT_SUCCESS(Status))
104 {
105 UserNtPasswordPresent = TRUE;
106 }
107
108 Status = STATUS_WRONG_PASSWORD;
109
110 /* Succeed, if no password has been set */
111 if (UserInfo->All.NtPasswordPresent == FALSE &&
112 UserInfo->All.LmPasswordPresent == FALSE)
113 {
114 TRACE("No password check!\n");
115 Status = STATUS_SUCCESS;
116 goto done;
117 }
118
119 /* Succeed, if NT password matches */
120 if (UserNtPasswordPresent && UserInfo->All.NtPasswordPresent)
121 {
122 TRACE("Check NT password hashes:\n");
123 if (RtlEqualMemory(&UserNtPassword,
124 UserInfo->All.NtOwfPassword.Buffer,
125 sizeof(ENCRYPTED_NT_OWF_PASSWORD)))
126 {
127 TRACE(" success!\n");
128 Status = STATUS_SUCCESS;
129 goto done;
130 }
131
132 TRACE(" failed!\n");
133 }
134
135 /* Succeed, if LM password matches */
136 if (UserLmPasswordPresent && UserInfo->All.LmPasswordPresent)
137 {
138 TRACE("Check LM password hashes:\n");
139 if (RtlEqualMemory(&UserLmPassword,
140 UserInfo->All.LmOwfPassword.Buffer,
141 sizeof(ENCRYPTED_LM_OWF_PASSWORD)))
142 {
143 TRACE(" success!\n");
144 Status = STATUS_SUCCESS;
145 goto done;
146 }
147 TRACE(" failed!\n");
148 }
149
150 done:
151 return Status;
152 }
153
154
155 static
156 bool
MsvpCheckLogonHours(_In_ PSAMPR_LOGON_HOURS LogonHours,_In_ LARGE_INTEGER LogonTime)157 MsvpCheckLogonHours(
158 _In_ PSAMPR_LOGON_HOURS LogonHours,
159 _In_ LARGE_INTEGER LogonTime)
160 {
161 #if 0
162 LARGE_INTEGER LocalLogonTime;
163 TIME_FIELDS TimeFields;
164 USHORT MinutesPerUnit, Offset;
165 bool bFound;
166
167 FIXME("MsvpCheckLogonHours(%p %llx)\n", LogonHours, LogonTime);
168
169 if (LogonHours->UnitsPerWeek == 0 || LogonHours->LogonHours == NULL)
170 {
171 FIXME("No logon hours!\n");
172 return true;
173 }
174
175 RtlSystemTimeToLocalTime(&LogonTime, &LocalLogonTime);
176 RtlTimeToTimeFields(&LocalLogonTime, &TimeFields);
177
178 FIXME("UnitsPerWeek: %u\n", LogonHours->UnitsPerWeek);
179 MinutesPerUnit = 10080 / LogonHours->UnitsPerWeek;
180
181 Offset = ((TimeFields.Weekday * 24 + TimeFields.Hour) * 60 + TimeFields.Minute) / MinutesPerUnit;
182 FIXME("Offset: %us\n", Offset);
183
184 bFound = (bool)(LogonHours->LogonHours[Offset / 8] & (1 << (Offset % 8)));
185 FIXME("Logon permitted: %s\n", bFound ? "Yes" : "No");
186
187 return bFound;
188 #endif
189 return true;
190 }
191
192
193 static
194 bool
MsvpCheckWorkstations(_In_ PRPC_UNICODE_STRING WorkStations,_In_ PWSTR ComputerName)195 MsvpCheckWorkstations(
196 _In_ PRPC_UNICODE_STRING WorkStations,
197 _In_ PWSTR ComputerName)
198 {
199 PWSTR pStart, pEnd;
200 bool bFound = false;
201
202 TRACE("MsvpCheckWorkstations(%p %S)\n", WorkStations, ComputerName);
203
204 if (WorkStations->Length == 0 || WorkStations->Buffer == NULL)
205 {
206 TRACE("No workstations!\n");
207 return true;
208 }
209
210 TRACE("Workstations: %wZ\n", WorkStations);
211
212 pStart = WorkStations->Buffer;
213 for (;;)
214 {
215 pEnd = wcschr(pStart, L',');
216 if (pEnd != NULL)
217 *pEnd = UNICODE_NULL;
218
219 TRACE("Comparing '%S' and '%S'\n", ComputerName, pStart);
220 if (_wcsicmp(ComputerName, pStart) == 0)
221 {
222 bFound = true;
223 if (pEnd != NULL)
224 *pEnd = L',';
225 break;
226 }
227
228 if (pEnd == NULL)
229 break;
230
231 *pEnd = L',';
232 pStart = pEnd + 1;
233 }
234
235 TRACE("Found allowed workstation: %s\n", (bFound) ? "Yes" : "No");
236
237 return bFound;
238 }
239
240
241 static
242 NTSTATUS
SamValidateNormalUser(_In_ PUNICODE_STRING UserName,_In_ PLSA_SAM_PWD_DATA PwdData,_In_ PUNICODE_STRING ComputerName,_Out_ PRPC_SID * AccountDomainSidPtr,_Out_ SAMPR_HANDLE * UserHandlePtr,_Out_ PSAMPR_USER_INFO_BUFFER * UserInfoPtr,_Out_ PNTSTATUS SubStatus)243 SamValidateNormalUser(
244 _In_ PUNICODE_STRING UserName,
245 _In_ PLSA_SAM_PWD_DATA PwdData,
246 _In_ PUNICODE_STRING ComputerName,
247 _Out_ PRPC_SID* AccountDomainSidPtr,
248 _Out_ SAMPR_HANDLE* UserHandlePtr,
249 _Out_ PSAMPR_USER_INFO_BUFFER* UserInfoPtr,
250 _Out_ PNTSTATUS SubStatus)
251 {
252 NTSTATUS Status;
253 SAMPR_HANDLE ServerHandle = NULL;
254 SAMPR_HANDLE DomainHandle = NULL;
255 PRPC_SID AccountDomainSid;
256 RPC_UNICODE_STRING Names[1];
257 SAMPR_HANDLE UserHandle = NULL;
258 SAMPR_ULONG_ARRAY RelativeIds = {0, NULL};
259 SAMPR_ULONG_ARRAY Use = {0, NULL};
260 PSAMPR_USER_INFO_BUFFER UserInfo = NULL;
261 LARGE_INTEGER LogonTime;
262
263 /* Get the logon time */
264 NtQuerySystemTime(&LogonTime);
265
266 /* Get the account domain SID */
267 Status = GetAccountDomainSid(&AccountDomainSid);
268 if (!NT_SUCCESS(Status))
269 {
270 ERR("GetAccountDomainSid() failed (Status 0x%08lx)\n", Status);
271 return Status;
272 }
273
274 /* Connect to the SAM server */
275 Status = SamIConnect(NULL, &ServerHandle, SAM_SERVER_CONNECT | SAM_SERVER_LOOKUP_DOMAIN, TRUE);
276 if (!NT_SUCCESS(Status))
277 {
278 TRACE("SamIConnect() failed (Status 0x%08lx)\n", Status);
279 goto done;
280 }
281
282 /* Open the account domain */
283 Status = SamrOpenDomain(ServerHandle, DOMAIN_LOOKUP, AccountDomainSid, &DomainHandle);
284 if (!NT_SUCCESS(Status))
285 {
286 ERR("SamrOpenDomain failed (Status %08lx)\n", Status);
287 goto done;
288 }
289
290 Names[0].Length = UserName->Length;
291 Names[0].MaximumLength = UserName->MaximumLength;
292 Names[0].Buffer = UserName->Buffer;
293
294 /* Try to get the RID for the user name */
295 Status = SamrLookupNamesInDomain(DomainHandle, 1, Names, &RelativeIds, &Use);
296 if (!NT_SUCCESS(Status))
297 {
298 ERR("SamrLookupNamesInDomain failed (Status %08lx)\n", Status);
299 Status = STATUS_NO_SUCH_USER;
300 // FIXME: Try without domain?
301 goto done;
302 }
303
304 /* Fail, if it is not a user account */
305 if (Use.Element[0] != SidTypeUser)
306 {
307 ERR("Account is not a user account!\n");
308 Status = STATUS_NO_SUCH_USER;
309 goto done;
310 }
311
312 /* Open the user object */
313 Status = SamrOpenUser(DomainHandle,
314 USER_READ_GENERAL | USER_READ_LOGON |
315 USER_READ_ACCOUNT | USER_READ_PREFERENCES, /* FIXME */
316 RelativeIds.Element[0],
317 &UserHandle);
318 if (!NT_SUCCESS(Status))
319 {
320 ERR("SamrOpenUser failed (Status %08lx)\n", Status);
321 goto done;
322 }
323
324 Status = SamrQueryInformationUser(UserHandle, UserAllInformation, &UserInfo);
325 if (!NT_SUCCESS(Status))
326 {
327 ERR("SamrQueryInformationUser failed (Status %08lx)\n", Status);
328 goto done;
329 }
330
331 TRACE("UserName: %wZ\n", &UserInfo->All.UserName);
332
333 /* Check the password */
334 if ((UserInfo->All.UserAccountControl & USER_PASSWORD_NOT_REQUIRED) == 0)
335 {
336 Status = MsvpCheckPassword(PwdData, UserInfo);
337 if (!NT_SUCCESS(Status))
338 {
339 ERR("MsvpCheckPassword failed (Status %08lx)\n", Status);
340 goto done;
341 }
342 }
343
344 /* Check account restrictions for non-administrator accounts */
345 if (RelativeIds.Element[0] != DOMAIN_USER_RID_ADMIN)
346 {
347 /* Check if the account has been disabled */
348 if (UserInfo->All.UserAccountControl & USER_ACCOUNT_DISABLED)
349 {
350 ERR("Account disabled!\n");
351 *SubStatus = STATUS_ACCOUNT_DISABLED;
352 Status = STATUS_ACCOUNT_RESTRICTION;
353 goto done;
354 }
355
356 /* Check if the account has been locked */
357 if (UserInfo->All.UserAccountControl & USER_ACCOUNT_AUTO_LOCKED)
358 {
359 ERR("Account locked!\n");
360 *SubStatus = STATUS_ACCOUNT_LOCKED_OUT;
361 Status = STATUS_ACCOUNT_RESTRICTION;
362 goto done;
363 }
364
365 /* Check if the account expired */
366 if (LogonTime.QuadPart >= *(UINT64*)&UserInfo->All.AccountExpires)
367 {
368 ERR("Account expired!\n");
369 *SubStatus = STATUS_ACCOUNT_EXPIRED;
370 Status = STATUS_ACCOUNT_RESTRICTION;
371 goto done;
372 }
373
374 /* Check if the password expired */
375 if (LogonTime.QuadPart >= *(UINT64*)&UserInfo->All.PasswordMustChange)
376 {
377 ERR("Password expired!\n");
378 if (*(UINT64*)&UserInfo->All.PasswordLastSet == 0)
379 *SubStatus = STATUS_PASSWORD_MUST_CHANGE;
380 else
381 *SubStatus = STATUS_PASSWORD_EXPIRED;
382
383 Status = STATUS_ACCOUNT_RESTRICTION;
384 goto done;
385 }
386
387 /* Check logon hours */
388 if (!MsvpCheckLogonHours(&UserInfo->All.LogonHours, LogonTime))
389 {
390 ERR("Invalid logon hours!\n");
391 *SubStatus = STATUS_INVALID_LOGON_HOURS;
392 Status = STATUS_ACCOUNT_RESTRICTION;
393 goto done;
394 }
395
396 /* Check workstations */
397 if (!MsvpCheckWorkstations(&UserInfo->All.WorkStations, ComputerName->Buffer))
398 {
399 ERR("Invalid workstation!\n");
400 *SubStatus = STATUS_INVALID_WORKSTATION;
401 Status = STATUS_ACCOUNT_RESTRICTION;
402 goto done;
403 }
404 }
405 done:
406 if (NT_SUCCESS(Status))
407 {
408 *UserHandlePtr = UserHandle;
409 *AccountDomainSidPtr = AccountDomainSid;
410 *UserInfoPtr = UserInfo;
411 }
412 else
413 {
414 if (AccountDomainSid != NULL)
415 RtlFreeHeap(RtlGetProcessHeap(), 0, AccountDomainSid);
416
417 if (UserHandle != NULL)
418 SamrCloseHandle(&UserHandle);
419
420 SamIFree_SAMPR_USER_INFO_BUFFER(UserInfo,
421 UserAllInformation);
422 }
423
424 SamIFree_SAMPR_ULONG_ARRAY(&RelativeIds);
425 SamIFree_SAMPR_ULONG_ARRAY(&Use);
426
427 if (DomainHandle != NULL)
428 SamrCloseHandle(&DomainHandle);
429
430 if (ServerHandle != NULL)
431 SamrCloseHandle(&ServerHandle);
432
433 return Status;
434 }
435
436
437 static
438 NTSTATUS
GetNtAuthorityDomainSid(_In_ PRPC_SID * Sid)439 GetNtAuthorityDomainSid(
440 _In_ PRPC_SID *Sid)
441 {
442 SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
443 ULONG Length = 0;
444
445 Length = RtlLengthRequiredSid(0);
446 *Sid = RtlAllocateHeap(RtlGetProcessHeap(), 0, Length);
447 if (*Sid == NULL)
448 {
449 ERR("Failed to allocate SID\n");
450 return STATUS_INSUFFICIENT_RESOURCES;
451 }
452
453 RtlInitializeSid(*Sid,&NtAuthority, 0);
454
455 return STATUS_SUCCESS;
456 }
457
458
459 NTSTATUS
SamValidateUser(_In_ SECURITY_LOGON_TYPE LogonType,_In_ PUNICODE_STRING LogonUserName,_In_ PUNICODE_STRING LogonDomain,_In_ PLSA_SAM_PWD_DATA LogonPwdData,_In_ PUNICODE_STRING ComputerName,_Out_ PBOOL SpecialAccount,_Out_ PRPC_SID * AccountDomainSidPtr,_Out_ SAMPR_HANDLE * UserHandlePtr,_Out_ PSAMPR_USER_INFO_BUFFER * UserInfoPtr,_Out_ PNTSTATUS SubStatus)460 SamValidateUser(
461 _In_ SECURITY_LOGON_TYPE LogonType,
462 _In_ PUNICODE_STRING LogonUserName,
463 _In_ PUNICODE_STRING LogonDomain,
464 _In_ PLSA_SAM_PWD_DATA LogonPwdData,
465 _In_ PUNICODE_STRING ComputerName,
466 _Out_ PBOOL SpecialAccount,
467 _Out_ PRPC_SID* AccountDomainSidPtr,
468 _Out_ SAMPR_HANDLE* UserHandlePtr,
469 _Out_ PSAMPR_USER_INFO_BUFFER* UserInfoPtr,
470 _Out_ PNTSTATUS SubStatus)
471 {
472 static const UNICODE_STRING NtAuthorityU = RTL_CONSTANT_STRING(L"NT AUTHORITY");
473 static const UNICODE_STRING LocalServiceU = RTL_CONSTANT_STRING(L"LocalService");
474 static const UNICODE_STRING NetworkServiceU = RTL_CONSTANT_STRING(L"NetworkService");
475
476 NTSTATUS Status = STATUS_SUCCESS;
477
478 *SpecialAccount = FALSE;
479 *UserInfoPtr = NULL;
480 *SubStatus = STATUS_SUCCESS;
481
482 /* Check for special accounts */
483 // FIXME: Windows does not do this that way!! (msv1_0 does not contain these hardcoded values)
484 if (RtlEqualUnicodeString(LogonDomain, &NtAuthorityU, TRUE))
485 {
486 *SpecialAccount = TRUE;
487
488 /* Get the authority domain SID */
489 Status = GetNtAuthorityDomainSid(AccountDomainSidPtr);
490 if (!NT_SUCCESS(Status))
491 {
492 ERR("GetNtAuthorityDomainSid() failed (Status 0x%08lx)\n", Status);
493 return Status;
494 }
495
496 if (RtlEqualUnicodeString(LogonUserName, &LocalServiceU, TRUE))
497 {
498 TRACE("SpecialAccount: LocalService\n");
499
500 if (LogonType != Service)
501 return STATUS_LOGON_FAILURE;
502
503 *UserInfoPtr = RtlAllocateHeap(RtlGetProcessHeap(),
504 HEAP_ZERO_MEMORY,
505 sizeof(SAMPR_USER_ALL_INFORMATION));
506 if (*UserInfoPtr == NULL)
507 return STATUS_INSUFFICIENT_RESOURCES;
508
509 (*UserInfoPtr)->All.UserId = SECURITY_LOCAL_SERVICE_RID;
510 (*UserInfoPtr)->All.PrimaryGroupId = SECURITY_LOCAL_SERVICE_RID;
511 }
512 else if (RtlEqualUnicodeString(LogonUserName, &NetworkServiceU, TRUE))
513 {
514 TRACE("SpecialAccount: NetworkService\n");
515
516 if (LogonType != Service)
517 return STATUS_LOGON_FAILURE;
518
519 *UserInfoPtr = RtlAllocateHeap(RtlGetProcessHeap(),
520 HEAP_ZERO_MEMORY,
521 sizeof(SAMPR_USER_ALL_INFORMATION));
522 if (*UserInfoPtr == NULL)
523 return STATUS_INSUFFICIENT_RESOURCES;
524
525 (*UserInfoPtr)->All.UserId = SECURITY_NETWORK_SERVICE_RID;
526 (*UserInfoPtr)->All.PrimaryGroupId = SECURITY_NETWORK_SERVICE_RID;
527 }
528 else
529 {
530 return STATUS_NO_SUCH_USER;
531 }
532 }
533 else
534 {
535 TRACE("NormalAccount\n");
536 Status = SamValidateNormalUser(LogonUserName,
537 LogonPwdData,
538 ComputerName,
539 AccountDomainSidPtr,
540 UserHandlePtr,
541 UserInfoPtr,
542 SubStatus);
543 if (!NT_SUCCESS(Status))
544 {
545 ERR("SamValidateNormalUser() failed (Status 0x%08lx)\n", Status);
546 return Status;
547 }
548 }
549
550 return Status;
551 }
552