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)
9 /* INCLUDES *******************************************************************/
16 /* GLOBALS ********************************************************************/
18 static RTL_CRITICAL_SECTION BaseDefineDosDeviceCritSec
;
20 /* PRIVATE FUNCTIONS **********************************************************/
22 VOID
BaseInitDefineDosDevice(VOID
)
24 RtlInitializeCriticalSection(&BaseDefineDosDeviceCritSec
);
27 VOID
BaseCleanupDefineDosDevice(VOID
)
29 RtlDeleteCriticalSection(&BaseDefineDosDeviceCritSec
);
33 GetCallerLuid(PLUID CallerLuid
)
38 TOKEN_STATISTICS TokenInformation
;
40 /* We need an output buffer */
41 if (CallerLuid
== NULL
)
43 return STATUS_INVALID_PARAMETER
;
46 /* Open thread token */
49 Status
= NtOpenThreadToken(NtCurrentThread(),
50 READ_CONTROL
| TOKEN_QUERY
,
52 /* If we fail, open process token */
53 if (Status
== STATUS_NO_TOKEN
)
55 Status
= NtOpenProcessToken(NtCurrentProcess(),
56 READ_CONTROL
| TOKEN_QUERY
,
60 /* In case of a success get caller LUID and copy it back */
61 if (NT_SUCCESS(Status
))
63 Status
= NtQueryInformationToken(TokenHandle
,
66 sizeof(TokenInformation
),
68 if (NT_SUCCESS(Status
))
70 RtlCopyLuid(CallerLuid
, &TokenInformation
.AuthenticationId
);
74 /* Close token handle */
84 IsGlobalSymbolicLink(HANDLE LinkHandle
,
89 UNICODE_STRING GlobalString
;
90 OBJECT_NAME_INFORMATION NameInfo
, *PNameInfo
;
92 /* We need both parameters */
93 if (LinkHandle
== 0 || IsGlobal
== NULL
)
95 return STATUS_INVALID_PARAMETER
;
101 /* Query handle information */
102 Status
= NtQueryObject(LinkHandle
,
103 ObjectNameInformation
,
107 /* Only failure we tolerate is length mismatch */
108 if (NT_SUCCESS(Status
) || Status
== STATUS_INFO_LENGTH_MISMATCH
)
110 /* Allocate big enough buffer */
111 PNameInfo
= RtlAllocateHeap(BaseSrvHeap
, 0, ReturnLength
);
112 if (PNameInfo
== NULL
)
114 Status
= STATUS_NO_MEMORY
;
118 /* Query again handle information */
119 Status
= NtQueryObject(LinkHandle
,
120 ObjectNameInformation
,
126 * If it succeed, check we have Global??
127 * If so, return success
129 if (NT_SUCCESS(Status
))
131 RtlInitUnicodeString(&GlobalString
, L
"\\GLOBAL??");
132 *IsGlobal
= RtlPrefixUnicodeString(&GlobalString
, &PNameInfo
->Name
, FALSE
);
133 Status
= STATUS_SUCCESS
;
139 if (PNameInfo
!= NULL
)
141 RtlFreeHeap(RtlGetProcessHeap(), 0, PNameInfo
);
149 /* PUBLIC SERVER APIS *********************************************************/
151 CSR_API(BaseSrvDefineDosDevice
)
154 PBASE_DEFINE_DOS_DEVICE DefineDosDeviceRequest
= &((PBASE_API_MESSAGE
)ApiMessage
)->Data
.DefineDosDeviceRequest
;
155 OBJECT_ATTRIBUTES ObjectAttributes
;
157 UNICODE_STRING DeviceName
= {0};
158 UNICODE_STRING LinkTarget
= {0};
160 SID_IDENTIFIER_AUTHORITY WorldAuthority
= {SECURITY_WORLD_SID_AUTHORITY
};
161 SID_IDENTIFIER_AUTHORITY SystemAuthority
= {SECURITY_NT_AUTHORITY
};
167 BOOLEAN DriveLetter
= FALSE
;
168 BOOLEAN RemoveDefinition
;
169 BOOLEAN HandleTarget
;
170 BOOLEAN HandleSMB
= FALSE
;
171 BOOLEAN IsGlobal
= FALSE
;
177 /* We store them on the stack, they are known in advance */
179 SECURITY_DESCRIPTOR SecurityDescriptor
;
181 } SecurityDescriptor
;
186 ACCESS_MASK AccessMask
;
195 /* FIXME: Check why it fails.... */
196 if (!CsrValidateMessageBuffer(ApiMessage
,
197 (PVOID
*)&DefineDosDeviceRequest
->DeviceName
,
198 DefineDosDeviceRequest
->DeviceName
.Length
,
200 (DefineDosDeviceRequest
->DeviceName
.Length
& 1) != 0 ||
201 !CsrValidateMessageBuffer(ApiMessage
,
202 (PVOID
*)&DefineDosDeviceRequest
->TargetPath
,
203 (DefineDosDeviceRequest
->TargetPath
.Length
!= 0 ? sizeof(UNICODE_NULL
) : 0) + DefineDosDeviceRequest
->TargetPath
.Length
,
205 (DefineDosDeviceRequest
->TargetPath
.Length
& 1) != 0)
207 return STATUS_INVALID_PARAMETER
;
211 DPRINT1("BaseSrvDefineDosDevice entered, Flags:%d, DeviceName:%wZ (%d), TargetPath:%wZ (%d)\n",
212 DefineDosDeviceRequest
->Flags
,
213 &DefineDosDeviceRequest
->DeviceName
,
214 DefineDosDeviceRequest
->DeviceName
.Length
,
215 &DefineDosDeviceRequest
->TargetPath
,
216 DefineDosDeviceRequest
->TargetPath
.Length
);
219 * Allocate a buffer big enough to contain:
223 lpBuffer
= RtlAllocateHeap(BaseSrvHeap
, 0, 0x2000);
224 if (lpBuffer
== NULL
)
226 return STATUS_NO_MEMORY
;
229 /* Enter our critical section */
230 Status
= RtlEnterCriticalSection(&BaseDefineDosDeviceCritSec
);
231 if (!NT_SUCCESS(Status
))
233 DPRINT1("RtlEnterCriticalSection() failed (Status %lx)\n",
235 RtlFreeHeap(BaseSrvHeap
, 0, lpBuffer
);
240 /* Does the caller wants to remove definition? */
241 RemoveDefinition
= !!(DefineDosDeviceRequest
->Flags
& DDD_REMOVE_DEFINITION
);
244 /* First of all, check if that's a drive letter device amongst LUID mappings */
245 if (BaseStaticServerData
->LUIDDeviceMapsEnabled
&& !(DefineDosDeviceRequest
->Flags
& DDD_NO_BROADCAST_SYSTEM
))
247 if (DefineDosDeviceRequest
->DeviceName
.Buffer
!= NULL
&&
248 DefineDosDeviceRequest
->DeviceName
.Length
== 2 * sizeof(WCHAR
) &&
249 DefineDosDeviceRequest
->DeviceName
.Buffer
[1] == L
':')
251 Letter
= DefineDosDeviceRequest
->DeviceName
.Buffer
[0];
253 /* Handle both lower cases and upper cases */
254 AbsLetter
= Letter
- L
'a';
255 if (AbsLetter
< 26 && AbsLetter
>= 0)
257 Letter
= RtlUpcaseUnicodeChar(Letter
);
260 AbsLetter
= Letter
- L
'A';
263 /* That's a letter! */
269 /* We can only broadcast drive letters in case of LUID mappings */
270 if (DefineDosDeviceRequest
->Flags
& DDD_LUID_BROADCAST_DRIVE
&&
273 Status
= STATUS_INVALID_PARAMETER
;
277 /* First usage of our buffer: create device name */
278 CchLength
= _snwprintf(lpBuffer
, 0x1000, L
"\\??\\%wZ", &DefineDosDeviceRequest
->DeviceName
);
279 CchLengthLeft
= 0x1000 - 1 - CchLength
; /* UNICODE_NULL */
280 CurrentBuffer
= lpBuffer
+ CchLength
+ 1; /* UNICODE_NULL */
281 RtlInitUnicodeString(&DeviceName
, lpBuffer
);
283 /* And prepare to open it */
284 InitializeObjectAttributes(&ObjectAttributes
,
286 OBJ_CASE_INSENSITIVE
,
290 /* Assume it's OK and has a target to deal with */
293 /* Move to the client context if the mapping was local */
294 if (!CsrImpersonateClient(NULL
))
296 Status
= STATUS_BAD_IMPERSONATION_LEVEL
;
300 /* While impersonating the caller, also get its LUID */
303 Status
= GetCallerLuid(&CallerLuid
);
304 if (NT_SUCCESS(Status
))
310 /* Now, open the device */
311 Status
= NtOpenSymbolicLinkObject(&LinkHandle
,
312 DELETE
| SYMBOLIC_LINK_QUERY
,
315 /* And get back to our context */
318 /* In case of LUID broadcast, do nothing but return to trigger broadcast */
319 if (DefineDosDeviceRequest
->Flags
& DDD_LUID_BROADCAST_DRIVE
)
321 /* Zero handle in case of a failure */
322 if (!NT_SUCCESS(Status
))
327 /* If removal was asked, and no object found: the remval was successful */
328 if (RemoveDefinition
&& Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
330 Status
= STATUS_SUCCESS
;
333 /* We're done here, nothing more to do */
337 /* If device was not found */
338 if (Status
== STATUS_OBJECT_NAME_NOT_FOUND
)
343 /* If we were asked to remove... */
344 if (RemoveDefinition
)
347 * If caller asked to pop first entry, nothing specific,
348 * then, we can consider this as a success
350 if (DefineDosDeviceRequest
->TargetPath
.Length
== 0)
352 Status
= STATUS_SUCCESS
;
355 /* We're done, nothing to change */
359 /* There's no target to handle */
360 HandleTarget
= FALSE
;
363 * We'll consider, that's a success
364 * Failing to open the device doesn't prevent
365 * from creating it later on to create
368 Status
= STATUS_SUCCESS
;
372 /* Unexpected failure, forward to caller */
373 if (!NT_SUCCESS(Status
))
378 /* If LUID mapping enabled */
379 if (BaseStaticServerData
->LUIDDeviceMapsEnabled
)
381 /* Check if that's global link */
382 Status
= IsGlobalSymbolicLink(LinkHandle
, &IsGlobal
);
383 if (!NT_SUCCESS(Status
))
388 /* If so, change our device name namespace to GLOBAL?? for link creation */
391 CchLength
= _snwprintf(lpBuffer
, 0x1000, L
"\\GLOBAL??\\%wZ", &DefineDosDeviceRequest
->DeviceName
);
392 CchLengthLeft
= 0x1000 - 1 - CchLength
; /* UNICODE_NULL */
393 CurrentBuffer
= lpBuffer
+ CchLength
+ 1; /* UNICODE_NULL */
395 DeviceName
.Length
= CchLength
* sizeof(WCHAR
);
396 DeviceName
.MaximumLength
= CchLength
* sizeof(WCHAR
) + sizeof(UNICODE_NULL
);
401 /* If caller provided a target */
402 if (DefineDosDeviceRequest
->TargetPath
.Length
!= 0)
404 /* Make sure it's null terminated */
405 DefineDosDeviceRequest
->TargetPath
.Buffer
[DefineDosDeviceRequest
->TargetPath
.Length
/ sizeof(WCHAR
)] = UNICODE_NULL
;
407 /* Compute its size */
408 TargetLength
= wcslen(DefineDosDeviceRequest
->TargetPath
.Buffer
);
410 /* And make sure it fits our buffer */
411 if (TargetLength
+ 1 >= CchLengthLeft
)
413 Status
= STATUS_INVALID_PARAMETER
;
417 /* Copy it to our internal buffer */
418 RtlMoveMemory(CurrentBuffer
, DefineDosDeviceRequest
->TargetPath
.Buffer
, TargetLength
* sizeof(WCHAR
) + sizeof(UNICODE_NULL
));
419 TargetBuffer
= CurrentBuffer
;
421 /* Update our buffer status */
422 CchLengthLeft
-= (TargetLength
+ 1);
423 CurrentBuffer
+= (TargetLength
+ 1);
425 /* Otherwise, zero everything */
432 /* If we opened the device, then, handle its current target */
435 /* Query it with our internal buffer */
436 LinkTarget
.Length
= 0;
437 LinkTarget
.MaximumLength
= CchLengthLeft
* sizeof(WCHAR
);
438 LinkTarget
.Buffer
= CurrentBuffer
;
440 Status
= NtQuerySymbolicLinkObject(LinkHandle
,
443 /* If we overflow, give up */
444 if (Length
== LinkTarget
.MaximumLength
)
446 Status
= STATUS_BUFFER_OVERFLOW
;
448 /* In case of a failure, bye bye */
449 if (!NT_SUCCESS(Status
))
455 * Properly null it for MULTI_SZ if needed
456 * Always update max length with
458 * This is needed to hand relatively "small"
459 * strings to Ob and avoid killing ourselves
462 CchLength
= Length
/ sizeof(WCHAR
);
464 CurrentBuffer
[CchLength
- 2] != UNICODE_NULL
||
465 CurrentBuffer
[CchLength
- 1] != UNICODE_NULL
)
467 CurrentBuffer
[CchLength
] = UNICODE_NULL
;
468 LinkTarget
.MaximumLength
= Length
+ sizeof(UNICODE_NULL
);
472 LinkTarget
.MaximumLength
= Length
;
475 /* There's no target, and we're asked to remove, so null target */
476 else if (RemoveDefinition
)
478 RtlInitUnicodeString(&LinkTarget
, NULL
);
480 /* There's a target provided - new device, update buffer */
483 RtlInitUnicodeString(&LinkTarget
, &CurrentBuffer
[-TargetLength
- 1]);
487 * We no longer need old symlink, just drop it, we'll recreate it now
488 * with updated target.
489 * The benefit of it is that if caller asked us to drop last target, then
490 * the device is removed and not dangling
494 Status
= NtMakeTemporaryObject(LinkHandle
);
499 /* At this point, we must have no failure */
500 if (!NT_SUCCESS(Status
))
506 * If we have to remove definition, let's start to browse our
507 * target to actually drop it.
509 if (RemoveDefinition
)
511 /* We'll browse our multi sz string */
513 CurrentPtr
= LinkTarget
.Buffer
;
514 InterPtr
= LinkTarget
.Buffer
;
515 while (*CurrentPtr
!= UNICODE_NULL
)
518 OrigPtr
= CurrentPtr
;
519 /* First, find next string */
522 CurrentChar
= *CurrentPtr
;
525 if (CurrentChar
== UNICODE_NULL
)
533 /* This check is a bit tricky, but dead useful:
534 * If on the previous loop, we found the caller provided target
535 * in our list, then, we'll move current entry over the found one
536 * So that, it gets deleted.
537 * Also, if we don't find caller entry in our entries, then move
538 * current entry in the string if a previous one got deleted
541 ((!(DefineDosDeviceRequest
->Flags
& DDD_EXACT_MATCH_ON_REMOVE
) ||
542 TargetLength
!= CchLength
|| _wcsicmp(OrigPtr
, TargetBuffer
) != 0) &&
543 ((DefineDosDeviceRequest
->Flags
& DDD_EXACT_MATCH_ON_REMOVE
) ||
544 (TargetLength
!= 0 && _wcsnicmp(OrigPtr
, TargetBuffer
, TargetLength
) != 0))))
546 if (InterPtr
!= OrigPtr
)
548 RtlMoveMemory(InterPtr
, OrigPtr
, sizeof(WCHAR
) * CchLength
+ sizeof(UNICODE_NULL
));
551 InterPtr
+= (CchLength
+ 1);
555 /* Match case! Remember for next loop turn and to delete it */
561 * Drop last entry, as required (pop)
562 * If there was a match previously, everything
563 * is already moved, so we're just nulling
564 * the end of the string
565 * If there was no match, this is the pop
567 *InterPtr
= UNICODE_NULL
;
570 /* Compute new target length */
571 TargetLength
= wcslen(LinkTarget
.Buffer
) * sizeof(WCHAR
);
573 * If it's empty, quit
574 * Beware, here, we quit with STATUS_SUCCESS, and that's expected!
575 * In case we dropped last target entry, then, it's empty
576 * and there's no need to recreate the device we deleted previously
578 if (TargetLength
== 0)
583 /* Update our target string */
584 LinkTarget
.Length
= TargetLength
;
585 LinkTarget
.MaximumLength
= (ULONG_PTR
)InterPtr
- (ULONG_PTR
)LinkTarget
.Buffer
;
587 /* If that's not a removal, just update the target to include new target */
588 else if (HandleTarget
)
590 LinkTarget
.Buffer
= LinkTarget
.Buffer
- TargetLength
- 1;
591 LinkTarget
.Length
= TargetLength
* sizeof(WCHAR
);
592 LinkTarget
.MaximumLength
+= (TargetLength
* sizeof(WCHAR
) + sizeof(UNICODE_NULL
));
593 TargetLength
*= sizeof(WCHAR
);
598 TargetLength
= LinkTarget
.Length
;
601 /* Make sure we don't create empty symlink */
602 if (TargetLength
== 0)
607 /* Initialize our SIDs for symlink ACLs */
608 Status
= RtlAllocateAndInitializeSid(&WorldAuthority
,
619 if (!NT_SUCCESS(Status
))
624 Status
= RtlAllocateAndInitializeSid(&SystemAuthority
,
626 SECURITY_RESTRICTED_CODE_RID
,
635 if (!NT_SUCCESS(Status
))
637 RtlFreeSid(WorldSid
);
641 /* Initialize our SD (on stack) */
642 RtlCreateSecurityDescriptor(&SecurityDescriptor
,
643 SECURITY_DESCRIPTOR_REVISION
);
645 /* And our ACL (still on stack) */
646 RtlCreateAcl(&Dacl
.Dacl
, sizeof(Dacl
), ACL_REVISION
);
649 * For access mask, if we have no session ID, or if
650 * protection mode is disabled, make them wide open
652 if (SessionId
== 0 ||
653 (ProtectionMode
& 3) == 0)
655 AccessMask
= DELETE
| SYMBOLIC_LINK_QUERY
;
659 AccessMask
= SYMBOLIC_LINK_QUERY
;
663 RtlAddAccessAllowedAce(&Dacl
.Dacl
, ACL_REVISION2
, AccessMask
, WorldSid
);
664 RtlAddAccessAllowedAce(&Dacl
.Dacl
, ACL_REVISION2
, AccessMask
, SystemSid
);
667 RtlFreeSid(WorldSid
);
668 RtlFreeSid(SystemSid
);
670 /* Link DACL to the SD */
671 RtlSetDaclSecurityDescriptor(&SecurityDescriptor
, TRUE
, &Dacl
.Dacl
, TRUE
);
673 /* And set it in the OA used for creation */
674 ObjectAttributes
.SecurityDescriptor
= &SecurityDescriptor
;
677 * If LUID and not global, we need to impersonate the caller
680 if (BaseStaticServerData
->LUIDDeviceMapsEnabled
)
684 if (!CsrImpersonateClient(NULL
))
686 Status
= STATUS_BAD_IMPERSONATION_LEVEL
;
691 /* The object will be permanent */
694 ObjectAttributes
.Attributes
|= OBJ_PERMANENT
;
697 /* (Re)Create the symbolic link/device */
698 Status
= NtCreateSymbolicLinkObject(&LinkHandle
,
699 SYMBOLIC_LINK_ALL_ACCESS
,
703 /* Revert to self if required */
704 if (BaseStaticServerData
->LUIDDeviceMapsEnabled
&& !IsGlobal
)
709 /* In case of a success, make object permanent for LUID links */
710 if (NT_SUCCESS(Status
))
712 if (BaseStaticServerData
->LUIDDeviceMapsEnabled
)
714 Status
= NtMakePermanentObject(LinkHandle
);
721 * Specific failure case here:
722 * We were asked to remove something
723 * but we didn't find the something
724 * (we recreated the symlink hence the fail here!)
725 * so fail with appropriate status
727 if (RemoveDefinition
&& !RemoveFound
)
729 Status
= STATUS_OBJECT_NAME_NOT_FOUND
;
733 /* We closed link, don't double close */
738 /* If we need to close the link, do it now */
744 /* Free our internal buffer */
745 RtlFreeHeap(BaseSrvHeap
, 0, lpBuffer
);
748 if (DriveLetter
&& Status
== STATUS_SUCCESS
&& HandleSMB
)
754 RtlLeaveCriticalSection(&BaseDefineDosDeviceCritSec
);