xref: /reactos/subsystems/win/basesrv/dosdev.c (revision 62919904)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Base API Server DLL
4  * FILE:            subsystems/win/basesrv/dosdev.c
5  * PURPOSE:         DOS Devices Management
6  * PROGRAMMERS:     Pierre Schweitzer (pierre.schweitzer@reactos.org)
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "basesrv.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 typedef struct _BSM_REQUEST
17 {
18     struct _BSM_REQUEST * Next;
19     LUID BroadcastLuid;
20     LONG DriveLetter;
21     LONG RemoveDefinition;
22 } BSM_REQUEST, *PBSM_REQUEST;
23 
24 /* GLOBALS ********************************************************************/
25 
26 static RTL_CRITICAL_SECTION BaseDefineDosDeviceCritSec;
27 RTL_CRITICAL_SECTION BaseSrvDDDBSMCritSec;
28 PBSM_REQUEST BSM_Request_Queue = NULL, BSM_Request_Queue_End = NULL;
29 ULONG BaseSrvpBSMThreadCount = 0;
30 LONG (WINAPI *PBROADCASTSYSTEMMESSAGEEXW)(DWORD, LPDWORD, UINT, WPARAM, LPARAM, PBSMINFO) = NULL;
31 
32 /* PRIVATE FUNCTIONS **********************************************************/
33 
34 VOID BaseInitDefineDosDevice(VOID)
35 {
36     RtlInitializeCriticalSection(&BaseDefineDosDeviceCritSec);
37 }
38 
39 VOID BaseCleanupDefineDosDevice(VOID)
40 {
41     RtlDeleteCriticalSection(&BaseDefineDosDeviceCritSec);
42 }
43 
44 NTSTATUS
45 GetCallerLuid(PLUID CallerLuid)
46 {
47     NTSTATUS Status;
48     HANDLE TokenHandle;
49     ULONG ReturnLength;
50     TOKEN_STATISTICS TokenInformation;
51 
52     /* We need an output buffer */
53     if (CallerLuid == NULL)
54     {
55         return STATUS_INVALID_PARAMETER;
56     }
57 
58     /* Open thread token */
59     TokenHandle = 0;
60     ReturnLength = 0;
61     Status = NtOpenThreadToken(NtCurrentThread(),
62                                READ_CONTROL | TOKEN_QUERY,
63                                FALSE, &TokenHandle);
64     /* If we fail, open process token */
65     if (Status == STATUS_NO_TOKEN)
66     {
67         Status = NtOpenProcessToken(NtCurrentProcess(),
68                                     READ_CONTROL | TOKEN_QUERY,
69                                     &TokenHandle);
70     }
71 
72     /* In case of a success get caller LUID and copy it back */
73     if (NT_SUCCESS(Status))
74     {
75         Status = NtQueryInformationToken(TokenHandle,
76                                          TokenStatistics,
77                                          &TokenInformation,
78                                          sizeof(TokenInformation),
79                                          &ReturnLength);
80         if (NT_SUCCESS(Status))
81         {
82             RtlCopyLuid(CallerLuid, &TokenInformation.AuthenticationId);
83         }
84     }
85 
86     /* Close token handle */
87     if (TokenHandle != 0)
88     {
89         NtClose(TokenHandle);
90     }
91 
92     return Status;
93 }
94 
95 NTSTATUS
96 IsGlobalSymbolicLink(HANDLE LinkHandle,
97                      PBOOLEAN IsGlobal)
98 {
99     NTSTATUS Status;
100     DWORD ReturnLength;
101     UNICODE_STRING GlobalString;
102     OBJECT_NAME_INFORMATION NameInfo, *PNameInfo;
103 
104     /* We need both parameters */
105     if (LinkHandle == 0 || IsGlobal == NULL)
106     {
107         return STATUS_INVALID_PARAMETER;
108     }
109 
110     PNameInfo = NULL;
111     _SEH2_TRY
112     {
113         /* Query handle information */
114         Status = NtQueryObject(LinkHandle,
115                                ObjectNameInformation,
116                                &NameInfo,
117                                0,
118                                &ReturnLength);
119         /* Only failure we tolerate is length mismatch */
120         if (NT_SUCCESS(Status) || Status == STATUS_INFO_LENGTH_MISMATCH)
121         {
122             /* Allocate big enough buffer */
123             PNameInfo = RtlAllocateHeap(BaseSrvHeap, 0, ReturnLength);
124             if (PNameInfo == NULL)
125             {
126                 Status = STATUS_NO_MEMORY;
127                 _SEH2_LEAVE;
128             }
129 
130             /* Query again handle information */
131             Status = NtQueryObject(LinkHandle,
132                                    ObjectNameInformation,
133                                    PNameInfo,
134                                    ReturnLength,
135                                    &ReturnLength);
136 
137             /*
138              * If it succeed, check we have Global??
139              * If so, return success
140              */
141             if (NT_SUCCESS(Status))
142             {
143                 RtlInitUnicodeString(&GlobalString, L"\\GLOBAL??");
144                 *IsGlobal = RtlPrefixUnicodeString(&GlobalString, &PNameInfo->Name, FALSE);
145                 Status = STATUS_SUCCESS;
146             }
147         }
148     }
149     _SEH2_FINALLY
150     {
151         if (PNameInfo != NULL)
152         {
153             RtlFreeHeap(BaseSrvHeap, 0, PNameInfo);
154         }
155     }
156     _SEH2_END;
157 
158     return Status;
159 }
160 
161 BOOLEAN
162 CheckForGlobalDriveLetter(SHORT DriveLetter)
163 {
164     WCHAR Path[8];
165     NTSTATUS Status;
166     BOOLEAN IsGlobal;
167     UNICODE_STRING PathU;
168     HANDLE SymbolicLinkHandle;
169     OBJECT_ATTRIBUTES ObjectAttributes;
170 
171     /* Setup our drive path */
172     wcsncpy(Path, L"\\??\\X:", (sizeof(L"\\??\\X:") / sizeof(WCHAR)));
173     Path[4] = DriveLetter + L'A';
174     Path[6] = UNICODE_NULL;
175 
176     /* Prepare everything to open the link */
177     RtlInitUnicodeString(&PathU, Path);
178     InitializeObjectAttributes(&ObjectAttributes,
179                                &PathU,
180                                OBJ_CASE_INSENSITIVE,
181                                NULL,
182                                NULL);
183 
184     /* Impersonate the caller */
185     if (!CsrImpersonateClient(NULL))
186     {
187         return FALSE;
188     }
189 
190     /* Open our drive letter */
191     Status = NtOpenSymbolicLinkObject(&SymbolicLinkHandle,
192                                       SYMBOLIC_LINK_QUERY,
193                                       &ObjectAttributes);
194 
195     CsrRevertToSelf();
196 
197     if (!NT_SUCCESS(Status))
198     {
199         return FALSE;
200     }
201 
202     /* Check whether it's global */
203     Status = IsGlobalSymbolicLink(SymbolicLinkHandle, &IsGlobal);
204     NtClose(SymbolicLinkHandle);
205 
206     if (!NT_SUCCESS(Status))
207     {
208         return FALSE;
209     }
210 
211     return IsGlobal;
212 }
213 
214 NTSTATUS
215 SendWinStationBSM(DWORD Flags,
216                   LPDWORD Recipients,
217                   UINT Message,
218                   WPARAM wParam,
219                   LPARAM lParam)
220 {
221     UNIMPLEMENTED;
222     return STATUS_NOT_IMPLEMENTED;
223 }
224 
225 NTSTATUS
226 BroadcastDriveLetterChange(LONG DriveLetter,
227                            BOOLEAN RemoveDefinition,
228                            PLUID BroadcastLuid)
229 {
230     HANDLE hUser32;
231     NTSTATUS Status;
232     UNICODE_STRING User32U;
233     ANSI_STRING ProcedureName;
234     DWORD Recipients, Flags, wParam;
235     LUID SystemLuid = SYSTEM_LUID;
236     BSMINFO Info;
237     DEV_BROADCAST_VOLUME Volume;
238 
239     /* We need a broadcast LUID */
240     if (BroadcastLuid == NULL)
241     {
242         return STATUS_INVALID_PARAMETER;
243     }
244 
245     /* Get the Csr procedure, and keep it forever */
246     if (PBROADCASTSYSTEMMESSAGEEXW == NULL)
247     {
248         hUser32 = NULL;
249         RtlInitUnicodeString(&User32U, L"user32");
250         Status = LdrGetDllHandle(NULL, NULL, &User32U, &hUser32);
251         if (hUser32 != NULL && NT_SUCCESS(Status))
252         {
253             RtlInitString(&ProcedureName, "CsrBroadcastSystemMessageExW");
254             Status = LdrGetProcedureAddress(hUser32,
255                                             &ProcedureName,
256                                             0,
257                                             (PVOID *)&PBROADCASTSYSTEMMESSAGEEXW);
258             if (!NT_SUCCESS(Status))
259             {
260                 PBROADCASTSYSTEMMESSAGEEXW = NULL;
261             }
262         }
263 
264         /* If we failed to get broadcast procedure, no more actions left */
265         if (PBROADCASTSYSTEMMESSAGEEXW == NULL)
266         {
267             return Status;
268         }
269     }
270 
271     /* Initialize broadcast info */
272     Info.cbSize = sizeof(BSMINFO);
273     Info.hdesk = 0;
274     Info.hwnd = 0;
275     RtlCopyLuid(&Info.luid, BroadcastLuid);
276 
277     /* Initialize volume information */
278     Volume.dbcv_size = sizeof(DEV_BROADCAST_VOLUME);
279     Volume.dbcv_devicetype = DBT_DEVTYP_VOLUME;
280     Volume.dbcv_reserved = 0;
281     Volume.dbcv_unitmask = 1 << DriveLetter;
282     Volume.dbcv_flags = DBTF_NET;
283 
284     /* Wide broadcast */
285     Recipients = BSM_APPLICATIONS | BSM_ALLDESKTOPS;
286     Flags = BSF_NOHANG | BSF_NOTIMEOUTIFNOTHUNG | BSF_FORCEIFHUNG;
287 
288     /*
289      * If we don't broadcast as system, it's not a global drive
290      * notification, then mark it as LUID mapped drive
291      */
292     if (!RtlEqualLuid(&Info.luid, &SystemLuid))
293     {
294         Flags |= BSF_LUID;
295     }
296 
297     /* Set event type */
298     wParam = RemoveDefinition ? DBT_DEVICEREMOVECOMPLETE : DBT_DEVICEARRIVAL;
299 
300     /* And broadcast! */
301     Status = PBROADCASTSYSTEMMESSAGEEXW(Flags, &Recipients, WM_DEVICECHANGE, wParam, (LPARAM)&Volume, &Info);
302 
303     /* If the drive is global, notify Winsta */
304     if (!(Flags & BSF_LUID))
305     {
306         Status = SendWinStationBSM(Flags, &Recipients, WM_DEVICECHANGE, wParam, (LPARAM)&Volume);
307     }
308 
309     return Status;
310 }
311 
312 ULONG
313 NTAPI
314 BaseSrvBSMThread(PVOID StartupContext)
315 {
316     ULONG ExitStatus;
317     NTSTATUS Status;
318     PBSM_REQUEST CurrentRequest;
319 
320     /* We have a thread */
321     ExitStatus = 0;
322     RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec);
323     ++BaseSrvpBSMThreadCount;
324 
325     while (TRUE)
326     {
327         /* If we flushed the queue, job done */
328         if (BSM_Request_Queue == NULL)
329         {
330             break;
331         }
332 
333         /* Queue current request, and remove it from the queue */
334         CurrentRequest = BSM_Request_Queue;
335         BSM_Request_Queue = BSM_Request_Queue->Next;
336 
337         /* If that was the last request, NULLify queue end */
338         if (BSM_Request_Queue == NULL)
339         {
340             BSM_Request_Queue_End = NULL;
341         }
342 
343         RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
344 
345         /* Broadcast the message */
346         Status = BroadcastDriveLetterChange(CurrentRequest->DriveLetter,
347                                             CurrentRequest->RemoveDefinition,
348                                             &CurrentRequest->BroadcastLuid);
349 
350         /* Reflect the last entry status on stop */
351         CurrentRequest->Next = NULL;
352         ExitStatus = Status;
353 
354         RtlFreeHeap(BaseSrvHeap, 0, CurrentRequest);
355         RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec);
356     }
357 
358     /* Here, we've flushed the queue, quit the user thread */
359     --BaseSrvpBSMThreadCount;
360     RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
361 
362     NtCurrentTeb()->FreeStackOnTermination = TRUE;
363     NtTerminateThread(NtCurrentThread(), ExitStatus);
364 
365     return ExitStatus;
366 }
367 
368 NTSTATUS
369 CreateBSMThread(VOID)
370 {
371     /* This can only be true for LUID mappings */
372     if (BaseStaticServerData->LUIDDeviceMapsEnabled == 0)
373     {
374         return STATUS_ACCESS_DENIED;
375     }
376 
377     /* Create our user thread */
378     return RtlCreateUserThread(NtCurrentProcess(),
379                                NULL,
380                                FALSE,
381                                0,
382                                0,
383                                0,
384                                BaseSrvBSMThread,
385                                NULL,
386                                NULL,
387                                NULL);
388 }
389 
390 NTSTATUS
391 AddBSMRequest(LONG DriveLetter,
392               BOOLEAN RemoveDefinition,
393               PLUID BroadcastLuid)
394 {
395     LUID CallerLuid;
396     NTSTATUS Status;
397     LUID SystemLuid = SYSTEM_LUID;
398     PBSM_REQUEST Request;
399 
400     /* We need a broadcast LUID */
401     if (BroadcastLuid == NULL)
402     {
403         return STATUS_INVALID_PARAMETER;
404     }
405 
406     /*
407      * If LUID mappings are not enabled, this call makes no sense
408      * It should not happen though
409      */
410     if (BaseStaticServerData->LUIDDeviceMapsEnabled == 0)
411     {
412         return STATUS_ACCESS_DENIED;
413     }
414 
415     /* Get our caller LUID (not the broadcaster!) */
416     Status = GetCallerLuid(&CallerLuid);
417     if (!NT_SUCCESS(Status))
418     {
419         return Status;
420     }
421 
422     /* System cannot create LUID mapped drives - thus broadcast makes no sense */
423     if (!RtlEqualLuid(&CallerLuid, &SystemLuid))
424     {
425         return STATUS_ACCESS_DENIED;
426     }
427 
428     /* Allocate our request */
429     Request = RtlAllocateHeap(BaseSrvHeap, 0, sizeof(BSM_REQUEST));
430     if (Request == NULL)
431     {
432         return STATUS_NO_MEMORY;
433     }
434 
435     /* Initialize it */
436     Request->DriveLetter = DriveLetter;
437     Request->RemoveDefinition = RemoveDefinition;
438     RtlCopyLuid(&Request->BroadcastLuid, BroadcastLuid);
439     Request->Next = NULL;
440 
441     /* And queue it */
442     RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec);
443 
444     /* At the end of the queue if not empty */
445     if (BSM_Request_Queue_End != NULL)
446     {
447         BSM_Request_Queue_End->Next = Request;
448     }
449     /* Otherwise, initialize the queue */
450     else
451     {
452         BSM_Request_Queue = Request;
453     }
454 
455     /* We're in FIFO mode */
456     BSM_Request_Queue_End = Request;
457 
458     /* If we don't have a messaging thread running, then start one */
459     if (BaseSrvpBSMThreadCount >= 1)
460     {
461         RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
462     }
463     else
464     {
465         RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
466         Status = CreateBSMThread();
467     }
468 
469     return Status;
470 }
471 
472 /* PUBLIC SERVER APIS *********************************************************/
473 
474 CSR_API(BaseSrvDefineDosDevice)
475 {
476     NTSTATUS Status;
477     PBASE_DEFINE_DOS_DEVICE DefineDosDeviceRequest = &((PBASE_API_MESSAGE)ApiMessage)->Data.DefineDosDeviceRequest;
478     OBJECT_ATTRIBUTES ObjectAttributes;
479     HANDLE LinkHandle;
480     UNICODE_STRING DeviceName = {0};
481     UNICODE_STRING LinkTarget = {0};
482     ULONG Length;
483     SID_IDENTIFIER_AUTHORITY WorldAuthority = {SECURITY_WORLD_SID_AUTHORITY};
484     SID_IDENTIFIER_AUTHORITY SystemAuthority = {SECURITY_NT_AUTHORITY};
485     PSID SystemSid;
486     PSID WorldSid;
487     PWSTR lpBuffer;
488     WCHAR Letter;
489     SHORT AbsLetter;
490     BOOLEAN DriveLetter = FALSE;
491     BOOLEAN RemoveDefinition;
492     BOOLEAN HandleTarget;
493     BOOLEAN Broadcast = FALSE;
494     BOOLEAN IsGlobal = FALSE;
495     ULONG CchLengthLeft;
496     ULONG CchLength;
497     ULONG TargetLength;
498     PWSTR TargetBuffer;
499     PWSTR CurrentBuffer;
500     /* We store them on the stack, they are known in advance */
501     union {
502         SECURITY_DESCRIPTOR SecurityDescriptor;
503         UCHAR Buffer[20];
504     } SecurityDescriptor;
505     union {
506         ACL Dacl;
507         UCHAR Buffer[256];
508     } Dacl;
509     ACCESS_MASK AccessMask;
510     LUID CallerLuid;
511     WCHAR * CurrentPtr;
512     WCHAR CurrentChar;
513     PWSTR OrigPtr;
514     PWSTR InterPtr;
515     BOOLEAN RemoveFound;
516 
517 #if 0
518     /* FIXME: Check why it fails.... */
519     if (!CsrValidateMessageBuffer(ApiMessage,
520                                   (PVOID*)&DefineDosDeviceRequest->DeviceName,
521                                   DefineDosDeviceRequest->DeviceName.Length,
522                                   1) ||
523         (DefineDosDeviceRequest->DeviceName.Length & 1) != 0 ||
524         !CsrValidateMessageBuffer(ApiMessage,
525                                   (PVOID*)&DefineDosDeviceRequest->TargetPath,
526                                   (DefineDosDeviceRequest->TargetPath.Length != 0 ? sizeof(UNICODE_NULL) : 0) + DefineDosDeviceRequest->TargetPath.Length,
527                                   1) ||
528         (DefineDosDeviceRequest->TargetPath.Length & 1) != 0)
529     {
530         return STATUS_INVALID_PARAMETER;
531     }
532 #endif
533 
534     DPRINT("BaseSrvDefineDosDevice entered, Flags:%d, DeviceName:%wZ (%d), TargetPath:%wZ (%d)\n",
535            DefineDosDeviceRequest->Flags,
536            &DefineDosDeviceRequest->DeviceName,
537            DefineDosDeviceRequest->DeviceName.Length,
538            &DefineDosDeviceRequest->TargetPath,
539            DefineDosDeviceRequest->TargetPath.Length);
540 
541     /*
542      * Allocate a buffer big enough to contain:
543      * - device name
544      * - targets
545      */
546     lpBuffer = RtlAllocateHeap(BaseSrvHeap, 0, 0x2000);
547     if (lpBuffer == NULL)
548     {
549         return STATUS_NO_MEMORY;
550     }
551 
552     /* Enter our critical section */
553     Status = RtlEnterCriticalSection(&BaseDefineDosDeviceCritSec);
554     if (!NT_SUCCESS(Status))
555     {
556         DPRINT1("RtlEnterCriticalSection() failed (Status %lx)\n",
557                 Status);
558         RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
559         return Status;
560     }
561 
562     LinkHandle = 0;
563     /* Does the caller wants to remove definition? */
564     RemoveDefinition = !!(DefineDosDeviceRequest->Flags & DDD_REMOVE_DEFINITION);
565     _SEH2_TRY
566     {
567         /* First of all, check if that's a drive letter device amongst LUID mappings */
568         if (BaseStaticServerData->LUIDDeviceMapsEnabled && !(DefineDosDeviceRequest->Flags & DDD_NO_BROADCAST_SYSTEM))
569         {
570             if (DefineDosDeviceRequest->DeviceName.Buffer != NULL &&
571                 DefineDosDeviceRequest->DeviceName.Length == 2 * sizeof(WCHAR) &&
572                 DefineDosDeviceRequest->DeviceName.Buffer[1] == L':')
573             {
574                 Letter = DefineDosDeviceRequest->DeviceName.Buffer[0];
575 
576                 /* Handle both lower cases and upper cases */
577                 AbsLetter = Letter - L'a';
578                 if (AbsLetter < 26 && AbsLetter >= 0)
579                 {
580                     Letter = RtlUpcaseUnicodeChar(Letter);
581                 }
582 
583                 AbsLetter = Letter - L'A';
584                 if (AbsLetter < 26)
585                 {
586                     /* That's a letter! */
587                     DriveLetter = TRUE;
588                 }
589             }
590         }
591 
592         /* We can only broadcast drive letters in case of LUID mappings */
593         if (DefineDosDeviceRequest->Flags & DDD_LUID_BROADCAST_DRIVE &&
594             !DriveLetter)
595         {
596             Status = STATUS_INVALID_PARAMETER;
597             _SEH2_LEAVE;
598         }
599 
600         /* First usage of our buffer: create device name */
601         CchLength = _snwprintf(lpBuffer, 0x1000, L"\\??\\%wZ", &DefineDosDeviceRequest->DeviceName);
602         CchLengthLeft = 0x1000 - 1 - CchLength; /* UNICODE_NULL */
603         CurrentBuffer = lpBuffer + CchLength + 1; /* UNICODE_NULL */
604         RtlInitUnicodeString(&DeviceName, lpBuffer);
605 
606         /* And prepare to open it */
607         InitializeObjectAttributes(&ObjectAttributes,
608                                    &DeviceName,
609                                    OBJ_CASE_INSENSITIVE,
610                                    NULL,
611                                    NULL);
612 
613         /* Assume it's OK and has a target to deal with */
614         HandleTarget = TRUE;
615 
616         /* Move to the client context if the mapping was local */
617         if (!CsrImpersonateClient(NULL))
618         {
619             Status = STATUS_BAD_IMPERSONATION_LEVEL;
620             _SEH2_LEAVE;
621         }
622 
623         /*
624          * While impersonating the caller, also get its LUID.
625          * This is mandatory in case we have a driver letter,
626          * Because we're in the case we've got LUID mapping
627          * enabled and broadcasting enabled. LUID will be required
628          * for the latter
629          */
630         if (DriveLetter)
631         {
632             Status = GetCallerLuid(&CallerLuid);
633             if (NT_SUCCESS(Status))
634             {
635                 Broadcast = TRUE;
636             }
637         }
638 
639         /* Now, open the device */
640         Status = NtOpenSymbolicLinkObject(&LinkHandle,
641                                           DELETE | SYMBOLIC_LINK_QUERY,
642                                           &ObjectAttributes);
643 
644         /* And get back to our context */
645         CsrRevertToSelf();
646 
647         /* In case of LUID broadcast, do nothing but return to trigger broadcast */
648         if (DefineDosDeviceRequest->Flags & DDD_LUID_BROADCAST_DRIVE)
649         {
650             /* Zero handle in case of a failure */
651             if (!NT_SUCCESS(Status))
652             {
653                 LinkHandle = 0;
654             }
655 
656             /* If removal was asked, and no object found: the remval was successful */
657             if (RemoveDefinition && Status == STATUS_OBJECT_NAME_NOT_FOUND)
658             {
659                 Status = STATUS_SUCCESS;
660             }
661 
662             /* We're done here, nothing more to do */
663             _SEH2_LEAVE;
664         }
665 
666         /* If device was not found */
667         if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
668         {
669             /* No handle */
670             LinkHandle = 0;
671 
672             /* If we were asked to remove... */
673             if (RemoveDefinition)
674             {
675                 /*
676                  * If caller asked to pop first entry, nothing specific,
677                  * then, we can consider this as a success
678                  */
679                 if (DefineDosDeviceRequest->TargetPath.Length == 0)
680                 {
681                     Status = STATUS_SUCCESS;
682                 }
683 
684                 /* We're done, nothing to change */
685                 _SEH2_LEAVE;
686             }
687 
688             /* There's no target to handle */
689             HandleTarget = FALSE;
690 
691             /*
692              * We'll consider, that's a success
693              * Failing to open the device doesn't prevent
694              * from creating it later on to create
695              * the linking.
696              */
697             Status = STATUS_SUCCESS;
698         }
699         else
700         {
701             /* Unexpected failure, forward to caller */
702             if (!NT_SUCCESS(Status))
703             {
704                 _SEH2_LEAVE;
705             }
706 
707             /* If LUID mapping enabled */
708             if (BaseStaticServerData->LUIDDeviceMapsEnabled)
709             {
710                 /* Check if that's global link */
711                 Status = IsGlobalSymbolicLink(LinkHandle, &IsGlobal);
712                 if (!NT_SUCCESS(Status))
713                 {
714                     _SEH2_LEAVE;
715                 }
716 
717                 /* If so, change our device name namespace to GLOBAL?? for link creation */
718                 if (IsGlobal)
719                 {
720                     CchLength = _snwprintf(lpBuffer, 0x1000, L"\\GLOBAL??\\%wZ", &DefineDosDeviceRequest->DeviceName);
721                     CchLengthLeft = 0x1000 - 1 - CchLength; /* UNICODE_NULL */
722                     CurrentBuffer = lpBuffer + CchLength + 1; /* UNICODE_NULL */
723 
724                     DeviceName.Length = CchLength * sizeof(WCHAR);
725                     DeviceName.MaximumLength = CchLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
726                 }
727             }
728         }
729 
730         /* If caller provided a target */
731         if (DefineDosDeviceRequest->TargetPath.Length != 0)
732         {
733             /* Make sure it's null terminated */
734             DefineDosDeviceRequest->TargetPath.Buffer[DefineDosDeviceRequest->TargetPath.Length / sizeof(WCHAR)] = UNICODE_NULL;
735 
736             /* Compute its size */
737             TargetLength = wcslen(DefineDosDeviceRequest->TargetPath.Buffer);
738 
739             /* And make sure it fits our buffer */
740             if (TargetLength + 1 >= CchLengthLeft)
741             {
742                 Status = STATUS_INVALID_PARAMETER;
743                 _SEH2_LEAVE;
744             }
745 
746             /* Copy it to our internal buffer */
747             RtlMoveMemory(CurrentBuffer, DefineDosDeviceRequest->TargetPath.Buffer, TargetLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
748             TargetBuffer = CurrentBuffer;
749 
750             /* Update our buffer status */
751             CchLengthLeft -= (TargetLength + 1);
752             CurrentBuffer += (TargetLength + 1);
753         }
754         /* Otherwise, zero everything */
755         else
756         {
757             TargetBuffer = NULL;
758             TargetLength = 0;
759         }
760 
761         /* If we opened the device, then, handle its current target */
762         if (HandleTarget)
763         {
764             /* Query it with our internal buffer */
765             LinkTarget.Length = 0;
766             LinkTarget.MaximumLength = CchLengthLeft * sizeof(WCHAR);
767             LinkTarget.Buffer = CurrentBuffer;
768 
769             Status = NtQuerySymbolicLinkObject(LinkHandle,
770                                                &LinkTarget,
771                                                &Length);
772             /* If we overflow, give up */
773             if (Length == LinkTarget.MaximumLength)
774             {
775                 Status = STATUS_BUFFER_OVERFLOW;
776             }
777             /* In case of a failure, bye bye */
778             if (!NT_SUCCESS(Status))
779             {
780                 _SEH2_LEAVE;
781             }
782 
783             /*
784              * Properly null it for MULTI_SZ if needed
785              * Always update max length with
786              * the need size
787              * This is needed to hand relatively "small"
788              * strings to Ob and avoid killing ourselves
789              * on the next query
790              */
791             CchLength = Length / sizeof(WCHAR);
792             if (CchLength < 2 ||
793                 CurrentBuffer[CchLength - 2] != UNICODE_NULL ||
794                 CurrentBuffer[CchLength - 1] != UNICODE_NULL)
795             {
796                 CurrentBuffer[CchLength] = UNICODE_NULL;
797                 LinkTarget.MaximumLength = Length + sizeof(UNICODE_NULL);
798             }
799             else
800             {
801                 LinkTarget.MaximumLength = Length;
802             }
803         }
804         /* There's no target, and we're asked to remove, so null target */
805         else if (RemoveDefinition)
806         {
807             RtlInitUnicodeString(&LinkTarget, NULL);
808         }
809         /* There's a target provided - new device, update buffer */
810         else
811         {
812             RtlInitUnicodeString(&LinkTarget, CurrentBuffer - TargetLength - 1);
813         }
814 
815         /*
816          * We no longer need old symlink, just drop it, we'll recreate it now
817          * with updated target.
818          * The benefit of it is that if caller asked us to drop last target, then
819          * the device is removed and not dangling
820          */
821         if (LinkHandle != 0)
822         {
823             Status = NtMakeTemporaryObject(LinkHandle);
824             NtClose(LinkHandle);
825             LinkHandle = 0;
826         }
827 
828         /* At this point, we must have no failure */
829         if (!NT_SUCCESS(Status))
830         {
831             _SEH2_LEAVE;
832         }
833 
834         /*
835          * If we have to remove definition, let's start to browse our
836          * target to actually drop it.
837          */
838         if (RemoveDefinition)
839         {
840             /* We'll browse our multi sz string */
841             RemoveFound = FALSE;
842             CurrentPtr = LinkTarget.Buffer;
843             InterPtr = LinkTarget.Buffer;
844             while (*CurrentPtr != UNICODE_NULL)
845             {
846                 CchLength = 0;
847                 OrigPtr = CurrentPtr;
848                 /* First, find next string */
849                 while (TRUE)
850                 {
851                     CurrentChar = *CurrentPtr;
852                     ++CurrentPtr;
853 
854                     if (CurrentChar == UNICODE_NULL)
855                     {
856                         break;
857                     }
858 
859                     ++CchLength;
860                 }
861 
862                 /* This check is a bit tricky, but dead useful:
863                  * If on the previous loop, we found the caller provided target
864                  * in our list, then, we'll move current entry over the found one
865                  * So that, it gets deleted.
866                  * Also, if we don't find caller entry in our entries, then move
867                  * current entry in the string if a previous one got deleted
868                  */
869                 if (RemoveFound ||
870                     ((!(DefineDosDeviceRequest->Flags & DDD_EXACT_MATCH_ON_REMOVE) ||
871                       TargetLength != CchLength || _wcsicmp(OrigPtr, TargetBuffer) != 0) &&
872                      ((DefineDosDeviceRequest->Flags & DDD_EXACT_MATCH_ON_REMOVE) ||
873                       (TargetLength != 0 && _wcsnicmp(OrigPtr, TargetBuffer, TargetLength) != 0))))
874                 {
875                     if (InterPtr != OrigPtr)
876                     {
877                         RtlMoveMemory(InterPtr, OrigPtr, sizeof(WCHAR) * CchLength + sizeof(UNICODE_NULL));
878                     }
879 
880                     InterPtr += (CchLength + 1);
881                 }
882                 else
883                 {
884                     /* Match case! Remember for next loop turn and to delete it */
885                     RemoveFound = TRUE;
886                 }
887             }
888 
889             /*
890              * Drop last entry, as required (pop)
891              * If there was a match previously, everything
892              * is already moved, so we're just nulling
893              * the end of the string
894              * If there was no match, this is the pop
895              */
896             *InterPtr = UNICODE_NULL;
897             ++InterPtr;
898 
899             /* Compute new target length */
900             TargetLength = wcslen(LinkTarget.Buffer) * sizeof(WCHAR);
901             /*
902              * If it's empty, quit
903              * Beware, here, we quit with STATUS_SUCCESS, and that's expected!
904              * In case we dropped last target entry, then, it's empty
905              * and there's no need to recreate the device we deleted previously
906              */
907             if (TargetLength == 0)
908             {
909                 _SEH2_LEAVE;
910             }
911 
912             /* Update our target string */
913             LinkTarget.Length = TargetLength;
914             LinkTarget.MaximumLength = (ULONG_PTR)InterPtr - (ULONG_PTR)LinkTarget.Buffer;
915         }
916         /* If that's not a removal, just update the target to include new target */
917         else if (HandleTarget)
918         {
919             LinkTarget.Buffer = LinkTarget.Buffer - TargetLength - 1;
920             LinkTarget.Length = TargetLength * sizeof(WCHAR);
921             LinkTarget.MaximumLength += (TargetLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
922             TargetLength *= sizeof(WCHAR);
923         }
924         /* No changes */
925         else
926         {
927             TargetLength = LinkTarget.Length;
928         }
929 
930         /* Make sure we don't create empty symlink */
931         if (TargetLength == 0)
932         {
933             _SEH2_LEAVE;
934         }
935 
936         /* Initialize our SIDs for symlink ACLs */
937         Status = RtlAllocateAndInitializeSid(&WorldAuthority,
938                                              1,
939                                              SECURITY_NULL_RID,
940                                              SECURITY_NULL_RID,
941                                              SECURITY_NULL_RID,
942                                              SECURITY_NULL_RID,
943                                              SECURITY_NULL_RID,
944                                              SECURITY_NULL_RID,
945                                              SECURITY_NULL_RID,
946                                              SECURITY_NULL_RID,
947                                              &WorldSid);
948         if (!NT_SUCCESS(Status))
949         {
950             _SEH2_LEAVE;
951         }
952 
953         Status = RtlAllocateAndInitializeSid(&SystemAuthority,
954                                              1,
955                                              SECURITY_RESTRICTED_CODE_RID,
956                                              SECURITY_NULL_RID,
957                                              SECURITY_NULL_RID,
958                                              SECURITY_NULL_RID,
959                                              SECURITY_NULL_RID,
960                                              SECURITY_NULL_RID,
961                                              SECURITY_NULL_RID,
962                                              SECURITY_NULL_RID,
963                                              &SystemSid);
964         if (!NT_SUCCESS(Status))
965         {
966             RtlFreeSid(WorldSid);
967             _SEH2_LEAVE;
968         }
969 
970         /* Initialize our SD (on stack) */
971         RtlCreateSecurityDescriptor(&SecurityDescriptor,
972                                     SECURITY_DESCRIPTOR_REVISION);
973 
974         /* And our ACL (still on stack) */
975         RtlCreateAcl(&Dacl.Dacl, sizeof(Dacl), ACL_REVISION);
976 
977         /*
978          * For access mask, if we have no session ID, or if
979          * protection mode is disabled, make them wide open
980          */
981         if (SessionId == 0 ||
982             (ProtectionMode & 3) == 0)
983         {
984             AccessMask = DELETE | SYMBOLIC_LINK_QUERY;
985         }
986         else
987         {
988             AccessMask = SYMBOLIC_LINK_QUERY;
989         }
990 
991         /* Setup the ACL */
992         RtlAddAccessAllowedAce(&Dacl.Dacl, ACL_REVISION2, AccessMask, WorldSid);
993         RtlAddAccessAllowedAce(&Dacl.Dacl, ACL_REVISION2, AccessMask, SystemSid);
994 
995         /* Drop SIDs */
996         RtlFreeSid(WorldSid);
997         RtlFreeSid(SystemSid);
998 
999         /* Link DACL to the SD */
1000         RtlSetDaclSecurityDescriptor(&SecurityDescriptor, TRUE, &Dacl.Dacl, TRUE);
1001 
1002         /* And set it in the OA used for creation */
1003         ObjectAttributes.SecurityDescriptor = &SecurityDescriptor;
1004 
1005         /*
1006          * If LUID and not global, we need to impersonate the caller
1007          * to make it local.
1008          */
1009         if (BaseStaticServerData->LUIDDeviceMapsEnabled)
1010         {
1011             if (!IsGlobal)
1012             {
1013                 if (!CsrImpersonateClient(NULL))
1014                 {
1015                     Status = STATUS_BAD_IMPERSONATION_LEVEL;
1016                     _SEH2_LEAVE;
1017                 }
1018             }
1019         }
1020         /* The object will be permanent */
1021         else
1022         {
1023             ObjectAttributes.Attributes |= OBJ_PERMANENT;
1024         }
1025 
1026         /* (Re)Create the symbolic link/device */
1027         Status = NtCreateSymbolicLinkObject(&LinkHandle,
1028                                             SYMBOLIC_LINK_ALL_ACCESS,
1029                                             &ObjectAttributes,
1030                                             &LinkTarget);
1031 
1032         /* Revert to self if required */
1033         if (BaseStaticServerData->LUIDDeviceMapsEnabled && !IsGlobal)
1034         {
1035             CsrRevertToSelf();
1036         }
1037 
1038         /* In case of a success, make object permanent for LUID links */
1039         if (NT_SUCCESS(Status))
1040         {
1041             if (BaseStaticServerData->LUIDDeviceMapsEnabled)
1042             {
1043                 Status = NtMakePermanentObject(LinkHandle);
1044             }
1045 
1046             /* Close the link */
1047             NtClose(LinkHandle);
1048 
1049             /*
1050              * Specific failure case here:
1051              * We were asked to remove something
1052              * but we didn't find the something
1053              * (we recreated the symlink hence the fail here!)
1054              * so fail with appropriate status
1055              */
1056             if (RemoveDefinition && !RemoveFound)
1057             {
1058                 Status = STATUS_OBJECT_NAME_NOT_FOUND;
1059             }
1060         }
1061 
1062         /* We closed link, don't double close */
1063         LinkHandle = 0;
1064     }
1065     _SEH2_FINALLY
1066     {
1067         /* If we need to close the link, do it now */
1068         if (LinkHandle != 0)
1069         {
1070              NtClose(LinkHandle);
1071         }
1072 
1073         /* Free our internal buffer */
1074         RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
1075 
1076         /* Broadcast drive letter creation */
1077         if (DriveLetter && Status == STATUS_SUCCESS && Broadcast)
1078         {
1079             LUID SystemLuid = SYSTEM_LUID;
1080 
1081             /* If that's a global drive, broadcast as system */
1082             if (IsGlobal)
1083             {
1084                 RtlCopyLuid(&CallerLuid, &SystemLuid);
1085             }
1086 
1087             /* Broadcast the event */
1088             AddBSMRequest(AbsLetter, RemoveDefinition, &CallerLuid);
1089 
1090             /*
1091              * If we removed drive, and the drive was shadowing a global one
1092              * broadcast the arrival of the global drive (as system - global)
1093              */
1094             if (RemoveDefinition && !RtlEqualLuid(&CallerLuid, &SystemLuid))
1095             {
1096                 if (CheckForGlobalDriveLetter(AbsLetter))
1097                 {
1098                     AddBSMRequest(AbsLetter, FALSE, &CallerLuid);
1099                 }
1100             }
1101         }
1102 
1103         /* Done! */
1104         RtlLeaveCriticalSection(&BaseDefineDosDeviceCritSec);
1105     }
1106     _SEH2_END;
1107 
1108     return Status;
1109 }
1110 
1111 /* EOF */
1112