[BASESRV] Implement LUID mapped drive arrival/removal notification
authorPierre Schweitzer <pierre@reactos.org>
Thu, 20 Jun 2019 07:00:07 +0000 (09:00 +0200)
committerPierre Schweitzer <pierre@reactos.org>
Sun, 30 Jun 2019 21:07:54 +0000 (23:07 +0200)
CORE-16114

subsystems/win/basesrv/basesrv.h
subsystems/win/basesrv/dosdev.c
subsystems/win/basesrv/init.c

index 67b73a6..c758ab4 100644 (file)
@@ -16,6 +16,7 @@
 #define COM_NO_WINDOWS_H
 #include <windef.h>
 #include <winbase.h>
+#include <dbt.h>
 #define NTOS_MODE_USER
 #include <ndk/rtlfuncs.h>
 #include <ndk/obfuncs.h>
@@ -67,6 +68,7 @@ extern HANDLE BaseSrvSharedHeap;
 extern PBASE_STATIC_SERVER_DATA BaseStaticServerData;
 extern ULONG SessionId;
 extern ULONG ProtectionMode;
+extern RTL_CRITICAL_SECTION BaseSrvDDDBSMCritSec;
 
 #define SM_REG_KEY \
     L"\\Registry\\Machine\\System\\CurrentControlSet\\Control\\Session Manager"
index d13edb4..83a0124 100644 (file)
 #define NDEBUG
 #include <debug.h>
 
+typedef struct _BSM_REQUEST
+{
+    struct _BSM_REQUEST * Next;
+    LUID BroadcastLuid;
+    LONG DriveLetter;
+    LONG RemoveDefinition;
+} BSM_REQUEST, *PBSM_REQUEST;
+
 /* GLOBALS ********************************************************************/
 
 static RTL_CRITICAL_SECTION BaseDefineDosDeviceCritSec;
+RTL_CRITICAL_SECTION BaseSrvDDDBSMCritSec;
+PBSM_REQUEST BSM_Request_Queue = NULL, BSM_Request_Queue_End = NULL;
+ULONG BaseSrvpBSMThreadCount = 0;
+LONG (WINAPI *PBROADCASTSYSTEMMESSAGEEXW)(DWORD, LPDWORD, UINT, WPARAM, LPARAM, PBSMINFO) = NULL;
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
@@ -146,6 +158,317 @@ IsGlobalSymbolicLink(HANDLE LinkHandle,
     return Status;
 }
 
