xref: /reactos/dll/win32/msv1_0/sam.c (revision f308c6a2)
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
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
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
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
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
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
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
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