[LOCALMON]
authorColin Finck <colin@reactos.org>
Mon, 6 Jul 2015 12:41:06 +0000 (12:41 +0000)
committerColin Finck <colin@reactos.org>
Mon, 6 Jul 2015 12:41:06 +0000 (12:41 +0000)
Implement a Local Port Monitor for COM, FILE: and LPT ports, usable as a drop-in replacement for the Windows original.
Fully implements opening, enumerating and closing ports, starting and ending documents as well as reading and writing to ports along with Xcv data transfer between Port Monitor and Port Monitor UI.
Does not support IrDA printers unlike the Windows original, sorry guys :-P

The Windows Local Port Monitor is partly documented in an ancient DDK sample.
Additional information was gathered through API monitoring.

TODO:
- AddPort, DeletePort and PortIsValid Xcv functions need to be implemented.
- A real privilege check needs to be added to LocalmonXcvOpenPort.
- TESTING TESTING TESTING (under Windows) and fixing bugs.

To test it under Windows:
- Copy the compiled "localmon.dll" to the system32 directory.
- Open regedit.exe and move to HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\Local Port.
- Replace "localspl.dll" by "localmon.dll" for the "Driver" value.

[SPOOLSS]
- Properly stub EnumPortsW to let localmon compile.

svn path=/branches/colins-printing-for-freedom/; revision=68356

13 files changed:
reactos/win32ss/printing/base/spoolss/CMakeLists.txt
reactos/win32ss/printing/base/spoolss/ports.c [new file with mode: 0644]
reactos/win32ss/printing/base/spoolss/spoolss.spec
reactos/win32ss/printing/monitors/localmon/CMakeLists.txt
reactos/win32ss/printing/monitors/localmon/lang/de-DE.rc [new file with mode: 0644]
reactos/win32ss/printing/monitors/localmon/lang/en-US.rc [new file with mode: 0644]
reactos/win32ss/printing/monitors/localmon/localmon.rc
reactos/win32ss/printing/monitors/localmon/main.c
reactos/win32ss/printing/monitors/localmon/ports.c [new file with mode: 0644]
reactos/win32ss/printing/monitors/localmon/precomp.h
reactos/win32ss/printing/monitors/localmon/resource.h [new file with mode: 0644]
reactos/win32ss/printing/monitors/localmon/tools.c [new file with mode: 0644]
reactos/win32ss/printing/monitors/localmon/xcv.c [new file with mode: 0644]

