xref: /reactos/dll/win32/lsasrv/session.c (revision 6ae7fc2b)
1 /*
2  * PROJECT:     Local Security Authority Server DLL
3  * LICENSE:     GPL - See COPYING in the top level directory
4  * FILE:        dll/win32/lsasrv/session.c
5  * PURPOSE:     Logon session management routines
6  * COPYRIGHT:   Copyright 2013 Eric Kohl
7  */
8 
9 #include "lsasrv.h"
10 
11 typedef struct _LSAP_LOGON_SESSION
12 {
13     LIST_ENTRY Entry;
14     LUID LogonId;
15     ULONG LogonType;
16     ULONG Session;
17     LARGE_INTEGER LogonTime;
18     PSID Sid;
19     UNICODE_STRING UserName;
20     UNICODE_STRING LogonDomain;
21     UNICODE_STRING AuthenticationPackage;
22     UNICODE_STRING LogonServer;
23     UNICODE_STRING DnsDomainName;
24     UNICODE_STRING Upn;
25 } LSAP_LOGON_SESSION, *PLSAP_LOGON_SESSION;
26 
27 
28 /* GLOBALS *****************************************************************/
29 
30 LIST_ENTRY SessionListHead;
31 ULONG SessionCount;
32 
33 /* FUNCTIONS ***************************************************************/
34 
35 VOID
LsapInitLogonSessions(VOID)36 LsapInitLogonSessions(VOID)
37 {
38     InitializeListHead(&SessionListHead);
39     SessionCount = 0;
40 }
41 
42 
43 static
44 PLSAP_LOGON_SESSION
LsapGetLogonSession(IN PLUID LogonId)45 LsapGetLogonSession(IN PLUID LogonId)
46 {
47     PLIST_ENTRY SessionEntry;
48     PLSAP_LOGON_SESSION CurrentSession;
49 
50     SessionEntry = SessionListHead.Flink;
51     while (SessionEntry != &SessionListHead)
52     {
53         CurrentSession = CONTAINING_RECORD(SessionEntry,
54                                            LSAP_LOGON_SESSION,
55                                            Entry);
56         if (RtlEqualLuid(&CurrentSession->LogonId, LogonId))
57             return CurrentSession;
58 
59         SessionEntry = SessionEntry->Flink;
60     }
61 
62     return NULL;
63 }
64 
65 
66 NTSTATUS
LsapSetLogonSessionData(_In_ PLUID LogonId,_In_ ULONG LogonType,_In_ PUNICODE_STRING UserName,_In_ PUNICODE_STRING LogonDomain,_In_ PSID Sid)67 LsapSetLogonSessionData(
68     _In_ PLUID LogonId,
69     _In_ ULONG LogonType,
70     _In_ PUNICODE_STRING UserName,
71     _In_ PUNICODE_STRING LogonDomain,
72     _In_ PSID Sid)
73 {
74     NTSTATUS Status;
75     PLSAP_LOGON_SESSION Session;
76     ULONG Length;
77 
78     TRACE("LsapSetLogonSessionData(%p)\n", LogonId);
79 
80     Session = LsapGetLogonSession(LogonId);
81     if (Session == NULL)
82         return STATUS_NO_SUCH_LOGON_SESSION;
83 
84     TRACE("LogonType %lu\n", LogonType);
85     Session->LogonType = LogonType;
86 
87     Status = RtlValidateUnicodeString(0, UserName);
88     if (!NT_SUCCESS(Status))
89         return STATUS_INVALID_PARAMETER;
90 
91     /* UserName is mandatory and cannot be an empty string */
92     TRACE("UserName %wZ\n", UserName);
93     Session->UserName.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
94                                                HEAP_ZERO_MEMORY,
95                                                UserName->MaximumLength);
96     if (Session->UserName.Buffer == NULL)
97         return STATUS_INSUFFICIENT_RESOURCES;
98 
99     Session->UserName.Length = UserName->Length;
100     Session->UserName.MaximumLength = UserName->MaximumLength;
101     RtlCopyMemory(Session->UserName.Buffer, UserName->Buffer, UserName->MaximumLength);
102 
103     Status = RtlValidateUnicodeString(0, LogonDomain);
104     if (!NT_SUCCESS(Status))
105     {
106         /* Cleanup and fail */
107         if (Session->UserName.Buffer != NULL)
108             RtlFreeHeap(RtlGetProcessHeap(), 0, Session->UserName.Buffer);
109 
110         return STATUS_INVALID_PARAMETER;
111     }
112 
113     /* LogonDomain is optional and can be an empty string */
114     TRACE("LogonDomain %wZ\n", LogonDomain);
115     if (LogonDomain->Length)
116     {
117         Session->LogonDomain.Buffer = RtlAllocateHeap(RtlGetProcessHeap(),
118                                                       HEAP_ZERO_MEMORY,
119                                                       LogonDomain->MaximumLength);
120         if (Session->LogonDomain.Buffer == NULL)
121         {
122             /* Cleanup and fail */
123             if (Session->UserName.Buffer != NULL)
124                 RtlFreeHeap(RtlGetProcessHeap(), 0, Session->UserName.Buffer);
125 
126             return STATUS_INSUFFICIENT_RESOURCES;
127         }
128 
129         Session->LogonDomain.Length = LogonDomain->Length;
130         Session->LogonDomain.MaximumLength = LogonDomain->MaximumLength;
131         RtlCopyMemory(Session->LogonDomain.Buffer, LogonDomain->Buffer, LogonDomain->MaximumLength);
132     }
133     else
134     {
135         RtlInitEmptyUnicodeString(&Session->LogonDomain, NULL, 0);
136     }
137 
138     Length = RtlLengthSid(Sid);
139     Session->Sid = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Length);
140     if (Session->Sid == NULL)
141     {
142         /* Cleanup and fail */
143         if (Session->LogonDomain.Buffer != NULL)
144             RtlFreeHeap(RtlGetProcessHeap(), 0, Session->LogonDomain.Buffer);
145         if (Session->UserName.Buffer != NULL)
146             RtlFreeHeap(RtlGetProcessHeap(), 0, Session->UserName.Buffer);
147 
148         return STATUS_INSUFFICIENT_RESOURCES;
149     }
150 
151     RtlCopyMemory(Session->Sid, Sid, Length);
152 
153     return STATUS_SUCCESS;
154 }
155 
156 
157 NTSTATUS
158 NTAPI
LsapCreateLogonSession(IN PLUID LogonId)159 LsapCreateLogonSession(IN PLUID LogonId)
160 {
161     PLSAP_LOGON_SESSION Session;
162     NTSTATUS Status;
163 
164     TRACE("LsapCreateLogonSession(%p)\n", LogonId);
165 
166     /* Fail, if a session already exists */
167     if (LsapGetLogonSession(LogonId) != NULL)
168         return STATUS_LOGON_SESSION_COLLISION;
169 
170     /* Allocate a new session entry */
171     Session = RtlAllocateHeap(RtlGetProcessHeap(),
172                               HEAP_ZERO_MEMORY,
173                               sizeof(LSAP_LOGON_SESSION));
174     if (Session == NULL)
175         return STATUS_INSUFFICIENT_RESOURCES;
176 
177     /* Initialize the session entry */
178     RtlCopyLuid(&Session->LogonId, LogonId);
179 
180     TRACE("LsapCreateLogonSession(<0x%lx,0x%lx>)\n",
181           LogonId->HighPart, LogonId->LowPart);
182 
183     /* Tell ntoskrnl to create a new logon session */
184     Status = LsapRmCreateLogonSession(LogonId);
185     if (!NT_SUCCESS(Status))
186     {
187         RtlFreeHeap(RtlGetProcessHeap(), 0, Session);
188         return Status;
189     }
190 
191     /* Insert the new session into the session list */
192     InsertHeadList(&SessionListHead, &Session->Entry);
193     SessionCount++;
194 
195     return STATUS_SUCCESS;
196 }
197 
198 
199 NTSTATUS
200 NTAPI
LsapDeleteLogonSession(IN PLUID LogonId)201 LsapDeleteLogonSession(IN PLUID LogonId)
202 {
203     PLSAP_LOGON_SESSION Session;
204     NTSTATUS Status;
205 
206     TRACE("LsapDeleteLogonSession(%p)\n", LogonId);
207 
208     /* Fail, if the session does not exist */
209     Session = LsapGetLogonSession(LogonId);
210     if (Session == NULL)
211         return STATUS_NO_SUCH_LOGON_SESSION;
212 
213     TRACE("LsapDeleteLogonSession(0x%08lx%08lx)\n",
214           LogonId->HighPart, LogonId->LowPart);
215 
216     /* Tell ntoskrnl to delete the logon session */
217     Status = LsapRmDeleteLogonSession(LogonId);
218     if (!NT_SUCCESS(Status))
219         return Status;
220 
221     /* Notify the authentication packages */
222     LsapTerminateLogon(LogonId);
223 
224     /* Remove the session entry from the list */
225     RemoveEntryList(&Session->Entry);
226     SessionCount--;
227 
228     /* Free the session data */
229     if (Session->Sid != NULL)
230         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->Sid);
231 
232     if (Session->UserName.Buffer != NULL)
233         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->UserName.Buffer);
234 
235     if (Session->LogonDomain.Buffer != NULL)
236         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->LogonDomain.Buffer);
237 
238     if (Session->AuthenticationPackage.Buffer != NULL)
239         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->AuthenticationPackage.Buffer);
240 
241     if (Session->LogonServer.Buffer != NULL)
242         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->LogonServer.Buffer);
243 
244     if (Session->DnsDomainName.Buffer != NULL)
245         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->DnsDomainName.Buffer);
246 
247     if (Session->Upn.Buffer != NULL)
248         RtlFreeHeap(RtlGetProcessHeap(), 0, Session->Upn.Buffer);
249 
250     /* Free the session entry */
251     RtlFreeHeap(RtlGetProcessHeap(), 0, Session);
252 
253     return STATUS_SUCCESS;
254 }
255 
256 
257 NTSTATUS
258 NTAPI
LsapAddCredential(_In_ PLUID LogonId,_In_ ULONG AuthenticationPackage,_In_ PLSA_STRING PrimaryKeyValue,_In_ PLSA_STRING Credential)259 LsapAddCredential(
260     _In_ PLUID LogonId,
261     _In_ ULONG AuthenticationPackage,
262     _In_ PLSA_STRING PrimaryKeyValue,
263     _In_ PLSA_STRING Credential)
264 {
265 
266     return STATUS_SUCCESS;
267 }
268 
269 
270 NTSTATUS
271 NTAPI
LsapGetCredentials(_In_ PLUID LogonId,_In_ ULONG AuthenticationPackage,_Inout_ PULONG QueryContext,_In_ BOOLEAN RetrieveAllCredentials,_Inout_ PLSA_STRING PrimaryKeyValue,_Out_ PULONG PrimaryKeyLength,_Out_ PLSA_STRING Credentials)272 LsapGetCredentials(
273     _In_ PLUID LogonId,
274     _In_ ULONG AuthenticationPackage,
275     _Inout_ PULONG QueryContext,
276     _In_ BOOLEAN RetrieveAllCredentials,
277     _Inout_ PLSA_STRING PrimaryKeyValue,
278     _Out_ PULONG PrimaryKeyLength,
279     _Out_ PLSA_STRING Credentials)
280 {
281 
282     return STATUS_SUCCESS;
283 }
284 
285 
286 NTSTATUS
287 NTAPI
LsapDeleteCredential(_In_ PLUID LogonId,_In_ ULONG AuthenticationPackage,_In_ PLSA_STRING PrimaryKeyValue)288 LsapDeleteCredential(
289     _In_ PLUID LogonId,
290     _In_ ULONG AuthenticationPackage,
291     _In_ PLSA_STRING PrimaryKeyValue)
292 {
293 
294     return STATUS_SUCCESS;
295 }
296 
297 
298 NTSTATUS
LsapEnumLogonSessions(IN OUT PLSA_API_MSG RequestMsg)299 LsapEnumLogonSessions(IN OUT PLSA_API_MSG RequestMsg)
300 {
301     OBJECT_ATTRIBUTES ObjectAttributes;
302     HANDLE ProcessHandle = NULL;
303     PLIST_ENTRY SessionEntry;
304     PLSAP_LOGON_SESSION CurrentSession;
305     PLUID SessionList;
306     ULONG i, Length;
307     SIZE_T MemSize;
308     PVOID ClientBaseAddress = NULL;
309     NTSTATUS Status;
310 
311     TRACE("LsapEnumLogonSessions(%p)\n", RequestMsg);
312 
313     Length = SessionCount * sizeof(LUID);
314     SessionList = RtlAllocateHeap(RtlGetProcessHeap(),
315                                   HEAP_ZERO_MEMORY,
316                                   Length);
317     if (SessionList == NULL)
318         return STATUS_INSUFFICIENT_RESOURCES;
319 
320     i = 0;
321     SessionEntry = SessionListHead.Flink;
322     while (SessionEntry != &SessionListHead)
323     {
324         CurrentSession = CONTAINING_RECORD(SessionEntry,
325                                            LSAP_LOGON_SESSION,
326                                            Entry);
327 
328         RtlCopyLuid(&SessionList[i],
329                     &CurrentSession->LogonId);
330 
331         SessionEntry = SessionEntry->Flink;
332         i++;
333     }
334 
335     InitializeObjectAttributes(&ObjectAttributes,
336                                NULL,
337                                0,
338                                NULL,
339                                NULL);
340 
341     Status = NtOpenProcess(&ProcessHandle,
342                            PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,
343                            &ObjectAttributes,
344                            &RequestMsg->h.ClientId);
345     if (!NT_SUCCESS(Status))
346     {
347         TRACE("NtOpenProcess() failed (Status %lx)\n", Status);
348         goto done;
349     }
350 
351     TRACE("Length: %lu\n", Length);
352 
353     MemSize = Length;
354     Status = NtAllocateVirtualMemory(ProcessHandle,
355                                      &ClientBaseAddress,
356                                      0,
357                                      &MemSize,
358                                      MEM_COMMIT,
359                                      PAGE_READWRITE);
360     if (!NT_SUCCESS(Status))
361     {
362         TRACE("NtAllocateVirtualMemory() failed (Status %lx)\n", Status);
363         goto done;
364     }
365 
366     TRACE("MemSize: %lu\n", MemSize);
367     TRACE("ClientBaseAddress: %p\n", ClientBaseAddress);
368 
369     Status = NtWriteVirtualMemory(ProcessHandle,
370                                   ClientBaseAddress,
371                                   SessionList,
372                                   Length,
373                                   NULL);
374     if (!NT_SUCCESS(Status))
375     {
376         TRACE("NtWriteVirtualMemory() failed (Status %lx)\n", Status);
377         goto done;
378     }
379 
380     RequestMsg->EnumLogonSessions.Reply.LogonSessionCount = SessionCount;
381     RequestMsg->EnumLogonSessions.Reply.LogonSessionBuffer = ClientBaseAddress;
382 
383 done:
384     if (ProcessHandle != NULL)
385         NtClose(ProcessHandle);
386 
387     if (SessionList != NULL)
388         RtlFreeHeap(RtlGetProcessHeap(), 0, SessionList);
389 
390     return Status;
391 }
392 
393 
394 NTSTATUS
LsapGetLogonSessionData(IN OUT PLSA_API_MSG RequestMsg)395 LsapGetLogonSessionData(IN OUT PLSA_API_MSG RequestMsg)
396 {
397     OBJECT_ATTRIBUTES ObjectAttributes;
398     HANDLE ProcessHandle = NULL;
399     PLSAP_LOGON_SESSION Session;
400     PSECURITY_LOGON_SESSION_DATA LocalSessionData;
401     PVOID ClientBaseAddress = NULL;
402     ULONG TotalLength, SidLength = 0;
403     SIZE_T MemSize;
404     PUCHAR Ptr;
405     NTSTATUS Status;
406 
407     TRACE("LsapGetLogonSessionData(%p)\n", RequestMsg);
408 
409     TRACE("LogonId: %lx\n", RequestMsg->GetLogonSessionData.Request.LogonId.LowPart);
410     Session = LsapGetLogonSession(&RequestMsg->GetLogonSessionData.Request.LogonId);
411     if (Session == NULL)
412         return STATUS_NO_SUCH_LOGON_SESSION;
413 
414     /* Calculate the required buffer size */
415     TotalLength = sizeof(SECURITY_LOGON_SESSION_DATA) +
416                   Session->UserName.MaximumLength +
417                   Session->LogonDomain.MaximumLength +
418                   Session->AuthenticationPackage.MaximumLength +
419                   Session->LogonServer.MaximumLength +
420                   Session->DnsDomainName.MaximumLength +
421                   Session->Upn.MaximumLength;
422     if (Session->Sid != NULL)
423     {
424         SidLength = RtlLengthSid(Session->Sid);
425         TotalLength += SidLength;
426     }
427     TRACE("TotalLength: %lu\n", TotalLength);
428 
429     /* Allocate the buffer */
430     LocalSessionData = RtlAllocateHeap(RtlGetProcessHeap(),
431                                        HEAP_ZERO_MEMORY,
432                                        TotalLength);
433     if (LocalSessionData == NULL)
434         return STATUS_INSUFFICIENT_RESOURCES;
435 
436     Ptr = (PUCHAR)((ULONG_PTR)LocalSessionData + sizeof(SECURITY_LOGON_SESSION_DATA));
437     TRACE("LocalSessionData: %p  Ptr: %p\n", LocalSessionData, Ptr);
438 
439     LocalSessionData->Size = sizeof(SECURITY_LOGON_SESSION_DATA);
440 
441     /* Copy the LogonId */
442     RtlCopyLuid(&LocalSessionData->LogonId,
443                 &RequestMsg->GetLogonSessionData.Request.LogonId);
444 
445     /* Copy the UserName string */
446     LocalSessionData->UserName.Length = Session->UserName.Length;
447     LocalSessionData->UserName.MaximumLength = Session->UserName.MaximumLength;
448     if (Session->UserName.MaximumLength != 0)
449     {
450         RtlCopyMemory(Ptr, Session->UserName.Buffer, Session->UserName.MaximumLength);
451         LocalSessionData->UserName.Buffer = (PWSTR)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
452 
453         Ptr = (PUCHAR)((ULONG_PTR)Ptr + Session->UserName.MaximumLength);
454     }
455 
456     /* Copy the LogonDomain string */
457     LocalSessionData->LogonDomain.Length = Session->LogonDomain.Length;
458     LocalSessionData->LogonDomain.MaximumLength = Session->LogonDomain.MaximumLength;
459     if (Session->LogonDomain.MaximumLength != 0)
460     {
461         RtlCopyMemory(Ptr, Session->LogonDomain.Buffer, Session->LogonDomain.MaximumLength);
462         LocalSessionData->LogonDomain.Buffer = (PWSTR)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
463 
464         Ptr = (PUCHAR)((ULONG_PTR)Ptr + Session->LogonDomain.MaximumLength);
465     }
466 
467     /* Copy the AuthenticationPackage string */
468     LocalSessionData->AuthenticationPackage.Length = Session->AuthenticationPackage.Length;
469     LocalSessionData->AuthenticationPackage.MaximumLength = Session->AuthenticationPackage.MaximumLength;
470     if (Session->AuthenticationPackage.MaximumLength != 0)
471     {
472         RtlCopyMemory(Ptr, Session->AuthenticationPackage.Buffer, Session->AuthenticationPackage.MaximumLength);
473         LocalSessionData->AuthenticationPackage.Buffer = (PWSTR)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
474 
475         Ptr = (PUCHAR)((ULONG_PTR)Ptr + Session->AuthenticationPackage.MaximumLength);
476     }
477 
478     LocalSessionData->LogonType = Session->LogonType;
479     LocalSessionData->Session = 0;
480 
481     /* Sid */
482     if (Session->Sid != NULL)
483     {
484         RtlCopyMemory(Ptr, Session->Sid, SidLength);
485         LocalSessionData->Sid = (PSID)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
486 
487         Ptr = (PUCHAR)((ULONG_PTR)Ptr + SidLength);
488     }
489 
490     /* LogonTime */
491     LocalSessionData->LogonTime.QuadPart = Session->LogonTime.QuadPart;
492 
493     /* Copy the LogonServer string */
494     LocalSessionData->LogonServer.Length = Session->LogonServer.Length;
495     LocalSessionData->LogonServer.MaximumLength = Session->LogonServer.MaximumLength;
496     if (Session->LogonServer.MaximumLength != 0)
497     {
498         RtlCopyMemory(Ptr, Session->LogonServer.Buffer, Session->LogonServer.MaximumLength);
499         LocalSessionData->LogonServer.Buffer = (PWSTR)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
500 
501         Ptr = (PUCHAR)((ULONG_PTR)Ptr + Session->LogonServer.MaximumLength);
502     }
503 
504     /* Copy the DnsDomainName string */
505     LocalSessionData->DnsDomainName.Length = Session->DnsDomainName.Length;
506     LocalSessionData->DnsDomainName.MaximumLength = Session->DnsDomainName.MaximumLength;
507     if (Session->DnsDomainName.MaximumLength != 0)
508     {
509         RtlCopyMemory(Ptr, Session->DnsDomainName.Buffer, Session->DnsDomainName.MaximumLength);
510         LocalSessionData->DnsDomainName.Buffer = (PWSTR)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
511 
512         Ptr = (PUCHAR)((ULONG_PTR)Ptr + Session->DnsDomainName.MaximumLength);
513     }
514 
515     /* Copy the Upn string */
516     LocalSessionData->Upn.Length = Session->Upn.Length;
517     LocalSessionData->Upn.MaximumLength = Session->Upn.MaximumLength;
518     if (Session->Upn.MaximumLength != 0)
519     {
520         RtlCopyMemory(Ptr, Session->Upn.Buffer, Session->Upn.MaximumLength);
521         LocalSessionData->Upn.Buffer = (PWSTR)((ULONG_PTR)Ptr - (ULONG_PTR)LocalSessionData);
522 
523         Ptr = (PUCHAR)((ULONG_PTR)Ptr + Session->Upn.MaximumLength);
524     }
525 
526     InitializeObjectAttributes(&ObjectAttributes,
527                                NULL,
528                                0,
529                                NULL,
530                                NULL);
531 
532     Status = NtOpenProcess(&ProcessHandle,
533                            PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,
534                            &ObjectAttributes,
535                            &RequestMsg->h.ClientId);
536     if (!NT_SUCCESS(Status))
537     {
538         TRACE("NtOpenProcess() failed (Status %lx)\n", Status);
539         goto done;
540     }
541 
542     MemSize = TotalLength;
543     Status = NtAllocateVirtualMemory(ProcessHandle,
544                                      &ClientBaseAddress,
545                                      0,
546                                      &MemSize,
547                                      MEM_COMMIT,
548                                      PAGE_READWRITE);
549     if (!NT_SUCCESS(Status))
550     {
551         TRACE("NtAllocateVirtualMemory() failed (Status %lx)\n", Status);
552         goto done;
553     }
554 
555     TRACE("MemSize: %lu\n", MemSize);
556     TRACE("ClientBaseAddress: %p\n", ClientBaseAddress);
557 
558     Status = NtWriteVirtualMemory(ProcessHandle,
559                                   ClientBaseAddress,
560                                   LocalSessionData,
561                                   TotalLength,
562                                   NULL);
563     if (!NT_SUCCESS(Status))
564     {
565         TRACE("NtWriteVirtualMemory() failed (Status %lx)\n", Status);
566         goto done;
567     }
568 
569     RequestMsg->GetLogonSessionData.Reply.SessionDataBuffer = ClientBaseAddress;
570 
571 done:
572     if (ProcessHandle != NULL)
573         NtClose(ProcessHandle);
574 
575     if (LocalSessionData != NULL)
576         RtlFreeHeap(RtlGetProcessHeap(), 0, LocalSessionData);
577 
578     return Status;
579 }
580 
581 /* EOF */
582