+BOOLEAN
+CheckForGlobalDriveLetter(SHORT DriveLetter)
+{
+    WCHAR Path[8];
+    NTSTATUS Status;
+    BOOLEAN IsGlobal;
+    UNICODE_STRING PathU;
+    HANDLE SymbolicLinkHandle;
+    OBJECT_ATTRIBUTES ObjectAttributes;
+
+    /* Setup our drive path */
+    wcsncpy(Path, L"\\??\\X:", (sizeof(L"\\??\\X:") / sizeof(WCHAR)));
+    Path[4] = DriveLetter + L'A';
+    Path[6] = UNICODE_NULL;
+
+    /* Prepare everything to open the link */
+    RtlInitUnicodeString(&PathU, Path);
+    InitializeObjectAttributes(&ObjectAttributes,
+                               &PathU,
+                               OBJ_CASE_INSENSITIVE,
+                               NULL,
+                               NULL);
+
+    /* Impersonate the caller */
+    if (!CsrImpersonateClient(NULL))
+    {
+        return FALSE;
+    }
+
+    /* Open our drive letter */
+    Status = NtOpenSymbolicLinkObject(&SymbolicLinkHandle,
+                                      SYMBOLIC_LINK_QUERY,
+                                      &ObjectAttributes);
+
+    CsrRevertToSelf();
+
+    if (!NT_SUCCESS(Status))
+    {
+        return FALSE;
+    }
+
+    /* Check whether it's global */
+    Status = IsGlobalSymbolicLink(SymbolicLinkHandle, &IsGlobal);
+    NtClose(SymbolicLinkHandle);
+
+    if (!NT_SUCCESS(Status))
+    {
+        return FALSE;
+    }
+
+    return IsGlobal;
+}
+
+NTSTATUS
+SendWinStationBSM(DWORD Flags,
+                  LPDWORD Recipients,
+                  UINT Message,
+                  WPARAM wParam,
+                  LPARAM lParam)
+{
+    UNIMPLEMENTED;
+    return STATUS_NOT_IMPLEMENTED;
+}
+
+NTSTATUS
+BroadcastDriveLetterChange(LONG DriveLetter,
+                           BOOLEAN RemoveDefinition,
+                           PLUID BroadcastLuid)
+{
+    HANDLE hUser32;
+    NTSTATUS Status;
+    UNICODE_STRING User32U;
+    ANSI_STRING ProcedureName;
+    DWORD Recipients, Flags, wParam;
+    LUID SystemLuid = SYSTEM_LUID;
+    BSMINFO Info;
+    DEV_BROADCAST_VOLUME Volume;
+
+    /* We need a broadcast LUID */
+    if (BroadcastLuid == NULL)
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /* Get the Csr procedure, and keep it forever */
+    if (PBROADCASTSYSTEMMESSAGEEXW == NULL)
+    {
+        hUser32 = NULL;
+        RtlInitUnicodeString(&User32U, L"user32");
+        Status = LdrGetDllHandle(NULL, NULL, &User32U, &hUser32);
+        if (hUser32 != NULL && NT_SUCCESS(Status))
+        {
+            RtlInitString(&ProcedureName, "CsrBroadcastSystemMessageExW");
+            Status = LdrGetProcedureAddress(hUser32,
+                                            &ProcedureName,
+                                            0,
+                                            (PVOID *)&PBROADCASTSYSTEMMESSAGEEXW);
+            if (!NT_SUCCESS(Status))
+            {
+                PBROADCASTSYSTEMMESSAGEEXW = NULL;
+            }
+        }
+
+        /* If we failed to get broadcast procedure, no more actions left */
+        if (PBROADCASTSYSTEMMESSAGEEXW == NULL)
+        {
+            return Status;
+        }
+    }
+
+    /* Initialize broadcast info */
+    Info.cbSize = sizeof(BSMINFO);
+    Info.hdesk = 0;
+    Info.hwnd = 0;
+    RtlCopyLuid(&Info.luid, BroadcastLuid);
+
+    /* Initialize volume information */
+    Volume.dbcv_size = sizeof(DEV_BROADCAST_VOLUME);
+    Volume.dbcv_devicetype = DBT_DEVTYP_VOLUME;
+    Volume.dbcv_reserved = 0;
+    Volume.dbcv_unitmask = 1 << DriveLetter;
+    Volume.dbcv_flags = DBTF_NET;
+
+    /* Wide broadcast */
+    Recipients = BSM_APPLICATIONS | BSM_ALLDESKTOPS;
+    Flags = BSF_NOHANG | BSF_NOTIMEOUTIFNOTHUNG | BSF_FORCEIFHUNG;
+
+    /*
+     * If we don't broadcast as system, it's not a global drive
+     * notification, then mark it as LUID mapped drive
+     */
+    if (!RtlEqualLuid(&Info.luid, &SystemLuid))
+    {
+        Flags |= BSF_LUID;
+    }
+
+    /* Set event type */
+    wParam = RemoveDefinition ? DBT_DEVICEREMOVECOMPLETE : DBT_DEVICEARRIVAL;
+
+    /* And broadcast! */
+    Status = PBROADCASTSYSTEMMESSAGEEXW(Flags, &Recipients, WM_DEVICECHANGE, wParam, (LPARAM)&Volume, &Info);
+
+    /* If the drive is global, notify Winsta */
+    if (!(Flags & BSF_LUID))
+    {
+        Status = SendWinStationBSM(Flags, &Recipients, WM_DEVICECHANGE, wParam, (LPARAM)&Volume);
+    }
+
+    return Status;
+}
+
+ULONG
+NTAPI
+BaseSrvBSMThread(PVOID StartupContext)
+{
+    ULONG ExitStatus;
+    NTSTATUS Status;
+    PBSM_REQUEST CurrentRequest;
+
+    /* We have a thread */
+    ExitStatus = 0;
+    RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec);
+    ++BaseSrvpBSMThreadCount;
+
+    while (TRUE)
+    {
+        /* If we flushed the queue, job done */
+        if (BSM_Request_Queue == NULL)
+        {
+            break;
+        }
+
+        /* Queue current request, and remove it from the queue */
+        CurrentRequest = BSM_Request_Queue;
+        BSM_Request_Queue = BSM_Request_Queue->Next;
+
+        /* If that was the last request, NULLify queue end */
+        if (BSM_Request_Queue == NULL)
+        {
+            BSM_Request_Queue_End = NULL;
+        }
+
+        RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
+
+        /* Broadcast the message */
+        Status = BroadcastDriveLetterChange(CurrentRequest->DriveLetter,
+                                            CurrentRequest->RemoveDefinition,
+                                            &CurrentRequest->BroadcastLuid);
+
+        /* Reflect the last entry status on stop */
+        CurrentRequest->Next = NULL;
+        ExitStatus = Status;
+
+        RtlFreeHeap(BaseSrvHeap, 0, CurrentRequest);
+        RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec);
+    }
+
+    /* Here, we've flushed the queue, quit the user thread */
+    --BaseSrvpBSMThreadCount;
+    RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
+
+    NtCurrentTeb()->FreeStackOnTermination = TRUE;
+    NtTerminateThread(NtCurrentThread(), ExitStatus);
+
+    return ExitStatus;
+}
+
+NTSTATUS
+CreateBSMThread(VOID)
+{
+    /* This can only be true for LUID mappings */
+    if (BaseStaticServerData->LUIDDeviceMapsEnabled == 0)
+    {
+        return STATUS_ACCESS_DENIED;
+    }
+
+    /* Create our user thread */
+    return RtlCreateUserThread(NtCurrentProcess(),
+                               NULL,
+                               FALSE,
+                               0,
+                               0,
+                               0,
+                               BaseSrvBSMThread,
+                               NULL,
+                               NULL,
+                               NULL);
+}
+
+NTSTATUS
+AddBSMRequest(LONG DriveLetter,
+              BOOLEAN RemoveDefinition,
+              PLUID BroadcastLuid)
+{
+    LUID CallerLuid;
+    NTSTATUS Status;
+    LUID SystemLuid = SYSTEM_LUID;
+    PBSM_REQUEST Request;
+
+    /* We need a broadcast LUID */
+    if (BroadcastLuid == NULL)
+    {
+        return STATUS_INVALID_PARAMETER;
+    }
+
+    /*
+     * If LUID mappings are not enabled, this call makes no sense
+     * It should not happen though
+     */
+    if (BaseStaticServerData->LUIDDeviceMapsEnabled == 0)
+    {
+        return STATUS_ACCESS_DENIED;
+    }
+
+    /* Get our caller LUID (not the broadcaster!) */
+    Status = GetCallerLuid(&CallerLuid);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }
+
+    /* System cannot create LUID mapped drives - thus broadcast makes no sense */
+    if (!RtlEqualLuid(&CallerLuid, &SystemLuid))
+    {
+        return STATUS_ACCESS_DENIED;
+    }
+
+    /* Allocate our request */
+    Request = RtlAllocateHeap(BaseSrvHeap, 0, sizeof(BSM_REQUEST));
+    if (Request == NULL)
+    {
+        return STATUS_NO_MEMORY;
+    }
+
+    /* Initialize it */
+    Request->DriveLetter = DriveLetter;
+    Request->RemoveDefinition = RemoveDefinition;
+    RtlCopyLuid(&Request->BroadcastLuid, BroadcastLuid);
+    Request->Next = NULL;
+
+    /* And queue it */
+    RtlEnterCriticalSection(&BaseSrvDDDBSMCritSec);
+
+    /* At the end of the queue if not empty */
+    if (BSM_Request_Queue_End != NULL)
+    {
+        BSM_Request_Queue_End->Next = Request;
+    }
+    /* Otherwise, initialize the queue */
+    else
+    {
+        BSM_Request_Queue = Request;
+    }
+
+    /* We're in FIFO mode */
+    BSM_Request_Queue_End = Request;
+
+    /* If we don't have a messaging thread running, then start one */
+    if (BaseSrvpBSMThreadCount >= 1)
+    {
+        RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
+    }
+    else
+    {
+        RtlLeaveCriticalSection(&BaseSrvDDDBSMCritSec);
+        Status = CreateBSMThread();
+    }
+
+    return Status;
+}
+
 /* PUBLIC SERVER APIS *********************************************************/
 
 CSR_API(BaseSrvDefineDosDevice)
