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