1 /*
2  * PROJECT:     ReactOS Local Port Monitor
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Main functions
5  * COPYRIGHT:   Copyright 2015 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 // Global Variables
11 DWORD cbLocalMonitor;
12 DWORD cbLocalPort;
13 PCWSTR pwszLocalMonitor;
14 PCWSTR pwszLocalPort;
15 
16 // Local Constants
17 static MONITOR2 _MonitorFunctions = {
18     sizeof(MONITOR2),               // cbSize
19     LocalmonEnumPorts,              // pfnEnumPorts
20     LocalmonOpenPort,               // pfnOpenPort
21     NULL,                           // pfnOpenPortEx
22     LocalmonStartDocPort,           // pfnStartDocPort
23     LocalmonWritePort,              // pfnWritePort
24     LocalmonReadPort,               // pfnReadPort
25     LocalmonEndDocPort,             // pfnEndDocPort
26     LocalmonClosePort,              // pfnClosePort
27     LocalmonAddPort,                // pfnAddPort moved to localui.dll since w2k, but~
28     LocalmonAddPortEx,              // pfnAddPortEx
29     LocalmonConfigurePort,          // pfnConfigurePort moved to localui.dll since w2k, but~
30     LocalmonDeletePort,             // pfnDeletePort moved to localui.dll since w2k, but~
31     LocalmonGetPrinterDataFromPort, // pfnGetPrinterDataFromPort
32     LocalmonSetPortTimeOuts,        // pfnSetPortTimeOuts
33     LocalmonXcvOpenPort,            // pfnXcvOpenPort
34     LocalmonXcvDataPort,            // pfnXcvDataPort
35     LocalmonXcvClosePort,           // pfnXcvClosePort
36     LocalmonShutdown,               // pfnShutdown
37     NULL,                           // pfnSendRecvBidiDataFromPort
38 };
39 
40 
41 /**
42  * @name _IsNEPort
43  *
44  * Checks if the given port name is a virtual Ne port.
45  * A virtual Ne port may appear in HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Ports and can have the formats
46  * Ne00:, Ne01:, Ne-02:, Ne456:
47  * This check is extra picky to not cause false positives (like file name ports starting with "Ne").
48  *
49  * @param pwszPortName
50  * The port name to check.
51  *
52  * @return
53  * TRUE if this is definitely a virtual Ne port, FALSE if not.
54  */
55 static __inline BOOL
56 _IsNEPort(PCWSTR pwszPortName)
57 {
58     PCWSTR p = pwszPortName;
59 
60     // First character needs to be 'N' (uppercase or lowercase)
61     if (*p != L'N' && *p != L'n')
62         return FALSE;
63 
64     // Next character needs to be 'E' (uppercase or lowercase)
65     p++;
66     if (*p != L'E' && *p != L'e')
67         return FALSE;
68 
69     // An optional hyphen may follow now.
70     p++;
71     if (*p == L'-')
72         p++;
73 
74     // Now an arbitrary number of digits may follow.
75     while (*p >= L'0' && *p <= L'9')
76         p++;
77 
78     // Finally, the virtual Ne port must be terminated by a colon.
79     if (*p != ':')
80         return FALSE;
81 
82     // If this is the end of the string, we have a virtual Ne port.
83     p++;
84     return (*p == L'\0');
85 }
86 
87 static void
88 _LoadResources(HINSTANCE hinstDLL)
89 {
90     LoadStringW(hinstDLL, IDS_LOCAL_MONITOR, (PWSTR)&pwszLocalMonitor, 0);
91     cbLocalMonitor = (wcslen(pwszLocalMonitor) + 1) * sizeof(WCHAR);
92 
93     LoadStringW(hinstDLL, IDS_LOCAL_PORT, (PWSTR)&pwszLocalPort, 0);
94     cbLocalPort = (wcslen(pwszLocalPort) + 1) * sizeof(WCHAR);
95 }
96 
97 BOOL WINAPI
98 DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
99 {
100     switch (fdwReason)
101     {
102         case DLL_PROCESS_ATTACH:
103             DisableThreadLibraryCalls(hinstDLL);
104             _LoadResources(hinstDLL);
105             break;
106     }
107 
108     return TRUE;
109 }
110 
111 void WINAPI
112 LocalmonShutdown(HANDLE hMonitor)
113 {
114     PLOCALMON_HANDLE pLocalmon;
115     PLOCALMON_PORT pPort;
116     PLOCALMON_XCV pXcv;
117     PLIST_ENTRY pEntry;
118 
119     TRACE("LocalmonShutdown(%p)\n", hMonitor);
120 
121     pLocalmon = (PLOCALMON_HANDLE)hMonitor;
122 
123     if ( pLocalmon->Sig != SIGLCMMON )
124     {
125         ERR("LocalmonShutdown : Invalid Monitor Handle\n",hMonitor);
126         return;
127     }
128 
129     // Close all virtual file ports.
130     if (!IsListEmpty(&pLocalmon->FilePorts))
131     {
132         for (pEntry = pLocalmon->FilePorts.Flink; pEntry != &pLocalmon->FilePorts; pEntry = pEntry->Flink)
133         {
134            pPort = CONTAINING_RECORD(&pLocalmon->FilePorts.Flink, LOCALMON_PORT, Entry);
135            LocalmonClosePort((HANDLE)pPort);
136         }
137     }
138 
139     // Do the same for the open Xcv ports.
140     if (!IsListEmpty(&pLocalmon->XcvHandles))
141     {
142         for (pEntry = pLocalmon->XcvHandles.Flink; pEntry != &pLocalmon->XcvHandles; pEntry = pEntry->Flink)
143         {
144             pXcv = CONTAINING_RECORD(pEntry, LOCALMON_XCV, Entry);
145             LocalmonXcvClosePort((HANDLE)pXcv);
146         }
147     }
148 
149     // Now close all registry ports, remove them from the list and free their memory.
150     if (!IsListEmpty(&pLocalmon->RegistryPorts))
151     {
152         for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
153         {
154             pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
155             if ( LocalmonClosePort((HANDLE)pPort) ) continue;
156             RemoveEntryList(&pPort->Entry);
157             DllFreeSplMem(pPort);
158         }
159     }
160 
161     // Finally clean the LOCALMON_HANDLE structure itself.
162     DeleteCriticalSection(&pLocalmon->Section);
163     DllFreeSplMem(pLocalmon);
164 }
165 
166 PMONITOR2 WINAPI
167 InitializePrintMonitor2(PMONITORINIT pMonitorInit, PHANDLE phMonitor)
168 {
169     DWORD cchMaxPortName;
170     DWORD cchPortName;
171     DWORD dwErrorCode;
172     DWORD dwPortCount;
173     DWORD i;
174     HKEY hKey;
175     PMONITOR2 pReturnValue = NULL;
176     PLOCALMON_HANDLE pLocalmon;
177     PLOCALMON_PORT pPort = NULL;
178 
179     TRACE("InitializePrintMonitor2(%p, %p)\n", pMonitorInit, phMonitor);
180 
181     // Create a new LOCALMON_HANDLE structure.
182     pLocalmon = DllAllocSplMem(sizeof(LOCALMON_HANDLE));
183     pLocalmon->Sig = SIGLCMMON;
184     InitializeCriticalSection(&pLocalmon->Section);
185     InitializeListHead(&pLocalmon->FilePorts);
186     InitializeListHead(&pLocalmon->RegistryPorts);
187     InitializeListHead(&pLocalmon->XcvHandles);
188 
189     // The Local Spooler Port Monitor doesn't need to care about the given registry key and functions.
190     // Instead it uses a well-known registry key for getting its information about local ports. Open this one.
191     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", 0, KEY_READ, &hKey);
192     if (dwErrorCode != ERROR_SUCCESS)
193     {
194         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
195         goto Cleanup;
196     }
197 
198     // Get the number of ports and the length of the largest port name.
199     dwErrorCode = (DWORD)RegQueryInfoKeyW(hKey, NULL, NULL, NULL, NULL, NULL, NULL, &dwPortCount, &cchMaxPortName, NULL, NULL, NULL);
200     if (dwErrorCode != ERROR_SUCCESS)
201     {
202         ERR("RegQueryInfoKeyW failed with status %lu!\n", dwErrorCode);
203         goto Cleanup;
204     }
205 
206     // Loop through all ports.
207     for (i = 0; i < dwPortCount; i++)
208     {
209         // Allocate memory for a new LOCALMON_PORT structure and its name.
210         pPort = DllAllocSplMem(sizeof(LOCALMON_PORT) + (cchMaxPortName + 1) * sizeof(WCHAR));
211         if (!pPort)
212         {
213             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
214             ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
215             goto Cleanup;
216         }
217 
218         pPort->Sig = SIGLCMPORT;
219         pPort->pLocalmon = pLocalmon;
220         pPort->hFile = INVALID_HANDLE_VALUE;
221         pPort->pwszPortName = (PWSTR)(pPort+1);
222 
223         // Get the port name.
224         cchPortName = cchMaxPortName + 1;
225         dwErrorCode = (DWORD)RegEnumValueW(hKey, i, pPort->pwszPortName, &cchPortName, NULL, NULL, NULL, NULL);
226         if (dwErrorCode != ERROR_SUCCESS)
227         {
228             ERR("RegEnumValueW failed with status %lu!\n", dwErrorCode);
229             goto Cleanup;
230         }
231 
232         // pwszPortName can be one of the following to be valid for this Port Monitor:
233         //    COMx:                        - Physical COM port
234         //    LPTx:                        - Physical LPT port (or redirected one using "net use LPT1 ...")
235         //    FILE:                        - Opens a prompt that asks for an output filename
236         //    C:\bla.txt                   - Redirection into the file "C:\bla.txt"
237         //    \\COMPUTERNAME\PrinterName   - Redirection to a shared network printer installed as a local port
238         //
239         // We can't detect valid and invalid ones by the name, so we can only exclude empty ports and the virtual "Ne00:", "Ne01:", ... ports.
240         // Skip the invalid ones here.
241         if (!cchPortName || _IsNEPort(pPort->pwszPortName))
242         {
243             DllFreeSplMem(pPort);
244             pPort = NULL;
245             continue;
246         }
247 
248         // Add it to the list.
249         InsertTailList(&pLocalmon->RegistryPorts, &pPort->Entry);
250         TRACE("InitializePrintMonitor2 Port : %s \n",debugstr_w(pPort->pwszPortName));
251 
252         // Don't let the cleanup routine free this.
253         pPort = NULL;
254     }
255 
256     // Return our handle and the Print Monitor functions.
257     *phMonitor = (HANDLE)pLocalmon;
258     pReturnValue = &_MonitorFunctions;
259     dwErrorCode = ERROR_SUCCESS;
260 
261 Cleanup:
262     if (pPort)
263         DllFreeSplMem(pPort);
264 
265     SetLastError(dwErrorCode);
266     return pReturnValue;
267 }
268