1 /* 2 * COPYRIGHT: See COPYING in the top level directory 3 * PROJECT: ReactOS Win32k subsystem 4 * PURPOSE: Window stations 5 * FILE: win32ss/user/ntuser/winsta.c 6 * PROGRAMER: Casper S. Hornstrup (chorns@users.sourceforge.net) 7 * TODO: The process window station is created on 8 * the first USER32/GDI32 call not related 9 * to window station/desktop handling 10 */ 11 12 #include <win32k.h> 13 DBG_DEFAULT_CHANNEL(UserWinsta); 14 15 /* GLOBALS *******************************************************************/ 16 17 /* 18 * The currently active window station. This is the 19 * only one interactive window station on the system. 20 */ 21 PWINSTATION_OBJECT InputWindowStation = NULL; 22 23 /* Winlogon SAS window */ 24 HWND hwndSAS = NULL; 25 26 /* Full path to WindowStations directory */ 27 UNICODE_STRING gustrWindowStationsDir; 28 29 /* INITIALIZATION FUNCTIONS ****************************************************/ 30 31 CODE_SEG("INIT") 32 NTSTATUS 33 NTAPI 34 InitWindowStationImpl(VOID) 35 { 36 GENERIC_MAPPING IntWindowStationMapping = { WINSTA_READ, 37 WINSTA_WRITE, 38 WINSTA_EXECUTE, 39 WINSTA_ACCESS_ALL}; 40 41 /* Set Winsta Object Attributes */ 42 ExWindowStationObjectType->TypeInfo.DefaultNonPagedPoolCharge = sizeof(WINSTATION_OBJECT); 43 ExWindowStationObjectType->TypeInfo.GenericMapping = IntWindowStationMapping; 44 ExWindowStationObjectType->TypeInfo.ValidAccessMask = WINSTA_ACCESS_ALL; 45 46 return STATUS_SUCCESS; 47 } 48 49 NTSTATUS 50 NTAPI 51 UserCreateWinstaDirectory(VOID) 52 { 53 NTSTATUS Status; 54 PPEB Peb; 55 OBJECT_ATTRIBUTES ObjectAttributes; 56 HANDLE hWinstaDir; 57 WCHAR wstrWindowStationsDir[MAX_PATH]; 58 59 /* Create the WindowStations directory and cache its path for later use */ 60 Peb = NtCurrentPeb(); 61 if(Peb->SessionId == 0) 62 { 63 if (!RtlCreateUnicodeString(&gustrWindowStationsDir, WINSTA_OBJ_DIR)) 64 { 65 return STATUS_INSUFFICIENT_RESOURCES; 66 } 67 } 68 else 69 { 70 Status = RtlStringCbPrintfW(wstrWindowStationsDir, 71 sizeof(wstrWindowStationsDir), 72 L"%ws\\%lu%ws", 73 SESSION_DIR, 74 Peb->SessionId, 75 WINSTA_OBJ_DIR); 76 if (!NT_SUCCESS(Status)) 77 return Status; 78 79 if (!RtlCreateUnicodeString(&gustrWindowStationsDir, wstrWindowStationsDir)) 80 { 81 return STATUS_INSUFFICIENT_RESOURCES; 82 } 83 } 84 85 InitializeObjectAttributes(&ObjectAttributes, 86 &gustrWindowStationsDir, 87 OBJ_KERNEL_HANDLE, 88 NULL, 89 NULL); 90 Status = ZwCreateDirectoryObject(&hWinstaDir, DIRECTORY_CREATE_OBJECT, &ObjectAttributes); 91 if (!NT_SUCCESS(Status)) 92 { 93 ERR("Could not create %wZ directory (Status 0x%X)\n", &gustrWindowStationsDir, Status); 94 return Status; 95 } 96 97 TRACE("Created directory %wZ for session %lu\n", &gustrWindowStationsDir, Peb->SessionId); 98 99 return Status; 100 } 101 102 /* OBJECT CALLBACKS ***********************************************************/ 103 104 NTSTATUS 105 NTAPI 106 IntWinStaObjectDelete( 107 _In_ PVOID Parameters) 108 { 109 PWIN32_DELETEMETHOD_PARAMETERS DeleteParameters = Parameters; 110 PWINSTATION_OBJECT WinSta = (PWINSTATION_OBJECT)DeleteParameters->Object; 111 112 TRACE("Deleting window station 0x%p\n", WinSta); 113 114 if (WinSta == InputWindowStation) 115 { 116 ERR("WARNING: Deleting the interactive window station '%wZ'!\n", 117 &(OBJECT_HEADER_TO_NAME_INFO(OBJECT_TO_OBJECT_HEADER(InputWindowStation))->Name)); 118 119 /* Only Winlogon can close and delete the interactive window station */ 120 ASSERT(gpidLogon == PsGetCurrentProcessId()); 121 122 InputWindowStation = NULL; 123 } 124 125 WinSta->Flags |= WSS_DYING; 126 127 UserEmptyClipboardData(WinSta); 128 129 RtlDestroyAtomTable(WinSta->AtomTable); 130 131 UserAssignmentUnlock((PVOID*)&WinSta->spklList); 132 133 return STATUS_SUCCESS; 134 } 135 136 NTSTATUS 137 NTAPI 138 IntWinStaObjectParse( 139 _In_ PVOID Parameters) 140 { 141 PWIN32_PARSEMETHOD_PARAMETERS ParseParameters = Parameters; 142 PUNICODE_STRING RemainingName = ParseParameters->RemainingName; 143 144 /* Assume we don't find anything */ 145 *ParseParameters->Object = NULL; 146 147 /* Check for an empty name */ 148 if (!RemainingName->Length) 149 { 150 /* Make sure this is a window station, can't parse a desktop now */ 151 if (ParseParameters->ObjectType != ExWindowStationObjectType) 152 { 153 /* Fail */ 154 return STATUS_OBJECT_TYPE_MISMATCH; 155 } 156 157 /* Reference the window station and return */ 158 ObReferenceObject(ParseParameters->ParseObject); 159 *ParseParameters->Object = ParseParameters->ParseObject; 160 return STATUS_SUCCESS; 161 } 162 163 /* Check for leading slash */ 164 if (RemainingName->Buffer[0] == OBJ_NAME_PATH_SEPARATOR) 165 { 166 /* Skip it */ 167 RemainingName->Buffer++; 168 RemainingName->Length -= sizeof(WCHAR); 169 RemainingName->MaximumLength -= sizeof(WCHAR); 170 } 171 172 /* Check if there is still a slash */ 173 if (wcschr(RemainingName->Buffer, OBJ_NAME_PATH_SEPARATOR)) 174 { 175 /* In this case, fail */ 176 return STATUS_OBJECT_PATH_INVALID; 177 } 178 179 /* 180 * Check if we are parsing a desktop. 181 */ 182 if (ParseParameters->ObjectType == ExDesktopObjectType) 183 { 184 /* Then call the desktop parse routine */ 185 return IntDesktopObjectParse(ParseParameters->ParseObject, 186 ParseParameters->ObjectType, 187 ParseParameters->AccessState, 188 ParseParameters->AccessMode, 189 ParseParameters->Attributes, 190 ParseParameters->CompleteName, 191 RemainingName, 192 ParseParameters->Context, 193 ParseParameters->SecurityQos, 194 ParseParameters->Object); 195 } 196 197 /* Should hopefully never get here */ 198 return STATUS_OBJECT_TYPE_MISMATCH; 199 } 200 201 NTSTATUS 202 NTAPI 203 IntWinStaOkToClose( 204 _In_ PVOID Parameters) 205 { 206 PWIN32_OKAYTOCLOSEMETHOD_PARAMETERS OkToCloseParameters = Parameters; 207 PPROCESSINFO ppi; 208 209 ppi = PsGetCurrentProcessWin32Process(); 210 211 if (ppi && (OkToCloseParameters->Handle == ppi->hwinsta)) 212 { 213 return STATUS_ACCESS_DENIED; 214 } 215 216 return STATUS_SUCCESS; 217 } 218 219 /* PRIVATE FUNCTIONS **********************************************************/ 220 221 /* 222 * IntValidateWindowStationHandle 223 * 224 * Validates the window station handle. 225 * 226 * Remarks 227 * If the function succeeds, the handle remains referenced. If the 228 * fucntion fails, last error is set. 229 */ 230 231 NTSTATUS FASTCALL 232 IntValidateWindowStationHandle( 233 HWINSTA WindowStation, 234 KPROCESSOR_MODE AccessMode, 235 ACCESS_MASK DesiredAccess, 236 PWINSTATION_OBJECT *Object, 237 POBJECT_HANDLE_INFORMATION pObjectHandleInfo) 238 { 239 NTSTATUS Status; 240 241 if (WindowStation == NULL) 242 { 243 ERR("Invalid window station handle\n"); 244 EngSetLastError(ERROR_INVALID_HANDLE); 245 return STATUS_INVALID_HANDLE; 246 } 247 248 Status = ObReferenceObjectByHandle(WindowStation, 249 DesiredAccess, 250 ExWindowStationObjectType, 251 AccessMode, 252 (PVOID*)Object, 253 pObjectHandleInfo); 254 255 if (!NT_SUCCESS(Status)) 256 SetLastNtError(Status); 257 258 return Status; 259 } 260 261 BOOL FASTCALL 262 co_IntInitializeDesktopGraphics(VOID) 263 { 264 TEXTMETRICW tmw; 265 UNICODE_STRING DriverName = RTL_CONSTANT_STRING(L"DISPLAY"); 266 PDESKTOP pdesk; 267 268 if (PDEVOBJ_lChangeDisplaySettings(NULL, NULL, NULL, &gpmdev, TRUE) != DISP_CHANGE_SUCCESSFUL) 269 { 270 ERR("PDEVOBJ_lChangeDisplaySettings() failed.\n"); 271 return FALSE; 272 } 273 274 ScreenDeviceContext = IntGdiCreateDC(&DriverName, NULL, NULL, NULL, FALSE); 275 if (NULL == ScreenDeviceContext) 276 { 277 IntDestroyPrimarySurface(); 278 return FALSE; 279 } 280 GreSetDCOwner(ScreenDeviceContext, GDI_OBJ_HMGR_PUBLIC); 281 282 if (!IntCreatePrimarySurface()) 283 { 284 return FALSE; 285 } 286 287 hSystemBM = NtGdiCreateCompatibleDC(ScreenDeviceContext); 288 289 NtGdiSelectFont(hSystemBM, NtGdiGetStockObject(SYSTEM_FONT)); 290 GreSetDCOwner(hSystemBM, GDI_OBJ_HMGR_PUBLIC); 291 292 /* Update the system metrics */ 293 InitMetrics(); 294 295 /* Set new size of the monitor */ 296 UserUpdateMonitorSize((HDEV)gpmdev->ppdevGlobal); 297 298 /* Update the SERVERINFO */ 299 gpsi->aiSysMet[SM_CXSCREEN] = gpmdev->ppdevGlobal->gdiinfo.ulHorzRes; 300 gpsi->aiSysMet[SM_CYSCREEN] = gpmdev->ppdevGlobal->gdiinfo.ulVertRes; 301 gpsi->Planes = NtGdiGetDeviceCaps(ScreenDeviceContext, PLANES); 302 gpsi->BitsPixel = NtGdiGetDeviceCaps(ScreenDeviceContext, BITSPIXEL); 303 gpsi->BitCount = gpsi->Planes * gpsi->BitsPixel; 304 gpsi->dmLogPixels = NtGdiGetDeviceCaps(ScreenDeviceContext, LOGPIXELSY); 305 if (NtGdiGetDeviceCaps(ScreenDeviceContext, RASTERCAPS) & RC_PALETTE) 306 { 307 gpsi->PUSIFlags |= PUSIF_PALETTEDISPLAY; 308 } 309 else 310 { 311 gpsi->PUSIFlags &= ~PUSIF_PALETTEDISPLAY; 312 } 313 // Font is realized and this dc was previously set to internal DC_ATTR. 314 gpsi->cxSysFontChar = IntGetCharDimensions(hSystemBM, &tmw, (DWORD*)&gpsi->cySysFontChar); 315 gpsi->tmSysFont = tmw; 316 317 /* Put the pointer in the center of the screen */ 318 gpsi->ptCursor.x = gpsi->aiSysMet[SM_CXSCREEN] / 2; 319 gpsi->ptCursor.y = gpsi->aiSysMet[SM_CYSCREEN] / 2; 320 321 /* Attach monitor */ 322 UserAttachMonitor((HDEV)gpmdev->ppdevGlobal); 323 324 /* Setup the cursor */ 325 co_IntLoadDefaultCursors(); 326 327 /* Setup the icons */ 328 co_IntSetWndIcons(); 329 330 /* Setup Menu */ 331 MenuInit(); 332 333 /* Show the desktop */ 334 pdesk = IntGetActiveDesktop(); 335 ASSERT(pdesk); 336 co_IntShowDesktop(pdesk, gpsi->aiSysMet[SM_CXSCREEN], gpsi->aiSysMet[SM_CYSCREEN], TRUE); 337 338 /* HACK: display wallpaper on all secondary displays */ 339 { 340 PGRAPHICS_DEVICE pGraphicsDevice; 341 UNICODE_STRING DriverName = RTL_CONSTANT_STRING(L"DISPLAY"); 342 UNICODE_STRING DisplayName; 343 HDC hdc; 344 ULONG iDevNum; 345 346 for (iDevNum = 1; (pGraphicsDevice = EngpFindGraphicsDevice(NULL, iDevNum)) != NULL; iDevNum++) 347 { 348 RtlInitUnicodeString(&DisplayName, pGraphicsDevice->szWinDeviceName); 349 hdc = IntGdiCreateDC(&DriverName, &DisplayName, NULL, NULL, FALSE); 350 IntPaintDesktop(hdc); 351 } 352 } 353 354 return TRUE; 355 } 356 357 VOID FASTCALL 358 IntEndDesktopGraphics(VOID) 359 { 360 if (NULL != ScreenDeviceContext) 361 { // No need to allocate a new dcattr. 362 GreSetDCOwner(ScreenDeviceContext, GDI_OBJ_HMGR_POWNED); 363 GreDeleteObject(ScreenDeviceContext); 364 ScreenDeviceContext = NULL; 365 } 366 IntHideDesktop(IntGetActiveDesktop()); 367 IntDestroyPrimarySurface(); 368 } 369 370 HDC FASTCALL 371 IntGetScreenDC(VOID) 372 { 373 return ScreenDeviceContext; 374 } 375 376 BOOL FASTCALL 377 CheckWinstaAttributeAccess(ACCESS_MASK DesiredAccess) 378 { 379 PPROCESSINFO ppi = PsGetCurrentProcessWin32Process(); 380 if ( gpidLogon != PsGetCurrentProcessId() ) 381 { 382 if (!(ppi->W32PF_flags & W32PF_IOWINSTA)) 383 { 384 ERR("Requires Interactive Window Station\n"); 385 EngSetLastError(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION); 386 return FALSE; 387 } 388 if (!RtlAreAllAccessesGranted(ppi->amwinsta, DesiredAccess)) 389 { 390 ERR("Access Denied\n"); 391 EngSetLastError(ERROR_ACCESS_DENIED); 392 return FALSE; 393 } 394 } 395 return TRUE; 396 } 397 398 // Win: _GetProcessWindowStation 399 PWINSTATION_OBJECT FASTCALL 400 IntGetProcessWindowStation(HWINSTA *phWinSta OPTIONAL) 401 { 402 PWINSTATION_OBJECT pWinSta; 403 PPROCESSINFO ppi = GetW32ProcessInfo(); 404 HWINSTA hWinSta = ppi->hwinsta; 405 if (phWinSta) 406 *phWinSta = hWinSta; 407 IntValidateWindowStationHandle(hWinSta, UserMode, 0, &pWinSta, 0); 408 return pWinSta; 409 } 410 411 /* PUBLIC FUNCTIONS ***********************************************************/ 412 413 /* 414 * NtUserCreateWindowStation 415 * 416 * Creates a new window station. 417 * 418 * Parameters 419 * lpszWindowStationName 420 * Pointer to a null-terminated string specifying the name of the 421 * window station to be created. Window station names are 422 * case-insensitive and cannot contain backslash characters (\). 423 * Only members of the Administrators group are allowed to specify a 424 * name. 425 * 426 * dwDesiredAccess 427 * Requested type of access 428 * 429 * lpSecurity 430 * Security descriptor 431 * 432 * Unknown3, Unknown4, Unknown5, Unknown6 433 * Unused 434 * 435 * Return Value 436 * If the function succeeds, the return value is a handle to the newly 437 * created window station. If the specified window station already 438 * exists, the function succeeds and returns a handle to the existing 439 * window station. If the function fails, the return value is NULL. 440 * 441 * Status 442 * @implemented 443 */ 444 445 NTSTATUS 446 FASTCALL 447 IntCreateWindowStation( 448 OUT HWINSTA* phWinSta, 449 IN POBJECT_ATTRIBUTES ObjectAttributes, 450 IN KPROCESSOR_MODE AccessMode, 451 IN KPROCESSOR_MODE OwnerMode, 452 IN ACCESS_MASK dwDesiredAccess, 453 DWORD Unknown2, 454 DWORD Unknown3, 455 DWORD Unknown4, 456 DWORD Unknown5, 457 DWORD Unknown6) 458 { 459 NTSTATUS Status; 460 HWINSTA hWinSta; 461 PWINSTATION_OBJECT WindowStation; 462 463 TRACE("IntCreateWindowStation called\n"); 464 465 ASSERT(phWinSta); 466 *phWinSta = NULL; 467 468 Status = ObOpenObjectByName(ObjectAttributes, 469 ExWindowStationObjectType, 470 AccessMode, 471 NULL, 472 dwDesiredAccess, 473 NULL, 474 (PVOID*)&hWinSta); 475 if (NT_SUCCESS(Status)) 476 { 477 TRACE("IntCreateWindowStation opened window station '%wZ'\n", 478 ObjectAttributes->ObjectName); 479 *phWinSta = hWinSta; 480 return Status; 481 } 482 483 /* 484 * No existing window station found, try to create a new one. 485 */ 486 487 /* Create the window station object */ 488 Status = ObCreateObject(AccessMode, 489 ExWindowStationObjectType, 490 ObjectAttributes, 491 OwnerMode, 492 NULL, 493 sizeof(WINSTATION_OBJECT), 494 0, 495 0, 496 (PVOID*)&WindowStation); 497 if (!NT_SUCCESS(Status)) 498 { 499 ERR("ObCreateObject failed for window station '%wZ', Status 0x%08lx\n", 500 ObjectAttributes->ObjectName, Status); 501 SetLastNtError(Status); 502 return Status; 503 } 504 505 /* Initialize the window station */ 506 RtlZeroMemory(WindowStation, sizeof(WINSTATION_OBJECT)); 507 508 InitializeListHead(&WindowStation->DesktopListHead); 509 WindowStation->dwSessionId = NtCurrentPeb()->SessionId; 510 Status = RtlCreateAtomTable(37, &WindowStation->AtomTable); 511 if (!NT_SUCCESS(Status)) 512 { 513 ERR("RtlCreateAtomTable failed for window station '%wZ', Status 0x%08lx\n", 514 ObjectAttributes->ObjectName, Status); 515 ObDereferenceObject(WindowStation); 516 SetLastNtError(Status); 517 return Status; 518 } 519 520 Status = ObInsertObject(WindowStation, 521 NULL, 522 dwDesiredAccess, 523 0, 524 NULL, 525 (PVOID*)&hWinSta); 526 if (!NT_SUCCESS(Status)) 527 { 528 ERR("ObInsertObject failed for window station, Status 0x%08lx\n", Status); 529 SetLastNtError(Status); 530 return Status; 531 } 532 533 // FIXME! TODO: Add this new window station to a linked list 534 535 if (InputWindowStation == NULL) 536 { 537 ERR("Initializing input window station\n"); 538 539 /* Only Winlogon can create the interactive window station */ 540 ASSERT(gpidLogon == PsGetCurrentProcessId()); 541 542 InputWindowStation = WindowStation; 543 WindowStation->Flags &= ~WSS_NOIO; 544 545 InitCursorImpl(); 546 547 UserCreateSystemThread(ST_DESKTOP_THREAD); 548 UserCreateSystemThread(ST_RIT); 549 550 /* Desktop functions require the desktop thread running so wait for it to initialize */ 551 UserLeaveCo(); 552 KeWaitForSingleObject(gpDesktopThreadStartedEvent, 553 UserRequest, 554 UserMode, 555 FALSE, 556 NULL); 557 UserEnterCo(); 558 } 559 else 560 { 561 WindowStation->Flags |= WSS_NOIO; 562 } 563 564 TRACE("IntCreateWindowStation created window station '%wZ' object 0x%p handle 0x%p\n", 565 ObjectAttributes->ObjectName, WindowStation, hWinSta); 566 567 *phWinSta = hWinSta; 568 EngSetLastError(ERROR_SUCCESS); 569 570 return STATUS_SUCCESS; 571 } 572 573 static VOID 574 FreeUserModeWindowStationName( 575 IN OUT PUNICODE_STRING WindowStationName, 576 IN PUNICODE_STRING TebStaticUnicodeString, 577 IN OUT POBJECT_ATTRIBUTES UserModeObjectAttributes OPTIONAL, 578 IN POBJECT_ATTRIBUTES LocalObjectAttributes OPTIONAL) 579 { 580 SIZE_T MemSize = 0; 581 582 /* Try to restore the user's UserModeObjectAttributes */ 583 if (UserModeObjectAttributes && LocalObjectAttributes) 584 { 585 _SEH2_TRY 586 { 587 ProbeForWrite(UserModeObjectAttributes, sizeof(OBJECT_ATTRIBUTES), sizeof(ULONG)); 588 *UserModeObjectAttributes = *LocalObjectAttributes; 589 } 590 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 591 { 592 NOTHING; 593 } 594 _SEH2_END; 595 } 596 597 /* Free the user-mode memory */ 598 if (WindowStationName && (WindowStationName != TebStaticUnicodeString)) 599 { 600 ZwFreeVirtualMemory(ZwCurrentProcess(), 601 (PVOID*)&WindowStationName, 602 &MemSize, 603 MEM_RELEASE); 604 } 605 } 606 607 static NTSTATUS 608 BuildUserModeWindowStationName( 609 IN OUT POBJECT_ATTRIBUTES UserModeObjectAttributes, 610 IN OUT POBJECT_ATTRIBUTES LocalObjectAttributes, 611 OUT PUNICODE_STRING* WindowStationName, 612 OUT PUNICODE_STRING* TebStaticUnicodeString) 613 { 614 NTSTATUS Status; 615 SIZE_T MemSize; 616 617 LUID CallerLuid; 618 PTEB Teb; 619 USHORT StrSize; 620 621 *WindowStationName = NULL; 622 *TebStaticUnicodeString = NULL; 623 624 /* Retrieve the current process LUID */ 625 Status = GetProcessLuid(NULL, NULL, &CallerLuid); 626 if (!NT_SUCCESS(Status)) 627 { 628 ERR("Failed to retrieve the caller LUID, Status 0x%08lx\n", Status); 629 return Status; 630 } 631 632 /* Compute the needed string size */ 633 MemSize = _scwprintf(L"%wZ\\Service-0x%x-%x$", 634 &gustrWindowStationsDir, 635 CallerLuid.HighPart, 636 CallerLuid.LowPart); 637 MemSize = MemSize * sizeof(WCHAR) + sizeof(UNICODE_NULL); 638 if (MemSize > MAXUSHORT) 639 { 640 ERR("Window station name length is too long.\n"); 641 return STATUS_NAME_TOO_LONG; 642 } 643 StrSize = (USHORT)MemSize; 644 645 /* 646 * Check whether it's short enough so that we can use the static buffer 647 * in the TEB. Otherwise continue with virtual memory allocation. 648 */ 649 Teb = NtCurrentTeb(); 650 if (Teb && (StrSize <= sizeof(Teb->StaticUnicodeBuffer))) 651 { 652 /* We can use the TEB's static unicode string */ 653 ASSERT(Teb->StaticUnicodeString.Buffer == Teb->StaticUnicodeBuffer); 654 ASSERT(Teb->StaticUnicodeString.MaximumLength == sizeof(Teb->StaticUnicodeBuffer)); 655 656 /* Remember the TEB's static unicode string address for later */ 657 *TebStaticUnicodeString = &Teb->StaticUnicodeString; 658 659 *WindowStationName = *TebStaticUnicodeString; 660 (*WindowStationName)->Length = 0; 661 } 662 else 663 { 664 /* The TEB's static unicode string is too small, allocate some user-mode virtual memory */ 665 MemSize += ALIGN_UP(sizeof(UNICODE_STRING), sizeof(PVOID)); 666 667 /* Allocate the memory in user-mode */ 668 Status = ZwAllocateVirtualMemory(ZwCurrentProcess(), 669 (PVOID*)WindowStationName, 670 0, 671 &MemSize, 672 MEM_COMMIT, 673 PAGE_READWRITE); 674 if (!NT_SUCCESS(Status)) 675 { 676 ERR("ZwAllocateVirtualMemory() failed, Status 0x%08lx\n", Status); 677 return Status; 678 } 679 680 RtlInitEmptyUnicodeString(*WindowStationName, 681 (PWCHAR)((ULONG_PTR)*WindowStationName + 682 ALIGN_UP(sizeof(UNICODE_STRING), sizeof(PVOID))), 683 StrSize); 684 } 685 686 /* Build a valid window station name from the LUID */ 687 Status = RtlStringCbPrintfW((*WindowStationName)->Buffer, 688 (*WindowStationName)->MaximumLength, 689 L"%wZ\\Service-0x%x-%x$", 690 &gustrWindowStationsDir, 691 CallerLuid.HighPart, 692 CallerLuid.LowPart); 693 if (!NT_SUCCESS(Status)) 694 { 695 ERR("Impossible to build a valid window station name, Status 0x%08lx\n", Status); 696 goto Quit; 697 } 698 (*WindowStationName)->Length = (USHORT)(wcslen((*WindowStationName)->Buffer) * sizeof(WCHAR)); 699 700 /* Try to update the user's UserModeObjectAttributes */ 701 _SEH2_TRY 702 { 703 ProbeForWrite(UserModeObjectAttributes, sizeof(OBJECT_ATTRIBUTES), sizeof(ULONG)); 704 *LocalObjectAttributes = *UserModeObjectAttributes; 705 706 UserModeObjectAttributes->ObjectName = *WindowStationName; 707 UserModeObjectAttributes->RootDirectory = NULL; 708 709 Status = STATUS_SUCCESS; 710 } 711 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 712 { 713 Status = _SEH2_GetExceptionCode(); 714 } 715 _SEH2_END; 716 717 Quit: 718 if (!NT_SUCCESS(Status)) 719 { 720 /* Release the window station name */ 721 FreeUserModeWindowStationName(*WindowStationName, 722 *TebStaticUnicodeString, 723 NULL, NULL); 724 } 725 726 return Status; 727 } 728 729 HWINSTA 730 APIENTRY 731 NtUserCreateWindowStation( 732 IN POBJECT_ATTRIBUTES ObjectAttributes, 733 IN ACCESS_MASK dwDesiredAccess, 734 DWORD Unknown2, 735 DWORD Unknown3, 736 DWORD Unknown4, 737 DWORD Unknown5, 738 DWORD Unknown6) 739 { 740 NTSTATUS Status = STATUS_SUCCESS; 741 HWINSTA hWinSta = NULL; 742 OBJECT_ATTRIBUTES LocalObjectAttributes; 743 PUNICODE_STRING WindowStationName = NULL; 744 PUNICODE_STRING TebStaticUnicodeString = NULL; 745 KPROCESSOR_MODE OwnerMode = UserMode; 746 747 TRACE("NtUserCreateWindowStation called\n"); 748 749 /* Capture the object attributes and the window station name */ 750 _SEH2_TRY 751 { 752 ProbeForRead(ObjectAttributes, sizeof(OBJECT_ATTRIBUTES), sizeof(ULONG)); 753 LocalObjectAttributes = *ObjectAttributes; 754 if (LocalObjectAttributes.Length != sizeof(OBJECT_ATTRIBUTES)) 755 { 756 ERR("Invalid ObjectAttributes length!\n"); 757 Status = STATUS_INVALID_PARAMETER; 758 _SEH2_LEAVE; 759 } 760 761 /* 762 * Check whether the caller provided a window station name together 763 * with a RootDirectory handle. 764 * 765 * If the caller did not provide a window station name, build a new one 766 * based on the logon session identifier for the calling process. 767 * The new name is allocated in user-mode, as the rest of ObjectAttributes 768 * already is, so that the validation performed by the Object Manager 769 * can be done adequately. 770 */ 771 if ((LocalObjectAttributes.ObjectName == NULL || 772 LocalObjectAttributes.ObjectName->Buffer == NULL || 773 LocalObjectAttributes.ObjectName->Length == 0 || 774 LocalObjectAttributes.ObjectName->Buffer[0] == UNICODE_NULL) 775 /* && 776 LocalObjectAttributes.RootDirectory == NULL */) 777 { 778 /* No, build the new window station name */ 779 Status = BuildUserModeWindowStationName(ObjectAttributes, 780 &LocalObjectAttributes, 781 &WindowStationName, 782 &TebStaticUnicodeString); 783 if (!NT_SUCCESS(Status)) 784 { 785 ERR("BuildUserModeWindowStationName() failed, Status 0x%08lx\n", Status); 786 _SEH2_LEAVE; 787 } 788 OwnerMode = KernelMode; 789 } 790 } 791 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 792 { 793 Status =_SEH2_GetExceptionCode(); 794 ERR("ObjectAttributes capture failed, Status 0x%08lx\n", Status); 795 } 796 _SEH2_END; 797 798 if (!NT_SUCCESS(Status)) 799 { 800 SetLastNtError(Status); 801 return NULL; 802 } 803 804 UserEnterExclusive(); 805 806 /* Create the window station */ 807 Status = IntCreateWindowStation(&hWinSta, 808 ObjectAttributes, 809 UserMode, 810 OwnerMode, 811 dwDesiredAccess, 812 Unknown2, 813 Unknown3, 814 Unknown4, 815 Unknown5, 816 Unknown6); 817 UserLeave(); 818 819 if (NT_SUCCESS(Status)) 820 { 821 TRACE("NtUserCreateWindowStation created window station '%wZ' with handle 0x%p\n", 822 ObjectAttributes->ObjectName, hWinSta); 823 } 824 else 825 { 826 ASSERT(hWinSta == NULL); 827 ERR("NtUserCreateWindowStation failed to create window station '%wZ', Status 0x%08lx\n", 828 ObjectAttributes->ObjectName, Status); 829 } 830 831 /* Try to restore the user's ObjectAttributes and release the window station name */ 832 FreeUserModeWindowStationName(WindowStationName, 833 TebStaticUnicodeString, 834 (OwnerMode == KernelMode ? ObjectAttributes : NULL), 835 &LocalObjectAttributes); 836 837 if (!NT_SUCCESS(Status)) 838 { 839 ASSERT(hWinSta == NULL); 840 SetLastNtError(Status); 841 } 842 843 return hWinSta; 844 } 845 846 /* 847 * NtUserOpenWindowStation 848 * 849 * Opens an existing window station. 850 * 851 * Parameters 852 * lpszWindowStationName 853 * Name of the existing window station. 854 * 855 * dwDesiredAccess 856 * Requested type of access. 857 * 858 * Return Value 859 * If the function succeeds, the return value is the handle to the 860 * specified window station. If the function fails, the return value 861 * is NULL. 862 * 863 * Remarks 864 * The returned handle can be closed with NtUserCloseWindowStation. 865 * 866 * Status 867 * @implemented 868 */ 869 870 HWINSTA 871 APIENTRY 872 NtUserOpenWindowStation( 873 IN POBJECT_ATTRIBUTES ObjectAttributes, 874 IN ACCESS_MASK dwDesiredAccess) 875 { 876 NTSTATUS Status = STATUS_SUCCESS; 877 HWINSTA hWinSta = NULL; 878 OBJECT_ATTRIBUTES LocalObjectAttributes; 879 PUNICODE_STRING WindowStationName = NULL; 880 PUNICODE_STRING TebStaticUnicodeString = NULL; 881 KPROCESSOR_MODE OwnerMode = UserMode; 882 883 TRACE("NtUserOpenWindowStation called\n"); 884 885 /* Capture the object attributes and the window station name */ 886 _SEH2_TRY 887 { 888 ProbeForRead(ObjectAttributes, sizeof(OBJECT_ATTRIBUTES), sizeof(ULONG)); 889 LocalObjectAttributes = *ObjectAttributes; 890 if (LocalObjectAttributes.Length != sizeof(OBJECT_ATTRIBUTES)) 891 { 892 ERR("Invalid ObjectAttributes length!\n"); 893 Status = STATUS_INVALID_PARAMETER; 894 _SEH2_LEAVE; 895 } 896 897 /* 898 * Check whether the caller did not provide a window station name, 899 * or provided the special "Service-0x00000000-00000000$" name. 900 * 901 * NOTE: On Windows, the special "Service-0x00000000-00000000$" string 902 * is used instead of an empty name (observed when API-monitoring 903 * OpenWindowStation() called with an empty window station name). 904 */ 905 if ((LocalObjectAttributes.ObjectName == NULL || 906 LocalObjectAttributes.ObjectName->Buffer == NULL || 907 LocalObjectAttributes.ObjectName->Length == 0 || 908 LocalObjectAttributes.ObjectName->Buffer[0] == UNICODE_NULL) 909 /* && 910 LocalObjectAttributes.RootDirectory == NULL */) 911 { 912 /* No, remember that for later */ 913 LocalObjectAttributes.ObjectName = NULL; 914 } 915 if (LocalObjectAttributes.ObjectName && 916 LocalObjectAttributes.ObjectName->Length == 917 sizeof(L"Service-0x00000000-00000000$") - sizeof(UNICODE_NULL) && 918 _wcsnicmp(LocalObjectAttributes.ObjectName->Buffer, 919 L"Service-0x00000000-00000000$", 920 LocalObjectAttributes.ObjectName->Length / sizeof(WCHAR)) == 0) 921 { 922 /* No, remember that for later */ 923 LocalObjectAttributes.ObjectName = NULL; 924 } 925 926 /* 927 * If the caller did not provide a window station name, build a new one 928 * based on the logon session identifier for the calling process. 929 * The new name is allocated in user-mode, as the rest of ObjectAttributes 930 * already is, so that the validation performed by the Object Manager 931 * can be done adequately. 932 */ 933 if (!LocalObjectAttributes.ObjectName) 934 { 935 /* No, build the new window station name */ 936 Status = BuildUserModeWindowStationName(ObjectAttributes, 937 &LocalObjectAttributes, 938 &WindowStationName, 939 &TebStaticUnicodeString); 940 if (!NT_SUCCESS(Status)) 941 { 942 ERR("BuildUserModeWindowStationName() failed, Status 0x%08lx\n", Status); 943 _SEH2_LEAVE; 944 } 945 OwnerMode = KernelMode; 946 } 947 } 948 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 949 { 950 Status =_SEH2_GetExceptionCode(); 951 ERR("ObjectAttributes capture failed, Status 0x%08lx\n", Status); 952 } 953 _SEH2_END; 954 955 if (!NT_SUCCESS(Status)) 956 { 957 SetLastNtError(Status); 958 return NULL; 959 } 960 961 /* Open the window station */ 962 Status = ObOpenObjectByName(ObjectAttributes, 963 ExWindowStationObjectType, 964 UserMode, 965 NULL, 966 dwDesiredAccess, 967 NULL, 968 (PVOID*)&hWinSta); 969 if (NT_SUCCESS(Status)) 970 { 971 TRACE("NtUserOpenWindowStation opened window station '%wZ' with handle 0x%p\n", 972 ObjectAttributes->ObjectName, hWinSta); 973 } 974 else 975 { 976 ASSERT(hWinSta == NULL); 977 ERR("NtUserOpenWindowStation failed to open window station '%wZ', Status 0x%08lx\n", 978 ObjectAttributes->ObjectName, Status); 979 } 980 981 /* Try to restore the user's ObjectAttributes and release the window station name */ 982 FreeUserModeWindowStationName(WindowStationName, 983 TebStaticUnicodeString, 984 (OwnerMode == KernelMode ? ObjectAttributes : NULL), 985 &LocalObjectAttributes); 986 987 if (!NT_SUCCESS(Status)) 988 { 989 ASSERT(hWinSta == NULL); 990 SetLastNtError(Status); 991 } 992 993 return hWinSta; 994 } 995 996 /* 997 * NtUserCloseWindowStation 998 * 999 * Closes a window station handle. 1000 * 1001 * Parameters 1002 * hWinSta 1003 * Handle to the window station. 1004 * 1005 * Return Value 1006 * Status 1007 * 1008 * Remarks 1009 * The window station handle can be created with NtUserCreateWindowStation 1010 * or NtUserOpenWindowStation. Attempts to close a handle to the window 1011 * station assigned to the calling process will fail. 1012 * 1013 * Status 1014 * @implemented 1015 */ 1016 1017 BOOL 1018 APIENTRY 1019 NtUserCloseWindowStation( 1020 HWINSTA hWinSta) 1021 { 1022 PWINSTATION_OBJECT Object; 1023 NTSTATUS Status; 1024 1025 TRACE("NtUserCloseWindowStation called (%p)\n", hWinSta); 1026 1027 if (hWinSta == UserGetProcessWindowStation()) 1028 { 1029 ERR("Attempted to close process window station\n"); 1030 return FALSE; 1031 } 1032 1033 Status = IntValidateWindowStationHandle(hWinSta, 1034 UserMode, 1035 0, 1036 &Object, 1037 NULL); 1038 if (!NT_SUCCESS(Status)) 1039 { 1040 ERR("Validation of window station handle (%p) failed\n", hWinSta); 1041 return FALSE; 1042 } 1043 1044 ObDereferenceObject(Object); 1045 1046 TRACE("Closing window station handle (%p)\n", hWinSta); 1047 1048 Status = ObCloseHandle(hWinSta, UserMode); 1049 if (!NT_SUCCESS(Status)) 1050 { 1051 SetLastNtError(Status); 1052 return FALSE; 1053 } 1054 1055 return TRUE; 1056 } 1057 1058 /* 1059 * NtUserGetObjectInformation 1060 * 1061 * The NtUserGetObjectInformation function retrieves information about a 1062 * window station or desktop object. 1063 * 1064 * Parameters 1065 * hObj 1066 * Handle to the window station or desktop object for which to 1067 * return information. This can be a handle of type HDESK or HWINSTA 1068 * (for example, a handle returned by NtUserCreateWindowStation, 1069 * NtUserOpenWindowStation, NtUserCreateDesktop, or NtUserOpenDesktop). 1070 * 1071 * nIndex 1072 * Specifies the object information to be retrieved. 1073 * 1074 * pvInfo 1075 * Pointer to a buffer to receive the object information. 1076 * 1077 * nLength 1078 * Specifies the size, in bytes, of the buffer pointed to by the 1079 * pvInfo parameter. 1080 * 1081 * lpnLengthNeeded 1082 * Pointer to a variable receiving the number of bytes required to 1083 * store the requested information. If this variable's value is 1084 * greater than the value of the nLength parameter when the function 1085 * returns, the function returns FALSE, and none of the information 1086 * is copied to the pvInfo buffer. If the value of the variable pointed 1087 * to by lpnLengthNeeded is less than or equal to the value of nLength, 1088 * the entire information block is copied. 1089 * 1090 * Return Value 1091 * If the function succeeds, the return value is nonzero. If the function 1092 * fails, the return value is zero. 1093 * 1094 * Status 1095 * @unimplemented 1096 */ 1097 1098 BOOL APIENTRY 1099 NtUserGetObjectInformation( 1100 HANDLE hObject, 1101 DWORD nIndex, 1102 PVOID pvInformation, 1103 DWORD nLength, 1104 PDWORD nLengthNeeded) 1105 { 1106 NTSTATUS Status; 1107 PWINSTATION_OBJECT WinStaObject = NULL; 1108 PDESKTOP DesktopObject = NULL; 1109 POBJECT_HEADER ObjectHeader; 1110 POBJECT_HEADER_NAME_INFO NameInfo; 1111 OBJECT_HANDLE_INFORMATION HandleInfo; 1112 USEROBJECTFLAGS ObjectFlags; 1113 PUNICODE_STRING pStrNameU = NULL; 1114 PVOID pvData = NULL; 1115 SIZE_T nDataSize = 0; 1116 1117 _SEH2_TRY 1118 { 1119 if (nLengthNeeded) 1120 ProbeForWrite(nLengthNeeded, sizeof(*nLengthNeeded), 1); 1121 ProbeForWrite(pvInformation, nLength, 1); 1122 } 1123 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1124 { 1125 SetLastNtError(_SEH2_GetExceptionCode()); 1126 return FALSE; 1127 } 1128 _SEH2_END; 1129 1130 /* Try window station */ 1131 TRACE("Trying to open window station 0x%p\n", hObject); 1132 Status = ObReferenceObjectByHandle(hObject, 1133 0, 1134 ExWindowStationObjectType, 1135 UserMode, 1136 (PVOID*)&WinStaObject, 1137 &HandleInfo); 1138 1139 if (Status == STATUS_OBJECT_TYPE_MISMATCH) 1140 { 1141 /* Try desktop */ 1142 TRACE("Trying to open desktop %p\n", hObject); 1143 WinStaObject = NULL; 1144 Status = IntValidateDesktopHandle(hObject, 1145 UserMode, 1146 0, 1147 &DesktopObject); 1148 } 1149 1150 if (!NT_SUCCESS(Status)) 1151 { 1152 ERR("Failed: 0x%x\n", Status); 1153 goto Exit; 1154 } 1155 1156 TRACE("WinSta or Desktop opened!\n"); 1157 1158 /* Get data */ 1159 switch (nIndex) 1160 { 1161 case UOI_FLAGS: 1162 { 1163 ObjectFlags.fReserved = FALSE; 1164 ObjectFlags.fInherit = !!(HandleInfo.HandleAttributes & OBJ_INHERIT); 1165 1166 ObjectFlags.dwFlags = 0; 1167 if (WinStaObject != NULL) 1168 { 1169 if (!(WinStaObject->Flags & WSS_NOIO)) 1170 ObjectFlags.dwFlags |= WSF_VISIBLE; 1171 } 1172 else if (DesktopObject != NULL) 1173 { 1174 FIXME("Setting DF_ALLOWOTHERACCOUNTHOOK is unimplemented.\n"); 1175 } 1176 else 1177 { 1178 ERR("No associated WinStaObject nor DesktopObject!\n"); 1179 } 1180 1181 pvData = &ObjectFlags; 1182 nDataSize = sizeof(ObjectFlags); 1183 Status = STATUS_SUCCESS; 1184 break; 1185 } 1186 1187 case UOI_NAME: 1188 { 1189 if (WinStaObject != NULL) 1190 { 1191 ObjectHeader = OBJECT_TO_OBJECT_HEADER(WinStaObject); 1192 NameInfo = OBJECT_HEADER_TO_NAME_INFO(ObjectHeader); 1193 1194 if (NameInfo && (NameInfo->Name.Length > 0)) 1195 { 1196 /* Named window station */ 1197 pStrNameU = &NameInfo->Name; 1198 nDataSize = pStrNameU->Length + sizeof(UNICODE_NULL); 1199 } 1200 else 1201 { 1202 /* Unnamed window station (should never happen!) */ 1203 ASSERT(FALSE); 1204 pStrNameU = NULL; 1205 nDataSize = sizeof(UNICODE_NULL); 1206 } 1207 Status = STATUS_SUCCESS; 1208 } 1209 else if (DesktopObject != NULL) 1210 { 1211 pvData = DesktopObject->pDeskInfo->szDesktopName; 1212 nDataSize = (wcslen(DesktopObject->pDeskInfo->szDesktopName) + 1) * sizeof(WCHAR); 1213 Status = STATUS_SUCCESS; 1214 } 1215 else 1216 { 1217 Status = STATUS_INVALID_PARAMETER; 1218 } 1219 break; 1220 } 1221 1222 case UOI_TYPE: 1223 { 1224 if (WinStaObject != NULL) 1225 { 1226 ObjectHeader = OBJECT_TO_OBJECT_HEADER(WinStaObject); 1227 pStrNameU = &ObjectHeader->Type->Name; 1228 nDataSize = pStrNameU->Length + sizeof(UNICODE_NULL); 1229 Status = STATUS_SUCCESS; 1230 } 1231 else if (DesktopObject != NULL) 1232 { 1233 ObjectHeader = OBJECT_TO_OBJECT_HEADER(DesktopObject); 1234 pStrNameU = &ObjectHeader->Type->Name; 1235 nDataSize = pStrNameU->Length + sizeof(UNICODE_NULL); 1236 Status = STATUS_SUCCESS; 1237 } 1238 else 1239 { 1240 Status = STATUS_INVALID_PARAMETER; 1241 } 1242 break; 1243 } 1244 1245 case UOI_USER_SID: 1246 Status = STATUS_NOT_IMPLEMENTED; 1247 ERR("UOI_USER_SID unimplemented!\n"); 1248 break; 1249 1250 default: 1251 Status = STATUS_INVALID_PARAMETER; 1252 break; 1253 } 1254 1255 Exit: 1256 if ((Status == STATUS_SUCCESS) && (nLength < nDataSize)) 1257 Status = STATUS_BUFFER_TOO_SMALL; 1258 1259 _SEH2_TRY 1260 { 1261 if (nLengthNeeded) 1262 *nLengthNeeded = nDataSize; 1263 1264 /* Try to copy data to caller */ 1265 if (Status == STATUS_SUCCESS && (nDataSize > 0)) 1266 { 1267 TRACE("Trying to copy data to caller (len = %lu, len needed = %lu)\n", nLength, nDataSize); 1268 if (pvData) 1269 { 1270 /* Copy the data */ 1271 RtlCopyMemory(pvInformation, pvData, nDataSize); 1272 } 1273 else if (pStrNameU) 1274 { 1275 /* Copy and NULL-terminate the string */ 1276 RtlCopyMemory(pvInformation, pStrNameU->Buffer, pStrNameU->Length); 1277 ((PWCHAR)pvInformation)[pStrNameU->Length / sizeof(WCHAR)] = UNICODE_NULL; 1278 } 1279 else 1280 { 1281 /* Zero the memory */ 1282 RtlZeroMemory(pvInformation, nDataSize); 1283 } 1284 } 1285 } 1286 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 1287 { 1288 Status = _SEH2_GetExceptionCode(); 1289 } 1290 _SEH2_END; 1291 1292 /* Release objects */ 1293 if (DesktopObject != NULL) 1294 ObDereferenceObject(DesktopObject); 1295 if (WinStaObject != NULL) 1296 ObDereferenceObject(WinStaObject); 1297 1298 if (!NT_SUCCESS(Status)) 1299 { 1300 SetLastNtError(Status); 1301 return FALSE; 1302 } 1303 1304 return TRUE; 1305 } 1306 1307 /* 1308 * NtUserSetObjectInformation 1309 * 1310 * The NtUserSetObjectInformation function sets information about a 1311 * window station or desktop object. 1312 * 1313 * Parameters 1314 * hObj 1315 * Handle to the window station or desktop object for which to set 1316 * object information. This value can be a handle of type HDESK or 1317 * HWINSTA. 1318 * 1319 * nIndex 1320 * Specifies the object information to be set. 1321 * 1322 * pvInfo 1323 * Pointer to a buffer containing the object information. 1324 * 1325 * nLength 1326 * Specifies the size, in bytes, of the information contained in the 1327 * buffer pointed to by pvInfo. 1328 * 1329 * Return Value 1330 * If the function succeeds, the return value is nonzero. If the function 1331 * fails the return value is zero. 1332 * 1333 * Status 1334 * @unimplemented 1335 */ 1336 1337 BOOL 1338 APIENTRY 1339 NtUserSetObjectInformation( 1340 HANDLE hObject, 1341 DWORD nIndex, 1342 PVOID pvInformation, 1343 DWORD nLength) 1344 { 1345 /* FIXME: ZwQueryObject */ 1346 /* FIXME: ZwSetInformationObject */ 1347 SetLastNtError(STATUS_UNSUCCESSFUL); 1348 return FALSE; 1349 } 1350 1351 1352 HWINSTA FASTCALL 1353 UserGetProcessWindowStation(VOID) 1354 { 1355 PPROCESSINFO ppi = PsGetCurrentProcessWin32Process(); 1356 1357 return ppi->hwinsta; 1358 } 1359 1360 1361 /* 1362 * NtUserGetProcessWindowStation 1363 * 1364 * Returns a handle to the current process window station. 1365 * 1366 * Return Value 1367 * If the function succeeds, the return value is handle to the window 1368 * station assigned to the current process. If the function fails, the 1369 * return value is NULL. 1370 * 1371 * Status 1372 * @implemented 1373 */ 1374 1375 HWINSTA APIENTRY 1376 NtUserGetProcessWindowStation(VOID) 1377 { 1378 return UserGetProcessWindowStation(); 1379 } 1380 1381 BOOL FASTCALL 1382 UserSetProcessWindowStation(HWINSTA hWindowStation) 1383 { 1384 NTSTATUS Status; 1385 PPROCESSINFO ppi; 1386 OBJECT_HANDLE_INFORMATION ObjectHandleInfo; 1387 PWINSTATION_OBJECT NewWinSta = NULL, OldWinSta; 1388 HWINSTA hCacheWinSta; 1389 1390 ppi = PsGetCurrentProcessWin32Process(); 1391 1392 /* Reference the new window station */ 1393 if (hWindowStation != NULL) 1394 { 1395 Status = IntValidateWindowStationHandle(hWindowStation, 1396 UserMode, 1397 0, 1398 &NewWinSta, 1399 &ObjectHandleInfo); 1400 if (!NT_SUCCESS(Status)) 1401 { 1402 TRACE("Validation of window station handle 0x%p failed\n", hWindowStation); 1403 SetLastNtError(Status); 1404 return FALSE; 1405 } 1406 } 1407 1408 OldWinSta = ppi->prpwinsta; 1409 hCacheWinSta = PsGetProcessWin32WindowStation(ppi->peProcess); 1410 1411 /* Dereference the previous window station */ 1412 if (OldWinSta != NULL) 1413 { 1414 ObDereferenceObject(OldWinSta); 1415 } 1416 1417 /* 1418 * FIXME: Don't allow changing the window station if there are threads that are attached to desktops and own GUI objects? 1419 */ 1420 1421 /* Close the cached EPROCESS window station handle if needed */ 1422 if (hCacheWinSta != NULL) 1423 { 1424 /* Reference the window station */ 1425 Status = ObReferenceObjectByHandle(hCacheWinSta, 1426 0, 1427 ExWindowStationObjectType, 1428 UserMode, 1429 (PVOID*)&OldWinSta, 1430 NULL); 1431 if (!NT_SUCCESS(Status)) 1432 { 1433 ERR("Failed to reference the inherited window station, Status 0x%08lx\n", Status); 1434 /* We failed, reset the cache */ 1435 hCacheWinSta = NULL; 1436 PsSetProcessWindowStation(ppi->peProcess, hCacheWinSta); 1437 } 1438 else 1439 { 1440 /* 1441 * Close the old handle and reset the cache only 1442 * if we are setting a different window station. 1443 */ 1444 if (NewWinSta != OldWinSta) 1445 { 1446 ObCloseHandle(hCacheWinSta, UserMode); 1447 hCacheWinSta = NULL; 1448 PsSetProcessWindowStation(ppi->peProcess, hCacheWinSta); 1449 } 1450 1451 /* Dereference the window station */ 1452 ObDereferenceObject(OldWinSta); 1453 } 1454 } 1455 1456 /* Duplicate and save a new cached EPROCESS window station handle */ 1457 if ((hCacheWinSta == NULL) && (hWindowStation != NULL)) 1458 { 1459 Status = ZwDuplicateObject(ZwCurrentProcess(), 1460 hWindowStation, 1461 ZwCurrentProcess(), 1462 (PHANDLE)&hCacheWinSta, 1463 0, 1464 0, 1465 DUPLICATE_SAME_ACCESS); 1466 if (!NT_SUCCESS(Status)) 1467 { 1468 ERR("UserSetProcessWindowStation: Failed to duplicate the window station handle, Status 0x%08lx\n", Status); 1469 } 1470 else 1471 { 1472 PsSetProcessWindowStation(ppi->peProcess, hCacheWinSta); 1473 } 1474 } 1475 1476 ppi->prpwinsta = NewWinSta; 1477 ppi->hwinsta = hWindowStation; 1478 ppi->amwinsta = hWindowStation != NULL ? ObjectHandleInfo.GrantedAccess : 0; 1479 TRACE("WS : Granted Access 0x%08lx\n",ppi->amwinsta); 1480 1481 if (RtlAreAllAccessesGranted(ppi->amwinsta, WINSTA_READSCREEN)) 1482 { 1483 ppi->W32PF_flags |= W32PF_READSCREENACCESSGRANTED; 1484 } 1485 else 1486 { 1487 ppi->W32PF_flags &= ~W32PF_READSCREENACCESSGRANTED; 1488 } 1489 1490 if (NewWinSta && !(NewWinSta->Flags & WSS_NOIO)) 1491 { 1492 ppi->W32PF_flags |= W32PF_IOWINSTA; 1493 } 1494 else /* Might be closed if the handle is NULL */ 1495 { 1496 ppi->W32PF_flags &= ~W32PF_IOWINSTA; 1497 } 1498 return TRUE; 1499 } 1500 1501 /* 1502 * NtUserSetProcessWindowStation 1503 * 1504 * Assigns a window station to the current process. 1505 * 1506 * Parameters 1507 * hWinSta 1508 * Handle to the window station. 1509 * 1510 * Return Value 1511 * Status 1512 * 1513 * Status 1514 * @implemented 1515 */ 1516 1517 BOOL APIENTRY 1518 NtUserSetProcessWindowStation(HWINSTA hWindowStation) 1519 { 1520 BOOL ret; 1521 1522 UserEnterExclusive(); 1523 1524 ret = UserSetProcessWindowStation(hWindowStation); 1525 1526 UserLeave(); 1527 1528 return ret; 1529 } 1530 1531 /* 1532 * NtUserLockWindowStation 1533 * 1534 * Locks switching desktops. Only the logon application is allowed to call this function. 1535 * 1536 * Status 1537 * @implemented 1538 */ 1539 1540 BOOL APIENTRY 1541 NtUserLockWindowStation(HWINSTA hWindowStation) 1542 { 1543 PWINSTATION_OBJECT Object; 1544 NTSTATUS Status; 1545 1546 TRACE("About to set process window station with handle (%p)\n", 1547 hWindowStation); 1548 1549 if (gpidLogon != PsGetCurrentProcessId()) 1550 { 1551 ERR("Unauthorized process attempted to lock the window station!\n"); 1552 EngSetLastError(ERROR_ACCESS_DENIED); 1553 return FALSE; 1554 } 1555 1556 Status = IntValidateWindowStationHandle(hWindowStation, 1557 UserMode, 1558 0, 1559 &Object, 1560 NULL); 1561 if (!NT_SUCCESS(Status)) 1562 { 1563 TRACE("Validation of window station handle (%p) failed\n", 1564 hWindowStation); 1565 SetLastNtError(Status); 1566 return FALSE; 1567 } 1568 1569 Object->Flags |= WSS_LOCKED; 1570 1571 ObDereferenceObject(Object); 1572 return TRUE; 1573 } 1574 1575 /* 1576 * NtUserUnlockWindowStation 1577 * 1578 * Unlocks switching desktops. Only the logon application is allowed to call this function. 1579 * 1580 * Status 1581 * @implemented 1582 */ 1583 1584 BOOL APIENTRY 1585 NtUserUnlockWindowStation(HWINSTA hWindowStation) 1586 { 1587 PWINSTATION_OBJECT Object; 1588 NTSTATUS Status; 1589 BOOL Ret; 1590 1591 TRACE("About to set process window station with handle (%p)\n", 1592 hWindowStation); 1593 1594 if (gpidLogon != PsGetCurrentProcessId()) 1595 { 1596 ERR("Unauthorized process attempted to unlock the window station!\n"); 1597 EngSetLastError(ERROR_ACCESS_DENIED); 1598 return FALSE; 1599 } 1600 1601 Status = IntValidateWindowStationHandle(hWindowStation, 1602 UserMode, 1603 0, 1604 &Object, 1605 NULL); 1606 if (!NT_SUCCESS(Status)) 1607 { 1608 TRACE("Validation of window station handle (%p) failed\n", 1609 hWindowStation); 1610 SetLastNtError(Status); 1611 return FALSE; 1612 } 1613 1614 Ret = (Object->Flags & WSS_LOCKED) == WSS_LOCKED; 1615 Object->Flags &= ~WSS_LOCKED; 1616 1617 ObDereferenceObject(Object); 1618 return Ret; 1619 } 1620 1621 static NTSTATUS FASTCALL 1622 BuildWindowStationNameList( 1623 ULONG dwSize, 1624 PVOID lpBuffer, 1625 PULONG pRequiredSize) 1626 { 1627 OBJECT_ATTRIBUTES ObjectAttributes; 1628 NTSTATUS Status; 1629 HANDLE DirectoryHandle; 1630 char InitialBuffer[256], *Buffer; 1631 ULONG Context, ReturnLength, BufferSize; 1632 DWORD EntryCount; 1633 POBJECT_DIRECTORY_INFORMATION DirEntry; 1634 WCHAR NullWchar; 1635 1636 // 1637 // FIXME: Fully wrong! Since, by calling NtUserCreateWindowStation 1638 // with judicious parameters one can create window stations elsewhere 1639 // than in Windows\WindowStations directory, Win32k definitely MUST 1640 // maintain a list of window stations it has created, and not rely 1641 // on the enumeration of Windows\WindowStations !!! 1642 // 1643 1644 /* 1645 * Try to open the directory. 1646 */ 1647 InitializeObjectAttributes(&ObjectAttributes, 1648 &gustrWindowStationsDir, 1649 OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, 1650 NULL, 1651 NULL); 1652 1653 Status = ZwOpenDirectoryObject(&DirectoryHandle, 1654 DIRECTORY_QUERY, 1655 &ObjectAttributes); 1656 1657 if (!NT_SUCCESS(Status)) 1658 { 1659 return Status; 1660 } 1661 1662 /* First try to query the directory using a fixed-size buffer */ 1663 Context = 0; 1664 Buffer = NULL; 1665 Status = ZwQueryDirectoryObject(DirectoryHandle, 1666 InitialBuffer, 1667 sizeof(InitialBuffer), 1668 FALSE, 1669 TRUE, 1670 &Context, 1671 &ReturnLength); 1672 if (NT_SUCCESS(Status)) 1673 { 1674 if (STATUS_NO_MORE_ENTRIES == ZwQueryDirectoryObject(DirectoryHandle, NULL, 0, FALSE, 1675 FALSE, &Context, NULL)) 1676 { 1677 /* Our fixed-size buffer is large enough */ 1678 Buffer = InitialBuffer; 1679 } 1680 } 1681 1682 if (NULL == Buffer) 1683 { 1684 /* Need a larger buffer, check how large exactly */ 1685 Status = ZwQueryDirectoryObject(DirectoryHandle, NULL, 0, FALSE, TRUE, &Context, 1686 &ReturnLength); 1687 if (!NT_SUCCESS(Status)) 1688 { 1689 ERR("ZwQueryDirectoryObject failed\n"); 1690 ZwClose(DirectoryHandle); 1691 return Status; 1692 } 1693 1694 BufferSize = ReturnLength; 1695 Buffer = ExAllocatePoolWithTag(PagedPool, BufferSize, TAG_WINSTA); 1696 if (NULL == Buffer) 1697 { 1698 ZwClose(DirectoryHandle); 1699 return STATUS_NO_MEMORY; 1700 } 1701 1702 /* We should have a sufficiently large buffer now */ 1703 Context = 0; 1704 Status = ZwQueryDirectoryObject(DirectoryHandle, Buffer, BufferSize, 1705 FALSE, TRUE, &Context, &ReturnLength); 1706 if (! NT_SUCCESS(Status) || 1707 STATUS_NO_MORE_ENTRIES != ZwQueryDirectoryObject(DirectoryHandle, NULL, 0, FALSE, 1708 FALSE, &Context, NULL)) 1709 { 1710 /* Something went wrong, maybe someone added a directory entry? Just give up. */ 1711 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1712 ZwClose(DirectoryHandle); 1713 return NT_SUCCESS(Status) ? STATUS_INTERNAL_ERROR : Status; 1714 } 1715 } 1716 1717 ZwClose(DirectoryHandle); 1718 1719 /* 1720 * Count the required size of buffer. 1721 */ 1722 ReturnLength = sizeof(DWORD); 1723 EntryCount = 0; 1724 for (DirEntry = (POBJECT_DIRECTORY_INFORMATION) Buffer; 1725 0 != DirEntry->Name.Length; 1726 DirEntry++) 1727 { 1728 ReturnLength += DirEntry->Name.Length + sizeof(WCHAR); 1729 EntryCount++; 1730 } 1731 TRACE("Required size: %lu Entry count: %lu\n", ReturnLength, EntryCount); 1732 if (NULL != pRequiredSize) 1733 { 1734 Status = MmCopyToCaller(pRequiredSize, &ReturnLength, sizeof(ULONG)); 1735 if (! NT_SUCCESS(Status)) 1736 { 1737 if (Buffer != InitialBuffer) 1738 { 1739 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1740 } 1741 return STATUS_BUFFER_TOO_SMALL; 1742 } 1743 } 1744 1745 /* 1746 * Check if the supplied buffer is large enough. 1747 */ 1748 if (dwSize < ReturnLength) 1749 { 1750 if (Buffer != InitialBuffer) 1751 { 1752 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1753 } 1754 return STATUS_BUFFER_TOO_SMALL; 1755 } 1756 1757 /* 1758 * Generate the resulting buffer contents. 1759 */ 1760 Status = MmCopyToCaller(lpBuffer, &EntryCount, sizeof(DWORD)); 1761 if (! NT_SUCCESS(Status)) 1762 { 1763 if (Buffer != InitialBuffer) 1764 { 1765 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1766 } 1767 return Status; 1768 } 1769 lpBuffer = (PVOID) ((PCHAR) lpBuffer + sizeof(DWORD)); 1770 1771 NullWchar = L'\0'; 1772 for (DirEntry = (POBJECT_DIRECTORY_INFORMATION) Buffer; 1773 0 != DirEntry->Name.Length; 1774 DirEntry++) 1775 { 1776 Status = MmCopyToCaller(lpBuffer, DirEntry->Name.Buffer, DirEntry->Name.Length); 1777 if (! NT_SUCCESS(Status)) 1778 { 1779 if (Buffer != InitialBuffer) 1780 { 1781 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1782 } 1783 return Status; 1784 } 1785 lpBuffer = (PVOID) ((PCHAR) lpBuffer + DirEntry->Name.Length); 1786 Status = MmCopyToCaller(lpBuffer, &NullWchar, sizeof(WCHAR)); 1787 if (! NT_SUCCESS(Status)) 1788 { 1789 if (Buffer != InitialBuffer) 1790 { 1791 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1792 } 1793 return Status; 1794 } 1795 lpBuffer = (PVOID) ((PCHAR) lpBuffer + sizeof(WCHAR)); 1796 } 1797 1798 /* 1799 * Clean up 1800 */ 1801 if (Buffer != InitialBuffer) 1802 { 1803 ExFreePoolWithTag(Buffer, TAG_WINSTA); 1804 } 1805 1806 return STATUS_SUCCESS; 1807 } 1808 1809 static NTSTATUS FASTCALL 1810 BuildDesktopNameList( 1811 HWINSTA hWindowStation, 1812 ULONG dwSize, 1813 PVOID lpBuffer, 1814 PULONG pRequiredSize) 1815 { 1816 NTSTATUS Status; 1817 PWINSTATION_OBJECT WindowStation; 1818 PLIST_ENTRY DesktopEntry; 1819 PDESKTOP DesktopObject; 1820 DWORD EntryCount; 1821 ULONG ReturnLength; 1822 WCHAR NullWchar; 1823 UNICODE_STRING DesktopName; 1824 1825 Status = IntValidateWindowStationHandle(hWindowStation, 1826 UserMode, 1827 0, 1828 &WindowStation, 1829 NULL); 1830 if (! NT_SUCCESS(Status)) 1831 { 1832 return Status; 1833 } 1834 1835 /* 1836 * Count the required size of buffer. 1837 */ 1838 ReturnLength = sizeof(DWORD); 1839 EntryCount = 0; 1840 for (DesktopEntry = WindowStation->DesktopListHead.Flink; 1841 DesktopEntry != &WindowStation->DesktopListHead; 1842 DesktopEntry = DesktopEntry->Flink) 1843 { 1844 DesktopObject = CONTAINING_RECORD(DesktopEntry, DESKTOP, ListEntry); 1845 RtlInitUnicodeString(&DesktopName, DesktopObject->pDeskInfo->szDesktopName); 1846 ReturnLength += DesktopName.Length + sizeof(WCHAR); 1847 EntryCount++; 1848 } 1849 TRACE("Required size: %lu Entry count: %lu\n", ReturnLength, EntryCount); 1850 if (NULL != pRequiredSize) 1851 { 1852 Status = MmCopyToCaller(pRequiredSize, &ReturnLength, sizeof(ULONG)); 1853 if (! NT_SUCCESS(Status)) 1854 { 1855 ObDereferenceObject(WindowStation); 1856 return STATUS_BUFFER_TOO_SMALL; 1857 } 1858 } 1859 1860 /* 1861 * Check if the supplied buffer is large enough. 1862 */ 1863 if (dwSize < ReturnLength) 1864 { 1865 ObDereferenceObject(WindowStation); 1866 return STATUS_BUFFER_TOO_SMALL; 1867 } 1868 1869 /* 1870 * Generate the resulting buffer contents. 1871 */ 1872 Status = MmCopyToCaller(lpBuffer, &EntryCount, sizeof(DWORD)); 1873 if (! NT_SUCCESS(Status)) 1874 { 1875 ObDereferenceObject(WindowStation); 1876 return Status; 1877 } 1878 lpBuffer = (PVOID) ((PCHAR) lpBuffer + sizeof(DWORD)); 1879 1880 NullWchar = L'\0'; 1881 for (DesktopEntry = WindowStation->DesktopListHead.Flink; 1882 DesktopEntry != &WindowStation->DesktopListHead; 1883 DesktopEntry = DesktopEntry->Flink) 1884 { 1885 DesktopObject = CONTAINING_RECORD(DesktopEntry, DESKTOP, ListEntry); 1886 RtlInitUnicodeString(&DesktopName, DesktopObject->pDeskInfo->szDesktopName); 1887 Status = MmCopyToCaller(lpBuffer, DesktopName.Buffer, DesktopName.Length); 1888 if (! NT_SUCCESS(Status)) 1889 { 1890 ObDereferenceObject(WindowStation); 1891 return Status; 1892 } 1893 lpBuffer = (PVOID) ((PCHAR)lpBuffer + DesktopName.Length); 1894 Status = MmCopyToCaller(lpBuffer, &NullWchar, sizeof(WCHAR)); 1895 if (! NT_SUCCESS(Status)) 1896 { 1897 ObDereferenceObject(WindowStation); 1898 return Status; 1899 } 1900 lpBuffer = (PVOID) ((PCHAR) lpBuffer + sizeof(WCHAR)); 1901 } 1902 1903 /* 1904 * Clean up and return 1905 */ 1906 ObDereferenceObject(WindowStation); 1907 return STATUS_SUCCESS; 1908 } 1909 1910 /* 1911 * NtUserBuildNameList 1912 * 1913 * Function used for enumeration of desktops or window stations. 1914 * 1915 * Parameters 1916 * hWinSta 1917 * For enumeration of window stations this parameter must be set to 1918 * zero. Otherwise it's handle for window station. 1919 * 1920 * dwSize 1921 * Size of buffer passed by caller. 1922 * 1923 * lpBuffer 1924 * Buffer passed by caller. If the function succeeds, the buffer is 1925 * filled with window station/desktop count (in first DWORD) and 1926 * NULL-terminated window station/desktop names. 1927 * 1928 * pRequiredSize 1929 * If the function succeeds, this is the number of bytes copied. 1930 * Otherwise it's size of buffer needed for function to succeed. 1931 * 1932 * Status 1933 * @implemented 1934 */ 1935 1936 NTSTATUS APIENTRY 1937 NtUserBuildNameList( 1938 HWINSTA hWindowStation, 1939 ULONG dwSize, 1940 PVOID lpBuffer, 1941 PULONG pRequiredSize) 1942 { 1943 /* The WindowStation name list and desktop name list are build in completely 1944 different ways. Call the appropriate function */ 1945 return NULL == hWindowStation ? BuildWindowStationNameList(dwSize, lpBuffer, pRequiredSize) : 1946 BuildDesktopNameList(hWindowStation, dwSize, lpBuffer, pRequiredSize); 1947 } 1948 1949 /* 1950 * @implemented 1951 */ 1952 BOOL APIENTRY 1953 NtUserSetLogonNotifyWindow(HWND hWnd) 1954 { 1955 if (gpidLogon != PsGetCurrentProcessId()) 1956 { 1957 return FALSE; 1958 } 1959 1960 if (!IntIsWindow(hWnd)) 1961 { 1962 return FALSE; 1963 } 1964 1965 hwndSAS = hWnd; 1966 1967 return TRUE; 1968 } 1969 1970 BOOL 1971 APIENTRY 1972 NtUserLockWorkStation(VOID) 1973 { 1974 BOOL ret; 1975 PTHREADINFO pti = PsGetCurrentThreadWin32Thread(); 1976 1977 UserEnterExclusive(); 1978 1979 if (pti->rpdesk == IntGetActiveDesktop()) 1980 { 1981 ret = UserPostMessage(hwndSAS, WM_LOGONNOTIFY, LN_LOCK_WORKSTATION, 0); 1982 } 1983 else 1984 { 1985 ret = FALSE; 1986 } 1987 1988 UserLeave(); 1989 1990 return ret; 1991 } 1992 1993 BOOL 1994 NTAPI 1995 NtUserSetWindowStationUser( 1996 IN HWINSTA hWindowStation, 1997 IN PLUID pluid, 1998 IN PSID psid OPTIONAL, 1999 IN DWORD size) 2000 { 2001 BOOL Ret = FALSE; 2002 NTSTATUS Status; 2003 PWINSTATION_OBJECT WindowStation = NULL; 2004 LUID luidUser; 2005 2006 UserEnterExclusive(); 2007 2008 if (gpidLogon != PsGetCurrentProcessId()) 2009 { 2010 EngSetLastError(ERROR_ACCESS_DENIED); 2011 goto Leave; 2012 } 2013 2014 /* Validate the window station */ 2015 Status = IntValidateWindowStationHandle(hWindowStation, 2016 UserMode, 2017 0, 2018 &WindowStation, 2019 NULL); 2020 if (!NT_SUCCESS(Status)) 2021 { 2022 goto Leave; 2023 } 2024 2025 /* Capture the user LUID */ 2026 _SEH2_TRY 2027 { 2028 ProbeForRead(pluid, sizeof(LUID), 1); 2029 luidUser = *pluid; 2030 } 2031 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 2032 { 2033 Status = _SEH2_GetExceptionCode(); 2034 _SEH2_YIELD(goto Leave); 2035 } 2036 _SEH2_END; 2037 2038 /* Reset the window station user LUID */ 2039 RtlZeroMemory(&WindowStation->luidUser, sizeof(LUID)); 2040 2041 /* Reset the window station user SID */ 2042 if (WindowStation->psidUser) 2043 { 2044 ExFreePoolWithTag(WindowStation->psidUser, USERTAG_SECURITY); 2045 WindowStation->psidUser = NULL; 2046 } 2047 2048 /* Copy the new user SID if one has been provided */ 2049 if (psid) 2050 { 2051 WindowStation->psidUser = ExAllocatePoolWithTag(PagedPool, size, USERTAG_SECURITY); 2052 if (WindowStation->psidUser == NULL) 2053 { 2054 EngSetLastError(ERROR_OUTOFMEMORY); 2055 goto Leave; 2056 } 2057 2058 Status = STATUS_SUCCESS; 2059 _SEH2_TRY 2060 { 2061 ProbeForRead(psid, size, 1); 2062 RtlCopyMemory(WindowStation->psidUser, psid, size); 2063 } 2064 _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER) 2065 { 2066 Status = _SEH2_GetExceptionCode(); 2067 } 2068 _SEH2_END; 2069 2070 if (!NT_SUCCESS(Status)) 2071 { 2072 ExFreePoolWithTag(WindowStation->psidUser, USERTAG_SECURITY); 2073 WindowStation->psidUser = NULL; 2074 goto Leave; 2075 } 2076 } 2077 2078 /* Copy the new user LUID */ 2079 WindowStation->luidUser = luidUser; 2080 2081 Ret = TRUE; 2082 2083 Leave: 2084 if (WindowStation) 2085 ObDereferenceObject(WindowStation); 2086 2087 UserLeave(); 2088 return Ret; 2089 } 2090 2091 /* EOF */ 2092