xref: /reactos/subsystems/win/basesrv/dosdev.c (revision 731eddfe)
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 
BaseInitDefineDosDevice(VOID)34 VOID BaseInitDefineDosDevice(VOID)
35 {
36     RtlInitializeCriticalSection(&BaseDefineDosDeviceCritSec);
37 }
38 
BaseCleanupDefineDosDevice(VOID)39 VOID BaseCleanupDefineDosDevice(VOID)
40 {
41     RtlDeleteCriticalSection(&BaseDefineDosDeviceCritSec);
42 }
43 
44 NTSTATUS
GetCallerLuid(PLUID CallerLuid)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
IsGlobalSymbolicLink(HANDLE LinkHandle,PBOOLEAN IsGlobal)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
CheckForGlobalDriveLetter(SHORT DriveLetter)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
SendWinStationBSM(DWORD Flags,LPDWORD Recipients,UINT Message,WPARAM wParam,LPARAM lParam)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
BroadcastDriveLetterChange(LONG DriveLetter,BOOLEAN RemoveDefinition,PLUID BroadcastLuid)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
BaseSrvBSMThread(PVOID StartupContext)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
CreateBSMThread(VOID)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
AddBSMRequest(LONG DriveLetter,BOOLEAN RemoveDefinition,PLUID BroadcastLuid)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 
CSR_API(BaseSrvDefineDosDevice)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 (!CsrValidateMessageBuffer(ApiMessage,
518                                   (PVOID*)&DefineDosDeviceRequest->DeviceName.Buffer,
519                                   DefineDosDeviceRequest->DeviceName.Length,
520                                   sizeof(BYTE)) ||
521         (DefineDosDeviceRequest->DeviceName.Length & 1) != 0 ||
522         !CsrValidateMessageBuffer(ApiMessage,
523                                   (PVOID*)&DefineDosDeviceRequest->TargetPath.Buffer,
524                                   DefineDosDeviceRequest->TargetPath.Length +
525                                     (DefineDosDeviceRequest->TargetPath.Length != 0
526                                         ? sizeof(UNICODE_NULL) : 0),
527                                   sizeof(BYTE)) ||
528         (DefineDosDeviceRequest->TargetPath.Length & 1) != 0)
529     {
530         return STATUS_INVALID_PARAMETER;
531     }
532 
533     DPRINT("BaseSrvDefineDosDevice entered, Flags:%d, DeviceName:%wZ (%d), TargetPath:%wZ (%d)\n",
534            DefineDosDeviceRequest->Flags,
535            &DefineDosDeviceRequest->DeviceName,
536            DefineDosDeviceRequest->DeviceName.Length,
537            &DefineDosDeviceRequest->TargetPath,
538            DefineDosDeviceRequest->TargetPath.Length);
539 
540     /*
541      * Allocate a buffer big enough to contain:
542      * - device name
543      * - targets
544      */
545     lpBuffer = RtlAllocateHeap(BaseSrvHeap, 0, 0x2000);
546     if (lpBuffer == NULL)
547     {
548         return STATUS_NO_MEMORY;
549     }
550 
551     /* Enter our critical section */
552     Status = RtlEnterCriticalSection(&BaseDefineDosDeviceCritSec);
553     if (!NT_SUCCESS(Status))
554     {
555         DPRINT1("RtlEnterCriticalSection() failed (Status %lx)\n",
556                 Status);
557         RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
558         return Status;
559     }
560 
561     LinkHandle = 0;
562     /* Does the caller wants to remove definition? */
563     RemoveDefinition = !!(DefineDosDeviceRequest->Flags & DDD_REMOVE_DEFINITION);
564     _SEH2_TRY
565     {
566         /* First of all, check if that's a drive letter device amongst LUID mappings */
567         if (BaseStaticServerData->LUIDDeviceMapsEnabled && !(DefineDosDeviceRequest->Flags & DDD_NO_BROADCAST_SYSTEM))
568         {
569             if (DefineDosDeviceRequest->DeviceName.Buffer != NULL &&
570                 DefineDosDeviceRequest->DeviceName.Length == 2 * sizeof(WCHAR) &&
571                 DefineDosDeviceRequest->DeviceName.Buffer[1] == L':')
572             {
573                 Letter = DefineDosDeviceRequest->DeviceName.Buffer[0];
574 
575                 /* Handle both lower cases and upper cases */
576                 AbsLetter = Letter - L'a';
577                 if (AbsLetter < 26 && AbsLetter >= 0)
578                 {
579                     Letter = RtlUpcaseUnicodeChar(Letter);
580                 }
581 
582                 AbsLetter = Letter - L'A';
583                 if (AbsLetter < 26)
584                 {
585                     /* That's a letter! */
586                     DriveLetter = TRUE;
587                 }
588             }
589         }
590 
591         /* We can only broadcast drive letters in case of LUID mappings */
592         if (DefineDosDeviceRequest->Flags & DDD_LUID_BROADCAST_DRIVE &&
593             !DriveLetter)
594         {
595             Status = STATUS_INVALID_PARAMETER;
596             _SEH2_LEAVE;
597         }
598 
599         /* First usage of our buffer: create device name */
600         CchLength = _snwprintf(lpBuffer, 0x1000, L"\\??\\%wZ", &DefineDosDeviceRequest->DeviceName);
601         CchLengthLeft = 0x1000 - 1 - CchLength; /* UNICODE_NULL */
602         CurrentBuffer = lpBuffer + CchLength + 1; /* UNICODE_NULL */
603         RtlInitUnicodeString(&DeviceName, lpBuffer);
604 
605         /* And prepare to open it */
606         InitializeObjectAttributes(&ObjectAttributes,
607                                    &DeviceName,
608                                    OBJ_CASE_INSENSITIVE,
609                                    NULL,
610                                    NULL);
611 
612         /* Assume it's OK and has a target to deal with */
613         HandleTarget = TRUE;
614 
615         /* Move to the client context if the mapping was local */
616         if (!CsrImpersonateClient(NULL))
617         {
618             Status = STATUS_BAD_IMPERSONATION_LEVEL;
619             _SEH2_LEAVE;
620         }
621 
622         /*
623          * While impersonating the caller, also get its LUID.
624          * This is mandatory in case we have a driver letter,
625          * Because we're in the case we've got LUID mapping
626          * enabled and broadcasting enabled. LUID will be required
627          * for the latter
628          */
629         if (DriveLetter)
630         {
631             Status = GetCallerLuid(&CallerLuid);
632             if (NT_SUCCESS(Status))
633             {
634                 Broadcast = TRUE;
635             }
636         }
637 
638         /* Now, open the device */
639         Status = NtOpenSymbolicLinkObject(&LinkHandle,
640                                           DELETE | SYMBOLIC_LINK_QUERY,
641                                           &ObjectAttributes);
642 
643         /* And get back to our context */
644         CsrRevertToSelf();
645 
646         /* In case of LUID broadcast, do nothing but return to trigger broadcast */
647         if (DefineDosDeviceRequest->Flags & DDD_LUID_BROADCAST_DRIVE)
648         {
649             /* Zero handle in case of a failure */
650             if (!NT_SUCCESS(Status))
651             {
652                 LinkHandle = 0;
653             }
654 
655             /* If removal was asked, and no object found: the remval was successful */
656             if (RemoveDefinition && Status == STATUS_OBJECT_NAME_NOT_FOUND)
657             {
658                 Status = STATUS_SUCCESS;
659             }
660 
661             /* We're done here, nothing more to do */
662             _SEH2_LEAVE;
663         }
664 
665         /* If device was not found */
666         if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
667         {
668             /* No handle */
669             LinkHandle = 0;
670 
671             /* If we were asked to remove... */
672             if (RemoveDefinition)
673             {
674                 /*
675                  * If caller asked to pop first entry, nothing specific,
676                  * then, we can consider this as a success
677                  */
678                 if (DefineDosDeviceRequest->TargetPath.Length == 0)
679                 {
680                     Status = STATUS_SUCCESS;
681                 }
682 
683                 /* We're done, nothing to change */
684                 _SEH2_LEAVE;
685             }
686 
687             /* There's no target to handle */
688             HandleTarget = FALSE;
689 
690             /*
691              * We'll consider, that's a success
692              * Failing to open the device doesn't prevent
693              * from creating it later on to create
694              * the linking.
695              */
696             Status = STATUS_SUCCESS;
697         }
698         else
699         {
700             /* Unexpected failure, forward to caller */
701             if (!NT_SUCCESS(Status))
702             {
703                 _SEH2_LEAVE;
704             }
705 
706             /* If LUID mapping enabled */
707             if (BaseStaticServerData->LUIDDeviceMapsEnabled)
708             {
709                 /* Check if that's global link */
710                 Status = IsGlobalSymbolicLink(LinkHandle, &IsGlobal);
711                 if (!NT_SUCCESS(Status))
712                 {
713                     _SEH2_LEAVE;
714                 }
715 
716                 /* If so, change our device name namespace to GLOBAL?? for link creation */
717                 if (IsGlobal)
718                 {
719                     CchLength = _snwprintf(lpBuffer, 0x1000, L"\\GLOBAL??\\%wZ", &DefineDosDeviceRequest->DeviceName);
720                     CchLengthLeft = 0x1000 - 1 - CchLength; /* UNICODE_NULL */
721                     CurrentBuffer = lpBuffer + CchLength + 1; /* UNICODE_NULL */
722 
723                     DeviceName.Length = CchLength * sizeof(WCHAR);
724                     DeviceName.MaximumLength = CchLength * sizeof(WCHAR) + sizeof(UNICODE_NULL);
725                 }
726             }
727         }
728 
729         /* If caller provided a target */
730         if (DefineDosDeviceRequest->TargetPath.Length != 0)
731         {
732             /* Make sure it's null terminated */
733             DefineDosDeviceRequest->TargetPath.Buffer[DefineDosDeviceRequest->TargetPath.Length / sizeof(WCHAR)] = UNICODE_NULL;
734 
735             /* Compute its size */
736             TargetLength = wcslen(DefineDosDeviceRequest->TargetPath.Buffer);
737 
738             /* And make sure it fits our buffer */
739             if (TargetLength + 1 >= CchLengthLeft)
740             {
741                 Status = STATUS_INVALID_PARAMETER;
742                 _SEH2_LEAVE;
743             }
744 
745             /* Copy it to our internal buffer */
746             RtlMoveMemory(CurrentBuffer, DefineDosDeviceRequest->TargetPath.Buffer, TargetLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
747             TargetBuffer = CurrentBuffer;
748 
749             /* Update our buffer status */
750             CchLengthLeft -= (TargetLength + 1);
751             CurrentBuffer += (TargetLength + 1);
752         }
753         /* Otherwise, zero everything */
754         else
755         {
756             TargetBuffer = NULL;
757             TargetLength = 0;
758         }
759 
760         /* If we opened the device, then, handle its current target */
761         if (HandleTarget)
762         {
763             /* Query it with our internal buffer */
764             LinkTarget.Length = 0;
765             LinkTarget.MaximumLength = CchLengthLeft * sizeof(WCHAR);
766             LinkTarget.Buffer = CurrentBuffer;
767 
768             Status = NtQuerySymbolicLinkObject(LinkHandle,
769                                                &LinkTarget,
770                                                &Length);
771             /* If we overflow, give up */
772             if (Length == LinkTarget.MaximumLength)
773             {
774                 Status = STATUS_BUFFER_OVERFLOW;
775             }
776             /* In case of a failure, bye bye */
777             if (!NT_SUCCESS(Status))
778             {
779                 _SEH2_LEAVE;
780             }
781 
782             /*
783              * Properly null it for MULTI_SZ if needed
784              * Always update max length with
785              * the need size
786              * This is needed to hand relatively "small"
787              * strings to Ob and avoid killing ourselves
788              * on the next query
789              */
790             CchLength = Length / sizeof(WCHAR);
791             if (CchLength < 2 ||
792                 CurrentBuffer[CchLength - 2] != UNICODE_NULL ||
793                 CurrentBuffer[CchLength - 1] != UNICODE_NULL)
794             {
795                 CurrentBuffer[CchLength] = UNICODE_NULL;
796                 LinkTarget.MaximumLength = Length + sizeof(UNICODE_NULL);
797             }
798             else
799             {
800                 LinkTarget.MaximumLength = Length;
801             }
802         }
803         /* There's no target, and we're asked to remove, so null target */
804         else if (RemoveDefinition)
805         {
806             RtlInitUnicodeString(&LinkTarget, NULL);
807         }
808         /* There's a target provided - new device, update buffer */
809         else
810         {
811             RtlInitUnicodeString(&LinkTarget, CurrentBuffer - TargetLength - 1);
812         }
813 
814         /*
815          * We no longer need old symlink, just drop it, we'll recreate it now
816          * with updated target.
817          * The benefit of it is that if caller asked us to drop last target, then
818          * the device is removed and not dangling
819          */
820         if (LinkHandle != 0)
821         {
822             Status = NtMakeTemporaryObject(LinkHandle);
823             NtClose(LinkHandle);
824             LinkHandle = 0;
825         }
826 
827         /* At this point, we must have no failure */
828         if (!NT_SUCCESS(Status))
829         {
830             _SEH2_LEAVE;
831         }
832 
833         /*
834          * If we have to remove definition, let's start to browse our
835          * target to actually drop it.
836          */
837         if (RemoveDefinition)
838         {
839             /* We'll browse our multi sz string */
840             RemoveFound = FALSE;
841             CurrentPtr = LinkTarget.Buffer;
842             InterPtr = LinkTarget.Buffer;
843             while (*CurrentPtr != UNICODE_NULL)
844             {
845                 CchLength = 0;
846                 OrigPtr = CurrentPtr;
847                 /* First, find next string */
848                 while (TRUE)
849                 {
850                     CurrentChar = *CurrentPtr;
851                     ++CurrentPtr;
852 
853                     if (CurrentChar == UNICODE_NULL)
854                     {
855                         break;
856                     }
857 
858                     ++CchLength;
859                 }
860 
861                 /* This check is a bit tricky, but dead useful:
862                  * If on the previous loop, we found the caller provided target
863                  * in our list, then, we'll move current entry over the found one
864                  * So that, it gets deleted.
865                  * Also, if we don't find caller entry in our entries, then move
866                  * current entry in the string if a previous one got deleted
867                  */
868                 if (RemoveFound ||
869                     ((!(DefineDosDeviceRequest->Flags & DDD_EXACT_MATCH_ON_REMOVE) ||
870                       TargetLength != CchLength || _wcsicmp(OrigPtr, TargetBuffer) != 0) &&
871                      ((DefineDosDeviceRequest->Flags & DDD_EXACT_MATCH_ON_REMOVE) ||
872                       (TargetLength != 0 && _wcsnicmp(OrigPtr, TargetBuffer, TargetLength) != 0))))
873                 {
874                     if (InterPtr != OrigPtr)
875                     {
876                         RtlMoveMemory(InterPtr, OrigPtr, sizeof(WCHAR) * CchLength + sizeof(UNICODE_NULL));
877                     }
878 
879                     InterPtr += (CchLength + 1);
880                 }
881                 else
882                 {
883                     /* Match case! Remember for next loop turn and to delete it */
884                     RemoveFound = TRUE;
885                 }
886             }
887 
888             /*
889              * Drop last entry, as required (pop)
890              * If there was a match previously, everything
891              * is already moved, so we're just nulling
892              * the end of the string
893              * If there was no match, this is the pop
894              */
895             *InterPtr = UNICODE_NULL;
896             ++InterPtr;
897 
898             /* Compute new target length */
899             TargetLength = wcslen(LinkTarget.Buffer) * sizeof(WCHAR);
900             /*
901              * If it's empty, quit
902              * Beware, here, we quit with STATUS_SUCCESS, and that's expected!
903              * In case we dropped last target entry, then, it's empty
904              * and there's no need to recreate the device we deleted previously
905              */
906             if (TargetLength == 0)
907             {
908                 _SEH2_LEAVE;
909             }
910 
911             /* Update our target string */
912             LinkTarget.Length = TargetLength;
913             LinkTarget.MaximumLength = (ULONG_PTR)InterPtr - (ULONG_PTR)LinkTarget.Buffer;
914         }
915         /* If that's not a removal, just update the target to include new target */
916         else if (HandleTarget)
917         {
918             LinkTarget.Buffer = LinkTarget.Buffer - TargetLength - 1;
919             LinkTarget.Length = TargetLength * sizeof(WCHAR);
920             LinkTarget.MaximumLength += (TargetLength * sizeof(WCHAR) + sizeof(UNICODE_NULL));
921             TargetLength *= sizeof(WCHAR);
922         }
923         /* No changes */
924         else
925         {
926             TargetLength = LinkTarget.Length;
927         }
928 
929         /* Make sure we don't create empty symlink */
930         if (TargetLength == 0)
931         {
932             _SEH2_LEAVE;
933         }
934 
935         /* Initialize our SIDs for symlink ACLs */
936         Status = RtlAllocateAndInitializeSid(&WorldAuthority,
937                                              1,
938                                              SECURITY_NULL_RID,
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                                              &WorldSid);
947         if (!NT_SUCCESS(Status))
948         {
949             _SEH2_LEAVE;
950         }
951 
952         Status = RtlAllocateAndInitializeSid(&SystemAuthority,
953                                              1,
954                                              SECURITY_RESTRICTED_CODE_RID,
955                                              SECURITY_NULL_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                                              &SystemSid);
963         if (!NT_SUCCESS(Status))
964         {
965             RtlFreeSid(WorldSid);
966             _SEH2_LEAVE;
967         }
968 
969         /* Initialize our SD (on stack) */
970         RtlCreateSecurityDescriptor(&SecurityDescriptor,
971                                     SECURITY_DESCRIPTOR_REVISION);
972 
973         /* And our ACL (still on stack) */
974         RtlCreateAcl(&Dacl.Dacl, sizeof(Dacl), ACL_REVISION);
975 
976         /*
977          * For access mask, if we have no session ID, or if
978          * protection mode is disabled, make them wide open
979          */
980         if (SessionId == 0 ||
981             (ProtectionMode & 3) == 0)
982         {
983             AccessMask = DELETE | SYMBOLIC_LINK_QUERY;
984         }
985         else
986         {
987             AccessMask = SYMBOLIC_LINK_QUERY;
988         }
989 
990         /* Setup the ACL */
991         RtlAddAccessAllowedAce(&Dacl.Dacl, ACL_REVISION2, AccessMask, WorldSid);
992         RtlAddAccessAllowedAce(&Dacl.Dacl, ACL_REVISION2, AccessMask, SystemSid);
993 
994         /* Drop SIDs */
995         RtlFreeSid(WorldSid);
996         RtlFreeSid(SystemSid);
997 
998         /* Link DACL to the SD */
999         RtlSetDaclSecurityDescriptor(&SecurityDescriptor, TRUE, &Dacl.Dacl, TRUE);
1000 
1001         /* And set it in the OA used for creation */
1002         ObjectAttributes.SecurityDescriptor = &SecurityDescriptor;
1003 
1004         /*
1005          * If LUID and not global, we need to impersonate the caller
1006          * to make it local.
1007          */
1008         if (BaseStaticServerData->LUIDDeviceMapsEnabled)
1009         {
1010             if (!IsGlobal)
1011             {
1012                 if (!CsrImpersonateClient(NULL))
1013                 {
1014                     Status = STATUS_BAD_IMPERSONATION_LEVEL;
1015                     _SEH2_LEAVE;
1016                 }
1017             }
1018         }
1019         /* The object will be permanent */
1020         else
1021         {
1022             ObjectAttributes.Attributes |= OBJ_PERMANENT;
1023         }
1024 
1025         /* (Re)Create the symbolic link/device */
1026         Status = NtCreateSymbolicLinkObject(&LinkHandle,
1027                                             SYMBOLIC_LINK_ALL_ACCESS,
1028                                             &ObjectAttributes,
1029                                             &LinkTarget);
1030 
1031         /* Revert to self if required */
1032         if (BaseStaticServerData->LUIDDeviceMapsEnabled && !IsGlobal)
1033         {
1034             CsrRevertToSelf();
1035         }
1036 
1037         /* In case of a success, make object permanent for LUID links */
1038         if (NT_SUCCESS(Status))
1039         {
1040             if (BaseStaticServerData->LUIDDeviceMapsEnabled)
1041             {
1042                 Status = NtMakePermanentObject(LinkHandle);
1043             }
1044 
1045             /* Close the link */
1046             NtClose(LinkHandle);
1047 
1048             /*
1049              * Specific failure case here:
1050              * We were asked to remove something
1051              * but we didn't find the something
1052              * (we recreated the symlink hence the fail here!)
1053              * so fail with appropriate status
1054              */
1055             if (RemoveDefinition && !RemoveFound)
1056             {
1057                 Status = STATUS_OBJECT_NAME_NOT_FOUND;
1058             }
1059         }
1060 
1061         /* We closed link, don't double close */
1062         LinkHandle = 0;
1063     }
1064     _SEH2_FINALLY
1065     {
1066         /* If we need to close the link, do it now */
1067         if (LinkHandle != 0)
1068         {
1069              NtClose(LinkHandle);
1070         }
1071 
1072         /* Free our internal buffer */
1073         RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
1074 
1075         /* Broadcast drive letter creation */
1076         if (DriveLetter && Status == STATUS_SUCCESS && Broadcast)
1077         {
1078             LUID SystemLuid = SYSTEM_LUID;
1079 
1080             /* If that's a global drive, broadcast as system */
1081             if (IsGlobal)
1082             {
1083                 RtlCopyLuid(&CallerLuid, &SystemLuid);
1084             }
1085 
1086             /* Broadcast the event */
1087             AddBSMRequest(AbsLetter, RemoveDefinition, &CallerLuid);
1088 
1089             /*
1090              * If we removed drive, and the drive was shadowing a global one
1091              * broadcast the arrival of the global drive (as system - global)
1092              */
1093             if (RemoveDefinition && !RtlEqualLuid(&CallerLuid, &SystemLuid))
1094             {
1095                 if (CheckForGlobalDriveLetter(AbsLetter))
1096                 {
1097                     AddBSMRequest(AbsLetter, FALSE, &CallerLuid);
1098                 }
1099             }
1100         }
1101 
1102         /* Done! */
1103         RtlLeaveCriticalSection(&BaseDefineDosDeviceCritSec);
1104     }
1105     _SEH2_END;
1106 
1107     return Status;
1108 }
1109 
1110 /* EOF */
1111