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 (!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