@@ -167,7 +490,7 @@ CSR_API(BaseSrvDefineDosDevice)
     BOOLEAN DriveLetter = FALSE;
     BOOLEAN RemoveDefinition;
     BOOLEAN HandleTarget;
-    BOOLEAN HandleSMB = FALSE;
+    BOOLEAN Broadcast = FALSE;
     BOOLEAN IsGlobal = FALSE;
     ULONG CchLengthLeft;
     ULONG CchLength;
@@ -297,13 +620,19 @@ CSR_API(BaseSrvDefineDosDevice)
             _SEH2_LEAVE;
         }
 
-        /* While impersonating the caller, also get its LUID */
+        /*
+         * While impersonating the caller, also get its LUID.
+         * This is mandatory in case we have a driver letter,
+         * Because we're in the case we've got LUID mapping
+         * enabled and broadcasting enabled. LUID will be required
+         * for the latter
+         */
         if (DriveLetter)
         {
             Status = GetCallerLuid(&CallerLuid);
             if (NT_SUCCESS(Status))
             {
-                HandleSMB = TRUE;
+                Broadcast = TRUE;
             }
         }
 
@@ -744,10 +1073,31 @@ CSR_API(BaseSrvDefineDosDevice)
         /* Free our internal buffer */
         RtlFreeHeap(BaseSrvHeap, 0, lpBuffer);
 