index b876773..fe1d628 100644 (file)
@@ -6,6 +6,7 @@ list(APPEND SOURCE
     jobs.c
     main.c
     memory.c
+    ports.c
     precomp.h
     printers.c
     printprocessors.c
diff --git a/reactos/win32ss/printing/base/spoolss/ports.c b/reactos/win32ss/printing/base/spoolss/ports.c
new file mode 100644 (file)
index 0000000..6fedbaa
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * PROJECT:     ReactOS Spooler Router
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Functions related to ports
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+BOOL WINAPI
+EnumPortsW(PWSTR pName, DWORD Level, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
+{
+    return FALSE;
+}
index 352ff69..c4bbac2 100644 (file)
@@ -62,7 +62,7 @@
 @ stdcall EnumJobsW(long long long long ptr long ptr ptr)
 @ stub EnumMonitorsW
 @ stub EnumPerMachineConnectionsW
-@ stub EnumPortsW
+@ stdcall EnumPortsW(ptr long ptr long ptr ptr)
 @ stub EnumPrinterDataExW
 @ stub EnumPrinterDataW
 @ stub EnumPrinterDriversW
index 88057dd..dc83cc0 100644 (file)
@@ -1,20 +1,22 @@
 
 add_subdirectory(ui)
 
-spec2def(localmon localmon.spec ADD_IMPORTLIB)
+spec2def(localmon.dll localmon.spec ADD_IMPORTLIB)
 
 list(APPEND SOURCE
     main.c
-    precomp.h)
+    ports.c
+    precomp.h
+    tools.c
+    xcv.c)
 
 add_library(localmon SHARED
     ${SOURCE}
     localmon.rc
-    ${CMAKE_CURRENT_BINARY_DIR}/localmon_stubs.c
     ${CMAKE_CURRENT_BINARY_DIR}/localmon.def)
 
 set_module_type(localmon win32dll UNICODE)
 target_link_libraries(localmon wine)
-add_importlibs(localmon kernel32 msvcrt ntdll)
+add_importlibs(localmon advapi32 spoolss user32 msvcrt kernel32 ntdll)
 add_pch(localmon precomp.h SOURCE)
 add_cd_file(TARGET localmon DESTINATION reactos/system32 FOR all)
diff --git a/reactos/win32ss/printing/monitors/localmon/lang/de-DE.rc b/reactos/win32ss/printing/monitors/localmon/lang/de-DE.rc
new file mode 100644 (file)
index 0000000..9432798
--- /dev/null
@@ -0,0 +1,7 @@
+LANGUAGE LANG_GERMAN, SUBLANG_NEUTRAL
+
+STRINGTABLE
+BEGIN
+    IDS_LOCAL_PORT "Lokaler Anschluss"
+    IDS_LOCAL_MONITOR "Lokaler Monitor"
+END
diff --git a/reactos/win32ss/printing/monitors/localmon/lang/en-US.rc b/reactos/win32ss/printing/monitors/localmon/lang/en-US.rc
new file mode 100644 (file)
index 0000000..191450d
--- /dev/null
@@ -0,0 +1,7 @@
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+STRINGTABLE
+BEGIN
+    IDS_LOCAL_PORT "Local Port"
+    IDS_LOCAL_MONITOR "Local Monitor"
+END
index 75a0a32..72a3837 100644 (file)
@@ -1,5 +1,26 @@
+/*
+ * PROJECT:     ReactOS Local Port Monitor
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Resource file
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include <windef.h>
+#include "resource.h"
+
+/* Version Information */
 #define REACTOS_VERSION_DLL
-#define REACTOS_STR_FILE_DESCRIPTION  "ReactOS Local Spooler Port Monitor"
+#define REACTOS_STR_FILE_DESCRIPTION  "ReactOS Local Port Monitor"
 #define REACTOS_STR_INTERNAL_NAME     "localmon"
 #define REACTOS_STR_ORIGINAL_FILENAME "localmon.dll"
 #include <reactos/version.rc>
+
+/* UTF-8 */
+#pragma code_page(65001)
+
+#ifdef LANGUAGE_DE_DE
+    #include "lang/de-DE.rc"
+#endif
+#ifdef LANGUAGE_EN_US
+    #include "lang/en-US.rc"
+#endif
index a66d9f3..15c0bd3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * PROJECT:     ReactOS Local Spooler Port Monitor
+ * PROJECT:     ReactOS Local Port Monitor
  * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
  * PURPOSE:     Main functions
  * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
@@ -7,8 +7,170 @@
 
 #include "precomp.h"
 
-LPMONITOR2 WINAPI
+// Global Variables
+DWORD cbLocalMonitor;
+DWORD cbLocalPort;
+PCWSTR pwszLocalMonitor;
+PCWSTR pwszLocalPort;
+
+// Local Constants
+static MONITOR2 _MonitorFunctions = {
+    sizeof(MONITOR2),               // cbSize
+    LocalmonEnumPorts,              // pfnEnumPorts
+    LocalmonOpenPort,               // pfnOpenPort
+    NULL,                           // pfnOpenPortEx
+    LocalmonStartDocPort,           // pfnStartDocPort
+    LocalmonWritePort,              // pfnWritePort
+    LocalmonReadPort,               // pfnReadPort
+    LocalmonEndDocPort,             // pfnEndDocPort
+    LocalmonClosePort,              // pfnClosePort
+    NULL,                           // pfnAddPort
+    NULL,                           // pfnAddPortEx
+    NULL,                           // pfnConfigurePort
+    NULL,                           // pfnDeletePort
+    LocalmonGetPrinterDataFromPort, // pfnGetPrinterDataFromPort
+    LocalmonSetPortTimeOuts,        // pfnSetPortTimeOuts
+    LocalmonXcvOpenPort,            // pfnXcvOpenPort
+    LocalmonXcvDataPort,            // pfnXcvDataPort
+    LocalmonXcvClosePort,           // pfnXcvClosePort
+    LocalmonShutdown,               // pfnShutdown
+    NULL,                           // pfnSendRecvBidiDataFromPort
+};
+
+
+static void
+_LoadResources(HINSTANCE hinstDLL)
+{
+    LoadStringW(hinstDLL, IDS_LOCAL_MONITOR, (PWSTR)&pwszLocalMonitor, 0);
+    cbLocalMonitor = (wcslen(pwszLocalMonitor) + 1) * sizeof(WCHAR);
+
+    LoadStringW(hinstDLL, IDS_LOCAL_PORT, (PWSTR)&pwszLocalPort, 0);
+    cbLocalPort = (wcslen(pwszLocalPort) + 1) * sizeof(WCHAR);
+}
+
+BOOL WINAPI
+DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
+{
+    switch (fdwReason)
+    {
+        case DLL_PROCESS_ATTACH:
+            DisableThreadLibraryCalls(hinstDLL);
+            _LoadResources(hinstDLL);
+            break;
+    }
+
+    return TRUE;
+}
+
+void WINAPI
+LocalmonShutdown(HANDLE hMonitor)
+{
+    PLOCALMON_HANDLE pLocalmon;
+    PLOCALMON_PORT pPort;
+
+    pLocalmon = (PLOCALMON_HANDLE)hMonitor;
+
+    // Close all virtual file ports.
+    while (!IsListEmpty(&pLocalmon->FilePorts))
+    {
+        pPort = CONTAINING_RECORD(&pLocalmon->FilePorts.Flink, LOCALMON_PORT, Entry);
+        LocalmonClosePort((HANDLE)pPort);
+    }
+
+    // Now close all regular ports and remove them from the list.
+    while (!IsListEmpty(&pLocalmon->Ports))
+    {
+        pPort = CONTAINING_RECORD(&pLocalmon->Ports.Flink, LOCALMON_PORT, Entry);
+        RemoveEntryList(&pPort->Entry);
+        LocalmonClosePort((HANDLE)pPort);
+    }
+
+    // Finally free the memory for the LOCALMON_HANDLE structure itself.
+    DllFreeSplMem(pLocalmon);
+}
+
+PMONITOR2 WINAPI
 InitializePrintMonitor2(PMONITORINIT pMonitorInit, PHANDLE phMonitor)
 {
-    return NULL;
+    DWORD cchMaxPortName;
+    DWORD cchPortName;
+    DWORD dwErrorCode;
+    DWORD dwPortCount;
+    DWORD i;
+    HKEY hKey;
+    PMONITOR2 pReturnValue = NULL;
+    PLOCALMON_HANDLE pLocalmon;
+    PLOCALMON_PORT pPort = NULL;
+
+    // Create a new LOCALMON_HANDLE structure.
+    pLocalmon = DllAllocSplMem(sizeof(LOCALMON_HANDLE));
+    InitializeListHead(&pLocalmon->FilePorts);
+    InitializeListHead(&pLocalmon->Ports);
+
+    // The Local Spooler Port Monitor doesn't need to care about the given registry key and functions.
+    // Instead it uses a well-known registry key for getting its information about local ports. Open this one.
+    dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", 0, KEY_READ, &hKey);
+    if (dwErrorCode != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // Get the number of ports and the length of the largest port name.
+    dwErrorCode = (DWORD)RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &dwPortCount, &cchMaxPortName, NULL, NULL, NULL);
+    if (dwErrorCode != ERROR_SUCCESS)
+    {
+        ERR("RegQueryInfoKeyW failed with status %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // Loop through all ports.
+    for (i = 0; i < dwPortCount; i++)
+    {
+        // Allocate memory for a new LOCALMON_PORT structure and its name.
+        pPort = DllAllocSplMem(sizeof(LOCALMON_PORT) + (cchMaxPortName + 1) * sizeof(WCHAR));
+        if (!pPort)
+        {
+            dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+            ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+            goto Cleanup;
+        }
+
+        pPort->hFile = INVALID_HANDLE_VALUE;
+        pPort->pwszPortName = (PWSTR)((PBYTE)pPort + sizeof(LOCALMON_PORT));
+
+        // Get the port name.
+        cchPortName = cchMaxPortName + 1;
+        dwErrorCode = (DWORD)RegEnumValueW(hKey, i, pPort->pwszPortName, &cchPortName, NULL, NULL, NULL, NULL);
+        if (dwErrorCode != ERROR_SUCCESS)
+        {
+            ERR("RegEnumValueW failed with status %lu!\n", dwErrorCode);
+            goto Cleanup;
+        }
+
+        // This Port Monitor supports COM, FILE and LPT ports. Skip all others.
+        if (_wcsnicmp(pPort->pwszPortName, L"COM", 3) != 0 && _wcsicmp(pPort->pwszPortName, L"FILE:") != 0 && _wcsnicmp(pPort->pwszPortName, L"LPT", 3) != 0)
+        {
+            DllFreeSplMem(pPort);
+            continue;
+        }
+
+        // Add it to the list.
+        InsertTailList(&pLocalmon->Ports, &pPort->Entry);
+
+        // Don't let the cleanup routine free this.
+        pPort = NULL;
+    }
+
+    // Return our handle and the Print Monitor functions.
+    *phMonitor = (HANDLE)pLocalmon;
+    pReturnValue = &_MonitorFunctions;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    if (pPort)
+        DllFreeSplMem(pPort);
+
+    SetLastError(dwErrorCode);
+    return pReturnValue;
 }
diff --git a/reactos/win32ss/printing/monitors/localmon/ports.c b/reactos/win32ss/printing/monitors/localmon/ports.c
new file mode 100644 (file)
index 0000000..43c634c
--- /dev/null
@@ -0,0 +1,1004 @@
+/*
+ * PROJECT:     ReactOS Local Port Monitor
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Functions related to ports
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+
+// Local Constants
+static const WCHAR wszNonspooledPrefix[] = L"NONSPOOLED_";
+static const DWORD cchNonspooledPrefix = _countof(wszNonspooledPrefix) - 1;
+
+
+/**
+ * @name _GetNonspooledPortName
+ *
+ * Prepends "NONSPOOLED_" to a port name without colon.
+ * You have to free the returned buffer using DllFreeSplMem.
+ *
+ * @param pwszPortNameWithoutColon
+ * Result of a previous GetPortNameWithoutColon call.
+ *
+ * @return
+ * Buffer containing the NONSPOOLED port name or NULL in case of failure.
+ */
+static __inline PWSTR
+_GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon)
+{
+    DWORD cchPortNameWithoutColon;
+    PWSTR pwszNonspooledPortName;
+
+    cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon);
+
+    pwszNonspooledPortName = DllAllocSplMem((cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
+    if (!pwszNonspooledPortName)
+    {
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        return NULL;
+    }
+
+    CopyMemory(pwszNonspooledPortName, wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR));
+    CopyMemory(&pwszNonspooledPortName[cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
+
+    return pwszNonspooledPortName;
+}
+
+/**
+ * @name _ClosePortHandles
+ *
+ * Closes a port of any type if its opened.
+ * Removes any saved mapping or existing definition of a NONSPOOLED device mapping.
+ *
+ * @param pPort
+ * The port you want to close.
+ */
+static void
+_ClosePortHandles(PLOCALMON_PORT pPort)
+{
+    PWSTR pwszNonspooledPortName;
+    PWSTR pwszPortNameWithoutColon;
+
+    // A port is already fully closed if the file handle is invalid.
+    if (pPort->hFile == INVALID_HANDLE_VALUE)
+        return;
+
+    // Close the file handle.
+    CloseHandle(pPort->hFile);
+    pPort->hFile = INVALID_HANDLE_VALUE;
+
+    // A NONSPOOLED port was only created if pwszMapping contains the current port mapping.
+    if (!pPort->pwszMapping)
+        return;
+
+    // Free the information about the current mapping.
+    DllFreeSplStr(pPort->pwszMapping);
+    pPort->pwszMapping = NULL;
+
+    // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port.
+    pwszPortNameWithoutColon = GetPortNameWithoutColon(pPort->pwszPortName);
+    if (pwszPortNameWithoutColon)
+    {
+        pwszNonspooledPortName = _GetNonspooledPortName(pwszPortNameWithoutColon);
+        if (pwszNonspooledPortName)
+        {
+            DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL);
+            DllFreeSplMem(pwszNonspooledPortName);
+        }
+
+        DllFreeSplMem(pwszPortNameWithoutColon);
+    }
+}
+
+/**
+ * @name _CreateNonspooledPort
+ *
+ * Queries the system-wide device definition of the given port.
+ * If such a definition exists, it's a legacy port remapped to a named pipe by the spooler.
+ * In this case, the function creates and opens a NONSPOOLED device definition to the most recent mapping before the current one (usually the physical device).
+ *
+ * @param pPort
+ * Pointer to the LOCALMON_PORT structure of the desired port.
+ *
+ * @return
+ * TRUE if a NONSPOOLED port was successfully created, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed,
+ * because no system-wide device definition is available.
+ */
+static BOOL
+_CreateNonspooledPort(PLOCALMON_PORT pPort)
+{
+    const WCHAR wszLocalSlashes[] = L"\\\\.\\";
+    const DWORD cchLocalSlashes = _countof(wszLocalSlashes) - 1;
+
+    const WCHAR wszSpoolerNamedPipe[] = L"\\Device\\NamedPipe\\Spooler\\";
+    const DWORD cchSpoolerNamedPipe = _countof(wszSpoolerNamedPipe) - 1;
+
+    BOOL bReturnValue = FALSE;
+    DWORD cchPortNameWithoutColon;
+    DWORD dwErrorCode;
+    HANDLE hToken = NULL;
+    PWSTR p;
+    PWSTR pwszDeviceMappings = NULL;
+    PWSTR pwszNonspooledFileName = NULL;
+    PWSTR pwszNonspooledPortName = NULL;
+    PWSTR pwszPipeName = NULL;
+    PWSTR pwszPortNameWithoutColon = NULL;
+
+    // We need the port name without the trailing colon.
+    pwszPortNameWithoutColon = GetPortNameWithoutColon(pPort->pwszPortName);
+    if (!pwszPortNameWithoutColon)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        goto Cleanup;
+    }
+
+    cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon);
+
+    // The spooler has usually remapped the legacy port to a named pipe of the format in wszSpoolerNamedPipe.
+    // Construct the device name of this pipe.
+    pwszPipeName = DllAllocSplMem((cchSpoolerNamedPipe + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
+    if (!pwszPipeName)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    CopyMemory(pwszPipeName, wszSpoolerNamedPipe, cchSpoolerNamedPipe * sizeof(WCHAR));
+    CopyMemory(&pwszPipeName[cchSpoolerNamedPipe], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
+
+    // QueryDosDeviceW is one of the shitty APIs that gives no information about the required buffer size and wants you to know it by pure magic.
+    // Examples show that a value of MAX_PATH * sizeof(WCHAR) is usually taken here, so we have no other option either.
+    pwszDeviceMappings = DllAllocSplMem(MAX_PATH * sizeof(WCHAR));
+    if (!pwszDeviceMappings)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Switch to the SYSTEM context, because we're only interested in creating NONSPOOLED ports for system-wide ports.
+    // User-local ports (like _some_ redirected networked ones) aren't remapped by the spooler and can be opened directly.
+    hToken = RevertToPrinterSelf();
+    if (!hToken)
+    {
+        dwErrorCode = GetLastError();
+        ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // QueryDosDeviceW returns the current mapping and a list of prior mappings of this legacy port, which is managed as a DOS device in the system.
+    if (!QueryDosDeviceW(pwszPortNameWithoutColon, pwszDeviceMappings, MAX_PATH))
+    {
+        // No system-wide port exists, so we also need no NONSPOOLED mapping.
+        dwErrorCode = ERROR_SUCCESS;
+        goto Cleanup;
+    }
+
+    // Check if this port has already been opened by _CreateNonspooledPort previously.
+    if (pPort->pwszMapping)
+    {
+        // In this case, we just need to do something if the mapping has changed.
+        // Therefore, check if the stored mapping equals the current mapping.
+        if (wcscmp(pPort->pwszMapping, pwszDeviceMappings) == 0)
+        {
+            // We don't need to do anything in this case.
+            dwErrorCode = ERROR_SUCCESS;
+            goto Cleanup;
+        }
+        else
+        {
+            // Close the open file handle and free the memory for pwszMapping before remapping.
+            CloseHandle(pPort->hFile);
+            pPort->hFile = INVALID_HANDLE_VALUE;
+
+            DllFreeSplStr(pPort->pwszMapping);
+            pPort->pwszMapping = NULL;
+        }
+    }
+
+    // The port is usually mapped to the named pipe and this is how we received our data for printing.
+    // What we now need for accessing the actual port is the most recent mapping different from the named pipe.
+    p = pwszDeviceMappings;
+
+    for (;;)
+    {
+        if (!*p)
+        {
+            // We reached the end of the list without finding a mapping.
+            ERR("Can't find a suitable mapping for the port \"%S\"!", pPort->pwszPortName);
+            goto Cleanup;
+        }
+
+        if (_wcsicmp(p, pwszPipeName) != 0)
+            break;
+
+        // Advance to the next mapping in the list.
+        p += wcslen(p) + 1;
+    }
+
+    // We now want to create a DOS device "NONSPOOLED_<PortName>" to this mapping, so that we're able to open it through CreateFileW.
+    pwszNonspooledPortName = _GetNonspooledPortName(pwszPortNameWithoutColon);
+    if (!pwszNonspooledPortName)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        goto Cleanup;
+    }
+
+    // Delete a possibly existing NONSPOOLED device before creating the new one, so we don't stack up device definitions.
+    DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL);
+
+    if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH, pwszNonspooledPortName, p))
+    {
+        dwErrorCode = GetLastError();
+        ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // This is all we needed to do in SYSTEM context.
+    ImpersonatePrinterClient(hToken);
+    hToken = NULL;
+
+    // Construct the file name to our created device for CreateFileW.
+    pwszNonspooledFileName = DllAllocSplMem((cchLocalSlashes + cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
+    if (!pwszNonspooledFileName)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    CopyMemory(pwszNonspooledFileName, wszLocalSlashes, cchLocalSlashes * sizeof(WCHAR));
+    CopyMemory(&pwszNonspooledFileName[cchLocalSlashes], wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR));
+    CopyMemory(&pwszNonspooledFileName[cchLocalSlashes + cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
+
+    // Finally open it for reading and writing.
+    pPort->hFile = CreateFileW(pwszNonspooledFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL);
+    if (pPort->hFile == INVALID_HANDLE_VALUE)
+    {
+        dwErrorCode = GetLastError();
+        ERR("CreateFileW failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // Store the current mapping of the port, so that we can check if it has changed.
+    pPort->pwszMapping = AllocSplStr(pwszDeviceMappings);
+    if (!pPort->pwszMapping)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        goto Cleanup;
+    }
+
+    bReturnValue = TRUE;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    if (hToken)
+        ImpersonatePrinterClient(hToken);
+
+    if (pwszDeviceMappings)
+        DllFreeSplMem(pwszDeviceMappings);
+
+    if (pwszNonspooledFileName)
+        DllFreeSplMem(pwszNonspooledFileName);
+
+    if (pwszNonspooledPortName)
+        DllFreeSplMem(pwszNonspooledPortName);
+
+    if (pwszPipeName)
+        DllFreeSplMem(pwszPipeName);
+
+    if (pwszPortNameWithoutColon)
+        DllFreeSplMem(pwszPortNameWithoutColon);
+
+    SetLastError(dwErrorCode);
+    return bReturnValue;
+}
+
+static PLOCALMON_PORT
+_FindPort(PLOCALMON_HANDLE pLocalmon, PCWSTR pwszPortName)
+{
+    PLIST_ENTRY pEntry;
+    PLOCALMON_PORT pPort;
+
+    for (pEntry = pLocalmon->Ports.Flink; pEntry != &pLocalmon->Ports; pEntry = pEntry->Flink)
+    {
+        pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
+
+        if (wcscmp(pPort->pwszPortName, pwszPortName) == 0)
+            return pPort;
+    }
+
+    return NULL;
+}
+
+static DWORD
+_LocalmonEnumPortsLevel1(PLOCALMON_HANDLE pLocalmon, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
+{
+    DWORD cbPortName;
+    DWORD dwErrorCode;
+    DWORD dwPortCount = 0;
+    PBYTE pPortInfo;
+    PBYTE pPortString;
+    PLIST_ENTRY pEntry;
+    PLOCALMON_PORT pPort;
+    PORT_INFO_1W PortInfo1;
+
+    // Count the required buffer size and the number of datatypes.
+    for (pEntry = pLocalmon->Ports.Flink; pEntry != &pLocalmon->Ports; pEntry = pEntry->Flink)
+    {
+        pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
+
+        cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
+        *pcbNeeded += sizeof(PORT_INFO_1W) + cbPortName;
+        dwPortCount++;
+    }
+
+    // Check if the supplied buffer is large enough.
+    if (cbBuf < *pcbNeeded)
+    {
+        dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
+        goto Cleanup;
+    }
+
+    // Put the strings right after the last PORT_INFO_1W structure.
+    pPortInfo = pPorts;
+    pPortString = pPorts + dwPortCount * sizeof(PORT_INFO_1W);
+
+    // Copy over all ports.
+    for (pEntry = pLocalmon->Ports.Flink; pEntry != &pLocalmon->Ports; pEntry = pEntry->Flink)
+    {
+        pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
+
+        // Copy the port name.
+        PortInfo1.pName = (PWSTR)pPortString;
+        cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
+        CopyMemory(pPortString, pPort->pwszPortName, cbPortName);
+        pPortString += cbPortName;
+
+        // Copy the structure and advance to the next one in the output buffer.
+        CopyMemory(pPortInfo, &PortInfo1, sizeof(PORT_INFO_1W));
+        pPortInfo += sizeof(PORT_INFO_1W);
+    }
+
+    *pcReturned = dwPortCount;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    return dwErrorCode;
+}
+
+static DWORD
+_LocalmonEnumPortsLevel2(PLOCALMON_HANDLE pLocalmon, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
+{
+    DWORD cbPortName;
+    DWORD dwErrorCode;
+    DWORD dwPortCount = 0;
+    PBYTE pPortInfo;
+    PBYTE pPortString;
+    PLIST_ENTRY pEntry;
+    PLOCALMON_PORT pPort;
+    PORT_INFO_2W PortInfo2;
+
+    // Count the required buffer size and the number of datatypes.
+    for (pEntry = pLocalmon->Ports.Flink; pEntry != &pLocalmon->Ports; pEntry = pEntry->Flink)
+    {
+        pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
+
+        cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
+        *pcbNeeded += sizeof(PORT_INFO_2W) + cbPortName + cbLocalMonitor + cbLocalPort;
+        dwPortCount++;
+    }
+
+    // Check if the supplied buffer is large enough.
+    if (cbBuf < *pcbNeeded)
+    {
+        dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
+        goto Cleanup;
+    }
+
+    // Put the strings right after the last PORT_INFO_2W structure.
+    pPortInfo = pPorts;
+    pPortString = pPorts + dwPortCount * sizeof(PORT_INFO_2W);
+
+    // Copy over all ports.
+    for (pEntry = pLocalmon->Ports.Flink; pEntry != &pLocalmon->Ports; pEntry = pEntry->Flink)
+    {
+        pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
+
+        // All local ports are writable and readable.
+        PortInfo2.fPortType = PORT_TYPE_WRITE | PORT_TYPE_READ;
+        PortInfo2.Reserved = 0;
+
+        // Copy the port name.
+        PortInfo2.pPortName = (PWSTR)pPortString;
+        cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
+        CopyMemory(pPortString, pPort->pwszPortName, cbPortName);
+        pPortString += cbPortName;
+
+        // Copy the monitor name.
+        PortInfo2.pMonitorName = (PWSTR)pPortString;
+        CopyMemory(pPortString, pwszLocalMonitor, cbLocalMonitor);
+        pPortString += cbLocalMonitor;
+
+        // Copy the description.
+        PortInfo2.pDescription = (PWSTR)pPortString;
+        CopyMemory(pPortString, pwszLocalPort, cbLocalPort);
+        pPortString += cbLocalPort;
+
+        // Copy the structure and advance to the next one in the output buffer.
+        CopyMemory(pPortInfo, &PortInfo2, sizeof(PORT_INFO_2W));
+        pPortInfo += sizeof(PORT_INFO_2W);
+    }
+
+    *pcReturned = dwPortCount;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    return dwErrorCode;
+}
+
+/**
+ * @name _SetTransmissionRetryTimeout
+ *
+ * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry.
+ *
+ * @param pPort
+ * The port to operate on.
+ *
+ * @return
+ * TRUE if the given port is a physical one, FALSE otherwise.
+ */
+static BOOL
+_SetTransmissionRetryTimeout(PLOCALMON_PORT pPort)
+{
+    COMMTIMEOUTS CommTimeouts;
+
+    // Get the timeout from the port.
+    if (!GetCommTimeouts(pPort->hFile, &CommTimeouts))
+        return FALSE;
+
+    // Set the timeout using the value from registry.
+    CommTimeouts.WriteTotalTimeoutConstant = GetLPTTransmissionRetryTimeout() * 1000;
+    SetCommTimeouts(pPort->hFile, &CommTimeouts);
+
+    return TRUE;
+}
+
+BOOL WINAPI
+LocalmonClosePort(HANDLE hPort)
+{
+    PLOCALMON_PORT pPort;
+
+    // Sanity checks
+    if (!hPort)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    pPort = (PLOCALMON_PORT)hPort;
+
+    if (pPort->PortType == PortType_FILE)
+    {
+        // Remove it from the list of virtual file ports.
+        RemoveEntryList(&pPort->Entry);
+    }
+
+    // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port.
+    _ClosePortHandles(pPort);
+
+    // Close any open printer handle.
+    if (pPort->hPrinter)
+        ClosePrinter(pPort->hPrinter);
+
+    // Finally free the memory for the port itself.
+    DllFreeSplMem(pPort);
+
+    SetLastError(ERROR_SUCCESS);
+    return TRUE;
+}
+
+BOOL WINAPI
+LocalmonEndDocPort(HANDLE hPort)
+{
+    PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
+
+    // Sanity checks
+    if (!pPort)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    // Ending a document requires starting it first :-P
+    if (pPort->bStartedDoc)
+    {
+        // Close all ports opened in StartDocPort.
+        // That is, all but physical LPT ports (opened in OpenPort).
+        if (pPort->PortType != PortType_PhysicalLPT)
+            _ClosePortHandles(pPort);
+
+        // Report our progress.
+        SetJobW(pPort->hPrinter, pPort->dwJobID, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);
+
+        // We're done with the printer.
+        ClosePrinter(pPort->hPrinter);
+        pPort->hPrinter = NULL;
+    }
+
+    SetLastError(ERROR_SUCCESS);
+    return TRUE;
+}
+
+BOOL WINAPI
+LocalmonEnumPorts(HANDLE hMonitor, PWSTR pName, DWORD Level, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
+{
+    DWORD dwErrorCode;
+
+    // Sanity checks
+    if (!hMonitor || (cbBuf && !pPorts) || !pcbNeeded || !pcReturned)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    if (Level > 2)
+    {
+        dwErrorCode = ERROR_INVALID_LEVEL;
+        goto Cleanup;
+    }
+
+    // Begin counting.
+    *pcbNeeded = 0;
+    *pcReturned = 0;
+
+    // The function behaves differently for each level.
+    if (Level == 1)
+        dwErrorCode = _LocalmonEnumPortsLevel1((PLOCALMON_HANDLE)hMonitor, pPorts, cbBuf, pcbNeeded, pcReturned);
+    else if (Level == 2)
+        dwErrorCode = _LocalmonEnumPortsLevel2((PLOCALMON_HANDLE)hMonitor, pPorts, cbBuf, pcbNeeded, pcReturned);
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+/*
+ * @name LocalmonSetPortTimeOuts
+ *
+ * Performs a DeviceIoControl call for the given port.
+ *
+ * @param hPort
+ * The port to operate on.
+ *
+ * @param ControlID
+ * The dwIoControlCode passed to DeviceIoControl. Must not be zero!
+ *
+ * @param pValueName
+ * This parameter is ignored.
+ *
+ * @param lpInBuffer
+ * The lpInBuffer passed to DeviceIoControl.
+ *
+ * @param cbInBuffer
+ * The nInBufferSize passed to DeviceIoControl.
+ *
+ * @param lpOutBuffer
+ * The lpOutBuffer passed to DeviceIoControl.
+ *
+ * @param cbOutBuffer
+ * The nOutBufferSize passed to DeviceIoControl.
+ *
+ * @param lpcbReturned
+ * The lpBytesReturned passed to DeviceIoControl. Must not be zero!
+ *
+ * @return
+ * TRUE if the DeviceIoControl call was successful, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ */
+BOOL WINAPI
+LocalmonGetPrinterDataFromPort(HANDLE hPort, DWORD ControlID, PWSTR pValueName, PWSTR lpInBuffer, DWORD cbInBuffer, PWSTR lpOutBuffer, DWORD cbOutBuffer, PDWORD lpcbReturned)
+{
+    BOOL bOpenedPort = FALSE;
+    DWORD dwErrorCode;
+    PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
+
+    // Sanity checks
+    if (!pPort || !ControlID || !lpcbReturned)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // If this is a serial port, a temporary file handle may be opened.
+    if (pPort->PortType == PortType_PhysicalCOM)
+    {
+        if (_CreateNonspooledPort(pPort))
+        {
+            bOpenedPort = TRUE;
+        }
+        else if (GetLastError() != ERROR_SUCCESS)
+        {
+            dwErrorCode = GetLastError();
+            goto Cleanup;
+        }
+    }
+    else if (pPort->hFile == INVALID_HANDLE_VALUE)
+    {
+        // All other port types need to be opened already.
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Pass the parameters to DeviceIoControl.
+    if (!DeviceIoControl(pPort->hFile, ControlID, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned, NULL))
+    {
+        dwErrorCode = GetLastError();
+        ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    if (bOpenedPort)
+        _ClosePortHandles(pPort);
+
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+BOOL WINAPI
+LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle)
+{
+    DWORD dwErrorCode;
+    PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
+    PLOCALMON_PORT pPort;
+
+    // Sanity checks
+    if (!pLocalmon || !pName || !pHandle)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Check if this is a FILE: port.
+    if (_wcsicmp(pName, L"FILE:") == 0)
+    {
+        // For FILE:, we create a virtual port for each request.
+        pPort = DllAllocSplMem(sizeof(LOCALMON_PORT));
+        pPort->PortType = PortType_FILE;
+        pPort->hFile = INVALID_HANDLE_VALUE;
+
+        // Add it to the list of file ports.
+        InsertTailList(&pLocalmon->FilePorts, &pPort->Entry);
+    }
+    else
+    {
+        // Check if the port name is valid.
+        pPort = _FindPort(pLocalmon, pName);
+        if (!pPort)
+        {
+            dwErrorCode = ERROR_UNKNOWN_PORT;
+            goto Cleanup;
+        }
+
+        // Even if this API is called OpenPort, port file handles aren't always opened here :-P
+        // Windows only does this for physical LPT ports here to enable bidirectional communication with the printer outside of jobs (using ReadPort and WritePort).
+        // The others are only opened per job in StartDocPort.
+        if (_wcsnicmp(pName, L"LPT", 3) == 0)
+        {
+            // Try to create a NONSPOOLED port and open it.
+            if (_CreateNonspooledPort(pPort))
+            {
+                // Set the transmission retry timeout for the ReadPort and WritePort calls.
+                // This also checks if this port is a physical one.
+                if (_SetTransmissionRetryTimeout(pPort))
+                {
+                    // This is definitely a physical LPT port!
+                    pPort->PortType = PortType_PhysicalLPT;
+                }
+                else
+                {
+                    // This is no physical port, so don't keep its handle open all the time.
+                    _ClosePortHandles(pPort);
+                    pPort->PortType = PortType_OtherLPT;
+                }
+            }
+            else if (GetLastError() != ERROR_SUCCESS)
+            {
+                dwErrorCode = GetLastError();
+                goto Cleanup;
+            }
+        }
+        else
+        {
+            // This can only be a COM port.
+            pPort->PortType = PortType_PhysicalCOM;
+        }
+    }
+
+    // Return our fetched LOCALMON_PORT structure in the handle.
+    *pHandle = (PHANDLE)pPort;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+/*
+ * @name LocalmonSetPortTimeOuts
+ *
+ * Performs a SetCommTimeouts call for the given port.
+ *
+ * @param hPort
+ * The port to operate on.
+ *
+ * @param lpCTO
+ * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts.
+ *
+ * @param Reserved
+ * Reserved parameter, must be 0.
+ *
+ * @return
+ * TRUE if the SetCommTimeouts call was successful, FALSE otherwise.
+ * A more specific error code can be obtained through GetLastError.
+ */
+BOOL WINAPI
+LocalmonSetPortTimeOuts(HANDLE hPort, LPCOMMTIMEOUTS lpCTO, DWORD Reserved)
+{
+    BOOL bOpenedPort = FALSE;
+    DWORD dwErrorCode;
+    PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
+
+    // Sanity checks
+    if (!pPort || !lpCTO)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // If this is a serial port, a temporary file handle may be opened.
+    if (pPort->PortType == PortType_PhysicalCOM)
+    {
+        if (_CreateNonspooledPort(pPort))
+        {
+            bOpenedPort = TRUE;
+        }
+        else if (GetLastError() != ERROR_SUCCESS)
+        {
+            dwErrorCode = GetLastError();
+            goto Cleanup;
+        }
+    }
+    else if (pPort->hFile == INVALID_HANDLE_VALUE)
+    {
+        // All other port types need to be opened already.
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Finally pass the parameters to SetCommTimeouts.
+    if (!SetCommTimeouts(pPort->hFile, lpCTO))
+    {
+        dwErrorCode = GetLastError();
+        ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    if (bOpenedPort)
+        _ClosePortHandles(pPort);
+
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+BOOL WINAPI
+LocalmonReadPort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuffer, PDWORD pcbRead)
+{
+    BOOL bOpenedPort = FALSE;
+    DWORD dwErrorCode;
+    PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
+
+    // Sanity checks
+    if (!pPort || (cbBuffer && !pBuffer) || !pcbRead)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Reading is only supported for physical ports.
+    if (pPort->PortType != PortType_PhysicalCOM && pPort->PortType != PortType_PhysicalLPT)
+    {
+        dwErrorCode = ERROR_INVALID_HANDLE;
+        goto Cleanup;
+    }
+
+    // If this is a serial port, a temporary file handle may be opened.
+    if (pPort->PortType == PortType_PhysicalCOM)
+    {
+        if (_CreateNonspooledPort(pPort))
+        {
+            bOpenedPort = TRUE;
+        }
+        else if (GetLastError() != ERROR_SUCCESS)
+        {
+            dwErrorCode = GetLastError();
+            goto Cleanup;
+        }
+    }
+
+    // Pass the parameters to ReadFile.
+    if (!ReadFile(pPort->hFile, pBuffer, cbBuffer, pcbRead, NULL))
+    {
+        dwErrorCode = GetLastError();
+        ERR("ReadFile failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+Cleanup:
+    if (bOpenedPort)
+        _ClosePortHandles(pPort);
+
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+BOOL WINAPI
+LocalmonStartDocPort(HANDLE hPort, PWSTR pPrinterName, DWORD JobId, DWORD Level, PBYTE pDocInfo)
+{
+    DWORD dwErrorCode;
+    PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo;        // DOC_INFO_1W is the least common denominator for both DOC_INFO levels.
+    PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
+
+    // Sanity checks
+    if (!pPort || !pPrinterName || (pPort->PortType == PortType_FILE && (!pDocInfo1 || !pDocInfo1->pOutputFile || !*pDocInfo1->pOutputFile)))
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    if (Level > 2)
+    {
+        dwErrorCode = ERROR_INVALID_LEVEL;
+        goto Cleanup;
+    }
+
+    // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then.
+    if (pPort->bStartedDoc)
+    {
+        dwErrorCode = ERROR_SUCCESS;
+        goto Cleanup;
+    }
+
+    // Open a handle to the given printer for later reporting our progress using SetJobW.
+    if (!OpenPrinterW(pPrinterName, &pPort->hPrinter, NULL))
+    {
+        dwErrorCode = GetLastError();
+        ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // We need our Job ID for SetJobW as well.
+    pPort->dwJobID = JobId;
+
+    // Check the port type.
+    if (pPort->PortType == PortType_PhysicalLPT)
+    {
+        // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call.
+        if (!_CreateNonspooledPort(pPort) && GetLastError() != ERROR_SUCCESS)
+        {
+            dwErrorCode = GetLastError();
+            goto Cleanup;
+        }
+
+        // Update the transmission retry timeout as well.
+        _SetTransmissionRetryTimeout(pPort);
+    }
+    else if(pPort->PortType == PortType_FILE)
+    {
+        // This is a FILE: port. Open the output file given in the Document Info.
+        pPort->hFile = CreateFileW(pDocInfo1->pOutputFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL);
+        if (pPort->hFile == INVALID_HANDLE_VALUE)
+        {
+            dwErrorCode = GetLastError();
+            goto Cleanup;
+        }
+    }
+    else
+    {
+        // This is a COM port or a non-physical LPT port. We open NONSPOOLED ports for these per job.
+        if (!_CreateNonspooledPort(pPort))
+        {
+            if (GetLastError() == ERROR_SUCCESS)
+            {
+                // This is a user-local instead of a system-wide port.
+                // Such local ports haven't been remapped by the spooler, so we can just open them.
+                pPort->hFile = CreateFileW(pPort->pwszPortName, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, 0, NULL);
+                if (pPort->hFile == INVALID_HANDLE_VALUE)
+                {
+                    dwErrorCode = GetLastError();
+                    goto Cleanup;
+                }
+            }
+            else
+            {
+                dwErrorCode = GetLastError();
+                goto Cleanup;
+            }
+        }
+    }
+
+    // We were successful!
+    dwErrorCode = ERROR_SUCCESS;
+    pPort->bStartedDoc = TRUE;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
+
+BOOL WINAPI
+LocalmonWritePort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuf, PDWORD pcbWritten)
+{
+    BOOL bOpenedPort = FALSE;
+    DWORD dwErrorCode;
+    PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
+
+    // Sanity checks
+    if (!pPort || (cbBuf && !pBuffer) || !pcbWritten)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // If this is a serial port, a temporary file handle may be opened.
+    if (pPort->PortType == PortType_PhysicalCOM)
+    {
+        if (_CreateNonspooledPort(pPort))
+        {
+            bOpenedPort = TRUE;
+        }
+        else if (GetLastError() != ERROR_SUCCESS)
+        {
+            dwErrorCode = GetLastError();
+            goto Cleanup;
+        }
+    }
+    else if (pPort->hFile == INVALID_HANDLE_VALUE)
+    {
+        // All other port types need to be opened already.
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    // Pass the parameters to WriteFile.
+    if (!WriteFile(pPort->hFile, pBuffer, cbBuf, pcbWritten, NULL))
+    {
+        dwErrorCode = GetLastError();
+        ERR("WriteFile failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // If something was written down, we consider that a success, otherwise it's a timeout.
+    if (*pcbWritten)
+        dwErrorCode = ERROR_SUCCESS;
+    else
+        dwErrorCode = ERROR_TIMEOUT;
+
+Cleanup:
+    if (bOpenedPort)
+        _ClosePortHandles(pPort);
+
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}
index 647439e..82a930c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * PROJECT:     ReactOS Local Spooler Port Monitor
+ * PROJECT:     ReactOS Local Port Monitor
  * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
  * PURPOSE:     Precompiled Header for all source files
  * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
@@ -9,14 +9,96 @@
 #define _PRECOMP_H
 
 #define WIN32_NO_STATUS
+#include <stdlib.h>
+
 #include <windef.h>
 #include <winbase.h>
 #include <wingdi.h>
 #include <winreg.h>
 #include <winspool.h>
 #include <winsplp.h>
+#include <winuser.h>
+#include <ndk/rtlfuncs.h>
+
+#include <spoolss.h>
 
 #include <wine/debug.h>
 WINE_DEFAULT_DEBUG_CHANNEL(localmon);
 
+#include "resource.h"
+
+// Structures
+/**
+ * Describes the port handle returned by LocalmonOpenPort.
+ * Manages a legacy port (COM/LPT) or virtual FILE: port for printing as well as its associated printer and job.
+ */
+typedef struct _LOCALMON_PORT
+{
+    LIST_ENTRY Entry;
+    enum {
+        PortType_FILE,              /** A virtual port for redirecting the document into a file. */
+        PortType_PhysicalCOM,       /** A physical serial port (COM) */
+        PortType_PhysicalLPT,       /** A physical parallel port (LPT) */
+        PortType_OtherLPT           /** A non-physical parallel port (e.g. a redirected one over network using "net use LPT1 ...") */
+    }
+    PortType;
+    BOOL bStartedDoc;               /** Whether a document has been started with StartDocPort. */
+    DWORD dwJobID;                  /** ID of the printing job we are processing (for later reporting progress using SetJobW). */
+    HANDLE hFile;                   /** Handle to the opened port or INVALID_HANDLE_VALUE if it isn't currently opened. */
+    HANDLE hPrinter;                /** Handle to the printer for the job on this port (for using SetJobW). */
+    PWSTR pwszMapping;              /** The current mapping of the DOS Device corresponding to this port at the time _CreateNonspooledPort has been called. */
+    PWSTR pwszPortName;             /** The name of this port including the trailing colon. Empty for virtual file ports. */
+}
+LOCALMON_PORT, *PLOCALMON_PORT;
+
+/**
+ * Describes the monitor handle returned by InitializePrintMonitor2.
+ * Manages all available ports in this instance.
+ */
+typedef struct _LOCALMON_HANDLE
+{
+    LIST_ENTRY FilePorts;           /** Virtual ports created for every document that's redirected to an output file. */
+    LIST_ENTRY Ports;               /** Ports found on the system (except for FILE:) */
+}
+LOCALMON_HANDLE, *PLOCALMON_HANDLE;
+
+/**
+ * Describes the Xcv handle returned by LocalmonXcvOpenPort.
+ * Manages the required data for the Xcv* calls.
+ */
+typedef struct _LOCALMON_XCV
+{
+    ACCESS_MASK GrantedAccess;
+    PWSTR pwszObject;
+}
+LOCALMON_XCV, *PLOCALMON_XCV;
+
+// main.c
+extern DWORD cbLocalMonitor;
+extern DWORD cbLocalPort;
+extern PCWSTR pwszLocalMonitor;
+extern PCWSTR pwszLocalPort;
+void WINAPI LocalmonShutdown(HANDLE hMonitor);
+
+// ports.c
+BOOL WINAPI LocalmonClosePort(HANDLE hPort);
+BOOL WINAPI LocalmonEndDocPort(HANDLE hPort);
+BOOL WINAPI LocalmonEnumPorts(HANDLE hMonitor, PWSTR pName, DWORD Level, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned);
+BOOL WINAPI LocalmonGetPrinterDataFromPort(HANDLE hPort, DWORD ControlID, PWSTR pValueName, PWSTR lpInBuffer, DWORD cbInBuffer, PWSTR lpOutBuffer, DWORD cbOutBuffer, PDWORD lpcbReturned);
+BOOL WINAPI LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle);
+BOOL WINAPI LocalmonReadPort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuffer, PDWORD pcbRead);
+BOOL WINAPI LocalmonSetPortTimeOuts(HANDLE hPort, LPCOMMTIMEOUTS lpCTO, DWORD Reserved);
+BOOL WINAPI LocalmonStartDocPort(HANDLE hPort, PWSTR pPrinterName, DWORD JobId, DWORD Level, PBYTE pDocInfo);
+BOOL WINAPI LocalmonWritePort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuf, PDWORD pcbWritten);
+
+// tools.c
+BOOL DoesPortExist(PCWSTR pwszPortName);
+DWORD GetLPTTransmissionRetryTimeout();
+PWSTR GetPortNameWithoutColon(PCWSTR pwszPortName);
+
+// xcv.c
+BOOL WINAPI LocalmonXcvClosePort(HANDLE hXcv);
+DWORD WINAPI LocalmonXcvDataPort(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded);
+BOOL WINAPI LocalmonXcvOpenPort(HANDLE hMonitor, PCWSTR pszObject, ACCESS_MASK GrantedAccess, PHANDLE phXcv);
+
 #endif
diff --git a/reactos/win32ss/printing/monitors/localmon/resource.h b/reactos/win32ss/printing/monitors/localmon/resource.h
new file mode 100644 (file)
index 0000000..850f1f7
--- /dev/null
@@ -0,0 +1,11 @@
+/*
+ * PROJECT:     ReactOS Local Port Monitor
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Resource IDs
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#pragma once
+
+#define IDS_LOCAL_PORT           500
+#define IDS_LOCAL_MONITOR        507
diff --git a/reactos/win32ss/printing/monitors/localmon/tools.c b/reactos/win32ss/printing/monitors/localmon/tools.c
new file mode 100644 (file)
index 0000000..4e0d36c
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * PROJECT:     ReactOS Local Port Monitor
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Various support functions shared by multiple files
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+/**
+ * @name DoesPortExist
+ *
+ * Checks all Port Monitors installed on the local system to find out if a given port already exists.
+ *
+ * @param pwszPortName
+ * The port name to check.
+ *
+ * @return
+ * TRUE if a port with that name already exists on the local system.
+ * If the return value is FALSE, either the port doesn't exist or an error occurred.
+ * Use GetLastError in this case to check the error case.
+ */
+BOOL
+DoesPortExist(PCWSTR pwszPortName)
+{
+    BOOL bReturnValue = FALSE;
+    DWORD cbNeeded;
+    DWORD dwErrorCode;
+    DWORD dwReturned;
+    DWORD i;
+    PPORT_INFO_1W p;
+    PPORT_INFO_1W pPortInfo1 = NULL;
+
+    // Determine the required buffer size.
+    EnumPortsW(NULL, 1, NULL, 0, &cbNeeded, &dwReturned);
+    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
+    {
+        dwErrorCode = GetLastError();
+        ERR("EnumPortsW failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // Allocate a buffer large enough.
+    pPortInfo1 = DllAllocSplMem(cbNeeded);
+    if (!pPortInfo1)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    // Now get the actual port information.
+    if (!EnumPortsW(NULL, 1, (PBYTE)pPortInfo1, cbNeeded, &cbNeeded, &dwReturned))
+    {
+        dwErrorCode = GetLastError();
+        ERR("EnumPortsW failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // We were successful! Loop through all returned ports.
+    dwErrorCode = ERROR_SUCCESS;
+    p = pPortInfo1;
+
+    for (i = 0; i < dwReturned; i++)
+    {
+        // Check if this existing port matches our queried one.
+        if (wcsicmp(p->pName, pwszPortName) == 0)
+        {
+            bReturnValue = TRUE;
+            goto Cleanup;
+        }
+
+        p++;
+    }
+
+Cleanup:
+    if (pPortInfo1)
+        DllFreeSplMem(pPortInfo1);
+
+    SetLastError(dwErrorCode);
+    return bReturnValue;
+}
+
+DWORD
+GetLPTTransmissionRetryTimeout()
+{
+    DWORD cbBuffer;
+    DWORD dwReturnValue = 90;       // Use 90 seconds as default if we fail to read from registry.
+    HKEY hKey;
+    LSTATUS lStatus;
+
+    // Six digits is the most you can enter in Windows' LocalUI.dll.
+    // Larger values make it crash, so introduce a limit here.
+    WCHAR wszBuffer[6 + 1];
+
+    // Open the key where our value is stored.
+    lStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0, KEY_READ, &hKey);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Query the value.
+    cbBuffer = sizeof(wszBuffer);
+    lStatus = RegQueryValueExW(hKey, L"TransmissionRetryTimeout", NULL, NULL, (PBYTE)wszBuffer, &cbBuffer);
+    if (lStatus != ERROR_SUCCESS)
+    {
+        ERR("RegQueryValueExW failed with status %ld!\n", lStatus);
+        goto Cleanup;
+    }
+
+    // Return it converted to a DWORD.
+    dwReturnValue = wcstoul(wszBuffer, NULL, 10);
+
+Cleanup:
+    if (hKey)
+        RegCloseKey(hKey);
+
+    return dwReturnValue;
+}
+
+/**
+ * @name GetPortNameWithoutColon
+ *
+ * Most of the time, we operate on port names with a trailing colon. But some functions require the name without the trailing colon.
+ * This function returns the port name without the colon. You have to free the returned buffer using DllFreeSplMem.
+ *
+ * @param pwszPortName
+ * The port name with colon
+ *
+ * @return
+ * Buffer containing the port name without a colon or NULL in case of failure.
+ */
+PWSTR
+GetPortNameWithoutColon(PCWSTR pwszPortName)
+{
+    DWORD cchPortName;
+    PWSTR pwszPortNameWithoutColon;
+
+    // Every port in our port list has a trailing colon, so we just need to remove the last character of the string.
+    cchPortName = wcslen(pwszPortName) - 1;
+
+    pwszPortNameWithoutColon = DllAllocSplMem((cchPortName + 1) * sizeof(WCHAR));
+    if (!pwszPortNameWithoutColon)
+    {
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        return NULL;
+    }
+
+    CopyMemory(pwszPortNameWithoutColon, pwszPortName, cchPortName * sizeof(WCHAR));
+    pwszPortNameWithoutColon[cchPortName] = 0;
+
+    return pwszPortNameWithoutColon;
+}
diff --git a/reactos/win32ss/printing/monitors/localmon/xcv.c b/reactos/win32ss/printing/monitors/localmon/xcv.c
new file mode 100644 (file)
index 0000000..0117948
--- /dev/null
@@ -0,0 +1,440 @@
+/*
+ * PROJECT:     ReactOS Local Port Monitor
+ * LICENSE:     GNU LGPL v2.1 or any later version as published by the Free Software Foundation
+ * PURPOSE:     Implementation of Xcv* and support functions
+ * COPYRIGHT:   Copyright 2015 Colin Finck <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+static DWORD
+_HandleAddPort(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
+{
+    return ERROR_CALL_NOT_IMPLEMENTED;
+}
+
+/**
+ * @name _HandleConfigureLPTPortCommandOK
+ *
+ * Writes the value for "TransmissionRetryTimeout" to the registry. Checks for granted SERVER_ACCESS_ADMINISTER access.
+ * Actually the opposite of _HandleGetTransmissionRetryTimeout, but name kept for compatibility.
+ *
+ * @param pXcv
+ * Pointer to the LOCALMON_XCV structure of the currently opened Xcv port.
+ *
+ * @param pInputData
+ * Pointer to a Unicode string containing the value to be written to the registry.
+ *
+ * @param pcbOutputNeeded
+ * Pointer to a DWORD that will be zeroed on return.
+ *
+ * @return
+ * An error code indicating success or failure.
+ */
+static DWORD
+_HandleConfigureLPTPortCommandOK(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
+{
+    DWORD cbBuffer;
+    DWORD dwErrorCode;
+    HKEY hKey = NULL;
+    HKEY hToken = NULL;
+
+    // Sanity checks
+    if (!pXcv || !pInputData || !pcbOutputNeeded)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    *pcbOutputNeeded = 0;
+
+    // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
+    if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
+    {
+        dwErrorCode = ERROR_ACCESS_DENIED;
+        goto Cleanup;
+    }
+
+    // Switch to the SYSTEM context for modifying the registry.
+    hToken = RevertToPrinterSelf();
+    if (!hToken)
+    {
+        dwErrorCode = GetLastError();
+        ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // Open the key where our value is stored.
+    dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0, KEY_SET_VALUE, &hKey);
+    if (dwErrorCode != ERROR_SUCCESS)
+    {
+        ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // We don't use cbInputData here, because the buffer pInputData could be bigger than the data it contains.
+    cbBuffer = (wcslen((PWSTR)pInputData) + 1) * sizeof(WCHAR);
+
+    // Write the value to the registry.
+    dwErrorCode = (DWORD)RegSetValueExW(hKey, L"TransmissionRetryTimeout", 0, REG_SZ, pInputData, cbBuffer);
+    if (dwErrorCode != ERROR_SUCCESS)
+    {
+        ERR("RegSetValueExW failed with status %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+Cleanup:
+    if (hKey)
+        RegCloseKey(hKey);
+
+    if (hToken)
+        ImpersonatePrinterClient(hToken);
+
+    return dwErrorCode;
+}
+
+static DWORD
+_HandleDeletePort(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
+{
+    return ERROR_CALL_NOT_IMPLEMENTED;
+}
+
+/**
+ * @name _HandleGetDefaultCommConfig
+ *
+ * Gets the default configuration of a legacy port.
+ * The opposite function is _HandleSetDefaultCommConfig.
+ *
+ * @param pInputData
+ * The port name (without colon!) whose default configuration you want to get.
+ *
+ * @param pOutputData
+ * Pointer to a COMMCONFIG structure that will receive the configuration information.
+ *
+ * @param cbOutputData
+ * Size of the variable pointed to by pOutputData.
+ *
+ * @param pcbOutputNeeded
+ * Pointer to a DWORD that contains the required size for pOutputData on return.
+ *
+ * @return
+ * An error code indicating success or failure.
+ */
+static DWORD
+_HandleGetDefaultCommConfig(PBYTE pInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
+{
+    // Sanity checks
+    if (!pInputData || !pcbOutputNeeded)
+        return ERROR_INVALID_PARAMETER;
+
+    *pcbOutputNeeded = sizeof(COMMCONFIG);
+
+    // Check if the supplied buffer is large enough.
+    if (cbOutputData < *pcbOutputNeeded)
+        return ERROR_INSUFFICIENT_BUFFER;
+
+    // Finally get the port configuration.
+    if (!GetDefaultCommConfigW((PCWSTR)pInputData, (LPCOMMCONFIG)pOutputData, pcbOutputNeeded))
+        return GetLastError();
+
+    return ERROR_SUCCESS;
+}
+
+/**
+ * @name _HandleGetTransmissionRetryTimeout
+ *
+ * Reads the value for "TransmissionRetryTimeout" from the registry and converts it to a DWORD.
+ * The opposite function is _HandleConfigureLPTPortCommandOK.
+ *
+ * @param pOutputData
+ * Pointer to a DWORD that will receive the timeout value.
+ *
+ * @param cbOutputData
+ * Size of the variable pointed to by pOutputData.
+ *
+ * @param pcbOutputNeeded
+ * Pointer to a DWORD that contains the required size for pOutputData on return.
+ *
+ * @return
+ * An error code indicating success or failure.
+ */
+static DWORD
+_HandleGetTransmissionRetryTimeout(PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
+{
+    DWORD dwTimeout;
+
+    // Sanity checks
+    if (!pOutputData || !pcbOutputNeeded)
+        return ERROR_INVALID_PARAMETER;
+
+    *pcbOutputNeeded = sizeof(DWORD);
+
+    // Check if the supplied buffer is large enough.
+    if (cbOutputData < *pcbOutputNeeded)
+        return ERROR_INSUFFICIENT_BUFFER;
+
+    // Retrieve and copy the number.
+    dwTimeout = GetLPTTransmissionRetryTimeout();
+    CopyMemory(pOutputData, &dwTimeout, sizeof(DWORD));
+    return ERROR_SUCCESS;
+}
+
+/**
+ * @name _HandleMonitorUI
+ *
+ * Returns the filename of the associated UI DLL for this Port Monitor.
+ *
+ * @param pOutputData
+ * Pointer to a Unicode string that will receive the DLL filename.
+ *
+ * @param cbOutputData
+ * Size of the variable pointed to by pOutputData.
+ *
+ * @param pcbOutputNeeded
+ * Pointer to a DWORD that contains the required size for pOutputData on return.
+ *
+ * @return
+ * An error code indicating success or failure.
+ */
+static DWORD
+_HandleMonitorUI(PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
+{
+    const WCHAR wszMonitorUI[] = L"LocalUI.dll";
+
+    // Sanity checks
+    if (!pOutputData || !pcbOutputNeeded)
+        return ERROR_INVALID_PARAMETER;
+
+    *pcbOutputNeeded = sizeof(wszMonitorUI);
+
+    // Check if the supplied buffer is large enough.
+    if (cbOutputData < *pcbOutputNeeded)
+        return ERROR_INSUFFICIENT_BUFFER;
+
+    // Copy the string.
+    CopyMemory(pOutputData, wszMonitorUI, sizeof(wszMonitorUI));
+    return ERROR_SUCCESS;
+}
+
+/**
+ * @name _HandlePortExists
+ *
+ * Checks all Port Monitors installed on the local system to find out if a given port already exists.
+ *
+ * @param pInputData
+ * Pointer to a Unicode string specifying the port name to check.
+ *
+ * @param pOutputData
+ * Pointer to a BOOL that receives the result of the check.
+ *
+ * @param cbOutputData
+ * Size of the variable pointed to by pOutputData.
+ *
+ * @param pcbOutputNeeded
+ * Pointer to a DWORD that contains the required size for pOutputData on return.
+ *
+ * @return
+ * An error code indicating success or failure.
+ */
+static DWORD
+_HandlePortExists(PBYTE pInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
+{
+    // Sanity checks
+    if (!pInputData || !pOutputData || !pcbOutputNeeded)
+        return ERROR_INVALID_PARAMETER;
+
+    *pcbOutputNeeded = sizeof(BOOL);
+
+    // Check if the supplied buffer is large enough.
+    if (cbOutputData < *pcbOutputNeeded)
+        return ERROR_INSUFFICIENT_BUFFER;
+
+    // Return the check result and error code.
+    *(PBOOL)pOutputData = DoesPortExist((PCWSTR)pInputData);
+    return GetLastError();
+}
+
+static DWORD
+_HandlePortIsValid(PBYTE pInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
+{
+    return ERROR_CALL_NOT_IMPLEMENTED;
+}
+
+/**
+ * @name _HandleSetDefaultCommConfig
+ *
+ * Sets the default configuration of a legacy port. Checks for granted SERVER_ACCESS_ADMINISTER access.
+ * You have to supply the port name (with colon!) in XcvOpenPort.
+ * The opposite function is _HandleGetDefaultCommConfig.
+ *
+ * @param pXcv
+ * Pointer to the LOCALMON_XCV structure of the currently opened Xcv port.
+ *
+ * @param pInputData
+ * Pointer to the COMMCONFIG structure that shall be passed to SetDefaultCommConfigW.
+ *
+ * @param pcbOutputNeeded
+ * Pointer to a DWORD that will be zeroed on return.
+ *
+ * @return
+ * An error code indicating success or failure.
+ */
+static DWORD
+_HandleSetDefaultCommConfig(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
+{
+    DWORD dwErrorCode;
+    HANDLE hToken = NULL;
+    LPCOMMCONFIG pCommConfig;
+    PWSTR pwszPortNameWithoutColon = NULL;
+
+    // Sanity checks
+    // pwszObject needs to be at least 2 characters long to be a port name with a trailing colon.
+    if (!pXcv || !pXcv->pwszObject || !pXcv->pwszObject[0] || !pXcv->pwszObject[1] || !pInputData || !pcbOutputNeeded)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    *pcbOutputNeeded = 0;
+
+    // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
+    if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
+    {
+        dwErrorCode = ERROR_ACCESS_DENIED;
+        goto Cleanup;
+    }
+
+    // SetDefaultCommConfigW needs the port name without colon.
+    pwszPortNameWithoutColon = GetPortNameWithoutColon(pXcv->pwszObject);
+    if (!pwszPortNameWithoutColon)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        goto Cleanup;
+    }
+
+    // Switch to the SYSTEM context for setting the port configuration.
+    hToken = RevertToPrinterSelf();
+    if (!hToken)
+    {
+        dwErrorCode = GetLastError();
+        ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    // Finally pass the parameters to SetDefaultCommConfigW.
+    pCommConfig = (LPCOMMCONFIG)pInputData;
+    if (!SetDefaultCommConfigW(pwszPortNameWithoutColon, pCommConfig, pCommConfig->dwSize))
+    {
+        dwErrorCode = GetLastError();
+        ERR("SetDefaultCommConfigW failed with error %lu!\n", dwErrorCode);
+        goto Cleanup;
+    }
+
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    if (hToken)
+        ImpersonatePrinterClient(hToken);
+
+    if (pwszPortNameWithoutColon)
+        DllFreeSplMem(pwszPortNameWithoutColon);
+
+    return dwErrorCode;
+}
+
+BOOL WINAPI
+LocalmonXcvClosePort(HANDLE hXcv)
+{
+    // Sanity checks
+    if (!hXcv)
+    {
+        SetLastError(ERROR_INVALID_PARAMETER);
+        return FALSE;
+    }
+
+    // Free the memory.
+    DllFreeSplMem(hXcv);
+    SetLastError(ERROR_SUCCESS);
+    return TRUE;
+}
+
+DWORD WINAPI
+LocalmonXcvDataPort(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
+{
+    // Sanity checks
+    if (!pszDataName)
+        return ERROR_INVALID_PARAMETER;
+
+    // Call the appropriate handler for the requested data name.
+    if (wcscmp(pszDataName, L"AddPort") == 0)
+        return _HandleAddPort((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"ConfigureLPTPortCommandOK") == 0)
+        return _HandleConfigureLPTPortCommandOK((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"DeletePort") == 0)
+        return _HandleDeletePort((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"GetDefaultCommConfig") == 0)
+        return _HandleGetDefaultCommConfig(pInputData, pOutputData, cbOutputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"GetTransmissionRetryTimeout") == 0)
+        return _HandleGetTransmissionRetryTimeout(pOutputData, cbOutputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"MonitorUI") == 0)
+        return _HandleMonitorUI(pOutputData, cbOutputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"PortExists") == 0)
+        return _HandlePortExists(pInputData, pOutputData, cbOutputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"PortIsValid") == 0)
+        return _HandlePortIsValid(pInputData, pOutputData, cbOutputData, pcbOutputNeeded);
+
+    if (wcscmp(pszDataName, L"SetDefaultCommConfig") == 0)
+        return _HandleSetDefaultCommConfig((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
+
+    return ERROR_INVALID_PARAMETER;
+}
+
+BOOL WINAPI
+LocalmonXcvOpenPort(HANDLE hMonitor, PCWSTR pwszObject, ACCESS_MASK GrantedAccess, PHANDLE phXcv)
+{
+    DWORD cbObject = 0;
+    DWORD dwErrorCode;
+    PLOCALMON_XCV pXcv;
+
+    // Sanity checks
+    if (!hMonitor || !phXcv)
+    {
+        dwErrorCode = ERROR_INVALID_PARAMETER;
+        goto Cleanup;
+    }
+
+    if (pwszObject)
+        cbObject = (wcslen(pwszObject) + 1) * sizeof(WCHAR);
+
+    // Create a new LOCALMON_XCV structure and fill the relevant fields.
+    pXcv = DllAllocSplMem(sizeof(LOCALMON_XCV) + cbObject);
+    if (!pXcv)
+    {
+        dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
+        ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
+        goto Cleanup;
+    }
+
+    pXcv->GrantedAccess = GrantedAccess;
+
+    if (cbObject)
+    {
+        pXcv->pwszObject = (PWSTR)((PBYTE)pXcv + sizeof(LOCALMON_XCV));
+        CopyMemory(pXcv->pwszObject, pwszObject, cbObject);
+    }
+
+    // Return it as the Xcv handle.
+    *phXcv = (HANDLE)pXcv;
+    dwErrorCode = ERROR_SUCCESS;
+
+Cleanup:
+    SetLastError(dwErrorCode);
+    return (dwErrorCode == ERROR_SUCCESS);
+}