-        /* Handle SMB */
-        if (DriveLetter && Status == STATUS_SUCCESS && HandleSMB)
+        /* Broadcast drive letter creation */
+        if (DriveLetter && Status == STATUS_SUCCESS && Broadcast)
         {
-            UNIMPLEMENTED;
+            LUID SystemLuid = SYSTEM_LUID;
+
+            /* If that's a global drive, broadcast as system */
+            if (IsGlobal)
+            {
+                RtlCopyLuid(&CallerLuid, &SystemLuid);
+            }
+
+            /* Broadcast the event */
+            AddBSMRequest(AbsLetter, RemoveDefinition, &CallerLuid);
+
+            /*
+             * If we removed drive, and the drive was shadowing a global one
+             * broadcast the arrival of the global drive (as system - global)
+             */
+            if (RemoveDefinition && !RtlEqualLuid(&CallerLuid, &SystemLuid))
+            {
+                if (CheckForGlobalDriveLetter(AbsLetter))
+                {
+                    AddBSMRequest(AbsLetter, FALSE, &CallerLuid);
+                }
+            }
         }
 
         /* Done! */
index a1bf0f8..0768f69 100644 (file)
@@ -580,7 +580,10 @@ BaseInitializeStaticServerData(IN PCSR_SERVER_DLL LoadedServerDll)
                                        NULL);
     ASSERT(NT_SUCCESS(Status));
     BaseStaticServerData->LUIDDeviceMapsEnabled = (BOOLEAN)LuidEnabled;
-    if (!BaseStaticServerData->LUIDDeviceMapsEnabled)
+
+    /* Initialize Global */
+    if (!BaseStaticServerData->LUIDDeviceMapsEnabled ||
+        NT_SUCCESS(RtlInitializeCriticalSectionAndSpinCount(&BaseSrvDDDBSMCritSec, 0x80000000)))
     {
         /* Make Global point back to BNO */
         RtlInitUnicodeString(&DirectoryName, L"Global");
@@ -638,6 +641,11 @@ BaseInitializeStaticServerData(IN PCSR_SERVER_DLL LoadedServerDll)
                                          &ObjectAttributes);
         ASSERT(NT_SUCCESS(Status));
     }
+    else
+    {
+        /* That should never happen */
+        ASSERT(FALSE);
+    }
 
     /* Initialize NLS */
     BaseSrvNLSInit(BaseStaticServerData);