1 /*
2  * PROJECT:     ReactOS Local Port Monitor
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Implementation of Xcv* and support functions
5  * COPYRIGHT:   Copyright 2015 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 static DWORD
11 _HandleAddPort(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
12 {
13     DWORD res, cbPortName;
14     HKEY hroot;
15     HKEY hToken = NULL;
16     PLOCALMON_PORT pPort;
17     PLOCALMON_HANDLE pLocalmon = pXcv->pLocalmon;
18     PWSTR PortName = (PWSTR)pInputData;
19 
20     FIXME("LcmXcvAddPort : %s\n", debugstr_w( (LPWSTR) PortName ) );
21 
22     if (!pLocalmon )
23     {
24         res = ERROR_INVALID_PARAMETER;
25         goto Cleanup;
26     }
27 
28     // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
29     if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
30     {
31         res = ERROR_ACCESS_DENIED;
32         goto Cleanup;
33     }
34 
35     // Switch to the SYSTEM context for modifying the registry.
36     hToken = RevertToPrinterSelf();
37     if (!hToken)
38     {
39         res = GetLastError();
40         ERR("RevertToPrinterSelf failed with error %lu!\n", res);
41         goto Cleanup;
42     }
43 
44     res = RegOpenKeyW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", &hroot);
45     if (res == ERROR_SUCCESS)
46     {
47         if ( DoesPortExist( PortName ) )
48         {
49             RegCloseKey(hroot);
50             FIXME("=> %u\n", ERROR_ALREADY_EXISTS);
51             res = ERROR_ALREADY_EXISTS;
52             goto Cleanup;
53         }
54 
55         cbPortName = (wcslen( PortName ) + 1) * sizeof(WCHAR);
56 
57         // Create a new LOCALMON_PORT structure for it.
58         pPort = DllAllocSplMem(sizeof(LOCALMON_PORT) + cbPortName);
59         if (!pPort)
60         {
61             res = ERROR_NOT_ENOUGH_MEMORY;
62             RegCloseKey( hroot );
63             goto Cleanup;
64         }
65         memset( pPort, 0, sizeof(LOCALMON_PORT) + cbPortName );
66 
67         pPort->hFile = INVALID_HANDLE_VALUE;
68         pPort->pLocalmon = pLocalmon;
69         pPort->pwszPortName = wcscpy( (PWSTR)(pPort+1), PortName );
70 
71         // Insert it into the Registry list.
72         InsertTailList(&pLocalmon->RegistryPorts, &pPort->Entry);
73 
74         res = RegSetValueExW(hroot, PortName, 0, REG_SZ, (const BYTE *) L"", sizeof(L""));
75         RegCloseKey(hroot);
76     }
77 
78     FIXME("=> %u\n", res);
79 
80 Cleanup:
81     if (hToken) ImpersonatePrinterClient(hToken);
82     SetLastError(res);
83     return res;
84 }
85 
86 /**
87  * @name _HandleConfigureLPTPortCommandOK
88  *
89  * Writes the value for "TransmissionRetryTimeout" to the registry. Checks for granted SERVER_ACCESS_ADMINISTER access.
90  * Actually the opposite of _HandleGetTransmissionRetryTimeout, but name kept for compatibility.
91  *
92  * @param pXcv
93  * Pointer to the LOCALMON_XCV structure of the currently opened Xcv port.
94  *
95  * @param pInputData
96  * Pointer to a Unicode string containing the value to be written to the registry.
97  *
98  * @param pcbOutputNeeded
99  * Pointer to a DWORD that will be zeroed on return.
100  *
101  * @return
102  * An error code indicating success or failure.
103  */
104 static DWORD
105 _HandleConfigureLPTPortCommandOK(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
106 {
107     DWORD cbBuffer;
108     DWORD dwErrorCode;
109     HKEY hKey = NULL;
110     HKEY hToken = NULL;
111 
112     // Sanity checks
113     if (!pXcv || !pInputData || !pcbOutputNeeded)
114     {
115         dwErrorCode = ERROR_INVALID_PARAMETER;
116         goto Cleanup;
117     }
118 
119     *pcbOutputNeeded = 0;
120 
121     // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
122     if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
123     {
124         dwErrorCode = ERROR_ACCESS_DENIED;
125         goto Cleanup;
126     }
127 
128     // Switch to the SYSTEM context for modifying the registry.
129     hToken = RevertToPrinterSelf();
130     if (!hToken)
131     {
132         dwErrorCode = GetLastError();
133         ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
134         goto Cleanup;
135     }
136 
137     // Open the key where our value is stored.
138     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0, KEY_SET_VALUE, &hKey);
139     if (dwErrorCode != ERROR_SUCCESS)
140     {
141         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
142         goto Cleanup;
143     }
144 
145     // We don't use cbInputData here, because the buffer pInputData could be bigger than the data it contains.
146     cbBuffer = (wcslen((PWSTR)pInputData) + 1) * sizeof(WCHAR);
147 
148     // Write the value to the registry.
149     dwErrorCode = (DWORD)RegSetValueExW(hKey, L"TransmissionRetryTimeout", 0, REG_SZ, pInputData, cbBuffer);
150     if (dwErrorCode != ERROR_SUCCESS)
151     {
152         ERR("RegSetValueExW failed with status %lu!\n", dwErrorCode);
153         goto Cleanup;
154     }
155 
156 Cleanup:
157     if (hKey)
158         RegCloseKey(hKey);
159 
160     if (hToken)
161         ImpersonatePrinterClient(hToken);
162 
163     return dwErrorCode;
164 }
165 
166 static DWORD
167 _HandleDeletePort(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
168 {
169     DWORD res;
170     HKEY hroot;
171     HKEY hToken = NULL;
172     PLOCALMON_HANDLE pLocalmon = pXcv->pLocalmon;
173     PLOCALMON_PORT pPort = NULL;
174     PLIST_ENTRY pEntry;
175     PWSTR pPortName = (PWSTR)pInputData;
176 
177     FIXME("LcmXcvDeletePort : %s\n", debugstr_w( pPortName ) );
178 
179     if (!pLocalmon )
180     {
181         res = ERROR_INVALID_PARAMETER;
182         goto Cleanup;
183     }
184 
185     // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
186     if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
187     {
188         res = ERROR_ACCESS_DENIED;
189         goto Cleanup;
190     }
191 
192     // Switch to the SYSTEM context for modifying the registry.
193     hToken = RevertToPrinterSelf();
194     if (!hToken)
195     {
196         res = GetLastError();
197         ERR("RevertToPrinterSelf failed with error %lu!\n", res);
198         goto Cleanup;
199     }
200 
201     res = RegOpenKeyW(HKEY_LOCAL_MACHINE, L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Ports", &hroot);
202     if ( res == ERROR_SUCCESS )
203     {
204         res = RegDeleteValueW(hroot, pPortName );
205 
206         RegCloseKey(hroot);
207 
208         if ( res == ERROR_SUCCESS )
209         {
210             EnterCriticalSection(&pLocalmon->Section);
211 
212             if (!IsListEmpty(&pLocalmon->RegistryPorts) )
213             {
214                 for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
215                 {
216                     pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
217 
218                     if (wcscmp(pPort->pwszPortName, pPortName) == 0)
219                         break;
220                 }
221             }
222 
223             LeaveCriticalSection(&pLocalmon->Section);
224 
225             if ( pPort )
226             {
227                 FIXME("LcmXcvDeletePort removed Port Entry\n");
228                 EnterCriticalSection(&pPort->pLocalmon->Section);
229                 RemoveEntryList(&pPort->Entry);
230                 LeaveCriticalSection(&pPort->pLocalmon->Section);
231 
232                 DllFreeSplMem(pPort);
233             }
234         }
235         FIXME("LcmXcvDeletePort => %u with %u\n", res, GetLastError() );
236     }
237 
238 Cleanup:
239     if (hToken) ImpersonatePrinterClient(hToken);
240     SetLastError(res);
241     return res;
242 }
243 
244 /**
245  * @name _HandleGetDefaultCommConfig
246  *
247  * Gets the default configuration of a legacy port.
248  * The opposite function is _HandleSetDefaultCommConfig.
249  *
250  * @param pInputData
251  * The port name (without colon!) whose default configuration you want to get.
252  *
253  * @param pOutputData
254  * Pointer to a COMMCONFIG structure that will receive the configuration information.
255  *
256  * @param cbOutputData
257  * Size of the variable pointed to by pOutputData.
258  *
259  * @param pcbOutputNeeded
260  * Pointer to a DWORD that contains the required size for pOutputData on return.
261  *
262  * @return
263  * An error code indicating success or failure.
264  */
265 static DWORD
266 _HandleGetDefaultCommConfig(PBYTE pInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
267 {
268     // Sanity checks
269     if (!pInputData || !pcbOutputNeeded)
270         return ERROR_INVALID_PARAMETER;
271 
272     *pcbOutputNeeded = sizeof(COMMCONFIG);
273 
274     // Check if the supplied buffer is large enough.
275     if (cbOutputData < *pcbOutputNeeded)
276         return ERROR_INSUFFICIENT_BUFFER;
277 
278     // Finally get the port configuration.
279     if (!GetDefaultCommConfigW((PCWSTR)pInputData, (LPCOMMCONFIG)pOutputData, pcbOutputNeeded))
280         return GetLastError();
281 
282     return ERROR_SUCCESS;
283 }
284 
285 /**
286  * @name _HandleGetTransmissionRetryTimeout
287  *
288  * Reads the value for "TransmissionRetryTimeout" from the registry and converts it to a DWORD.
289  * The opposite function is _HandleConfigureLPTPortCommandOK.
290  *
291  * @param pOutputData
292  * Pointer to a DWORD that will receive the timeout value.
293  *
294  * @param cbOutputData
295  * Size of the variable pointed to by pOutputData.
296  *
297  * @param pcbOutputNeeded
298  * Pointer to a DWORD that contains the required size for pOutputData on return.
299  *
300  * @return
301  * An error code indicating success or failure.
302  */
303 static DWORD
304 _HandleGetTransmissionRetryTimeout(PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
305 {
306     DWORD dwTimeout;
307 
308     // Sanity checks
309     if (!pOutputData || !pcbOutputNeeded)
310         return ERROR_INVALID_PARAMETER;
311 
312     *pcbOutputNeeded = sizeof(DWORD);
313 
314     // Check if the supplied buffer is large enough.
315     if (cbOutputData < *pcbOutputNeeded)
316         return ERROR_INSUFFICIENT_BUFFER;
317 
318     // Retrieve and copy the number.
319     dwTimeout = GetLPTTransmissionRetryTimeout();
320     CopyMemory(pOutputData, &dwTimeout, sizeof(DWORD));
321     return ERROR_SUCCESS;
322 }
323 
324 /**
325  * @name _HandleMonitorUI
326  *
327  * Returns the filename of the associated UI DLL for this Port Monitor.
328  *
329  * @param pOutputData
330  * Pointer to a Unicode string that will receive the DLL filename.
331  *
332  * @param cbOutputData
333  * Size of the variable pointed to by pOutputData.
334  *
335  * @param pcbOutputNeeded
336  * Pointer to a DWORD that contains the required size for pOutputData on return.
337  *
338  * @return
339  * An error code indicating success or failure.
340  */
341 static DWORD
342 _HandleMonitorUI(PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
343 {
344     const WCHAR wszMonitorUI[] = L"LocalUI.dll";
345 
346     // Sanity checks
347     if (!pcbOutputNeeded)
348         return ERROR_INVALID_PARAMETER;
349 
350     *pcbOutputNeeded = sizeof(wszMonitorUI);
351 
352     // Check if the supplied buffer is large enough.
353     if (cbOutputData < *pcbOutputNeeded)
354         return ERROR_INSUFFICIENT_BUFFER;
355 
356     if (!pOutputData)
357         return ERROR_INVALID_PARAMETER;
358 
359     // Copy the string.
360     CopyMemory(pOutputData, wszMonitorUI, sizeof(wszMonitorUI));
361     return ERROR_SUCCESS;
362 }
363 
364 /**
365  * @name _HandlePortExists
366  *
367  * Checks all Port Monitors installed on the local system to find out if a given port already exists.
368  *
369  * @param pInputData
370  * Pointer to a Unicode string specifying the port name to check.
371  *
372  * @param pOutputData
373  * Pointer to a BOOL that receives the result of the check.
374  *
375  * @param cbOutputData
376  * Size of the variable pointed to by pOutputData.
377  *
378  * @param pcbOutputNeeded
379  * Pointer to a DWORD that contains the required size for pOutputData on return.
380  *
381  * @return
382  * An error code indicating success or failure.
383  */
384 static DWORD
385 _HandlePortExists(PBYTE pInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
386 {
387     // Sanity checks
388     if (!pInputData || !pOutputData || !pcbOutputNeeded)
389         return ERROR_INVALID_PARAMETER;
390 
391     *pcbOutputNeeded = sizeof(BOOL);
392 
393     // Check if the supplied buffer is large enough.
394     if (cbOutputData < *pcbOutputNeeded)
395         return ERROR_INSUFFICIENT_BUFFER;
396 
397     // Return the check result and error code.
398     *(PBOOL)pOutputData = DoesPortExist((PCWSTR)pInputData);
399     return GetLastError();
400 }
401 
402 static DWORD
403 _HandlePortIsValid(PBYTE pInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
404 {
405     DWORD res;
406 
407     TRACE("HandlePortIsValid : pInputData %s\n", debugstr_w( (LPWSTR) pInputData));
408 
409     res = GetTypeFromName((LPCWSTR) pInputData);
410 
411     TRACE("HandlePortIsValid : detected as %u\n",  res);
412 
413     /* names, that we have recognized, are valid */
414     if (res) return ERROR_SUCCESS;
415 
416     TRACE("=> %u\n", GetLastError());
417 
418     /* ERROR_ACCESS_DENIED, ERROR_PATH_NOT_FOUND or something else */
419     return GetLastError();
420 }
421 
422 /**
423  * @name _HandleSetDefaultCommConfig
424  *
425  * Sets the default configuration of a legacy port. Checks for granted SERVER_ACCESS_ADMINISTER access.
426  * You have to supply the port name (with colon!) in XcvOpenPort.
427  * The opposite function is _HandleGetDefaultCommConfig.
428  *
429  * @param pXcv
430  * Pointer to the LOCALMON_XCV structure of the currently opened Xcv port.
431  *
432  * @param pInputData
433  * Pointer to the COMMCONFIG structure that shall be passed to SetDefaultCommConfigW.
434  *
435  * @param pcbOutputNeeded
436  * Pointer to a DWORD that will be zeroed on return.
437  *
438  * @return
439  * An error code indicating success or failure.
440  */
441 static DWORD
442 _HandleSetDefaultCommConfig(PLOCALMON_XCV pXcv, PBYTE pInputData, PDWORD pcbOutputNeeded)
443 {
444     DWORD dwErrorCode;
445     HANDLE hToken = NULL;
446     LPCOMMCONFIG pCommConfig;
447     PWSTR pwszPortNameWithoutColon = NULL;
448 
449     // Sanity checks
450     // pwszObject needs to be at least 2 characters long to be a port name with a trailing colon.
451     if (!pXcv || !pXcv->pwszObject || !pXcv->pwszObject[0] || !pXcv->pwszObject[1] || !pInputData || !pcbOutputNeeded)
452     {
453         dwErrorCode = ERROR_INVALID_PARAMETER;
454         goto Cleanup;
455     }
456 
457     *pcbOutputNeeded = 0;
458 
459     // This action can only happen at SERVER_ACCESS_ADMINISTER access level.
460     if (!(pXcv->GrantedAccess & SERVER_ACCESS_ADMINISTER))
461     {
462         dwErrorCode = ERROR_ACCESS_DENIED;
463         goto Cleanup;
464     }
465 
466     // SetDefaultCommConfigW needs the port name without colon.
467     dwErrorCode = GetPortNameWithoutColon(pXcv->pwszObject, &pwszPortNameWithoutColon);
468     if (dwErrorCode != ERROR_SUCCESS)
469         goto Cleanup;
470 
471     // Switch to the SYSTEM context for setting the port configuration.
472     hToken = RevertToPrinterSelf();
473     if (!hToken)
474     {
475         dwErrorCode = GetLastError();
476         ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
477         goto Cleanup;
478     }
479 
480     // Finally pass the parameters to SetDefaultCommConfigW.
481     pCommConfig = (LPCOMMCONFIG)pInputData;
482     if (!SetDefaultCommConfigW(pwszPortNameWithoutColon, pCommConfig, pCommConfig->dwSize))
483     {
484         dwErrorCode = GetLastError();
485         ERR("SetDefaultCommConfigW failed with error %lu!\n", dwErrorCode);
486         goto Cleanup;
487     }
488 
489     dwErrorCode = ERROR_SUCCESS;
490 
491 Cleanup:
492     if (hToken)
493         ImpersonatePrinterClient(hToken);
494 
495     if (pwszPortNameWithoutColon)
496         DllFreeSplMem(pwszPortNameWithoutColon);
497 
498     return dwErrorCode;
499 }
500 
501 BOOL WINAPI
502 LocalmonXcvClosePort(HANDLE hXcv)
503 {
504     PLOCALMON_XCV pXcv = (PLOCALMON_XCV)hXcv;
505 
506     TRACE("LocalmonXcvClosePort(%p)\n", hXcv);
507 
508     // Sanity checks
509     if (!pXcv)
510     {
511         SetLastError(ERROR_INVALID_PARAMETER);
512         return FALSE;
513     }
514 
515     // Remove it from the list and free the memory.
516     EnterCriticalSection(&pXcv->pLocalmon->Section);
517     RemoveEntryList(&pXcv->Entry);
518     LeaveCriticalSection(&pXcv->pLocalmon->Section);
519     DllFreeSplMem(pXcv);
520 
521     SetLastError(ERROR_SUCCESS);
522     return TRUE;
523 }
524 
525 DWORD WINAPI
526 LocalmonXcvDataPort(HANDLE hXcv, PCWSTR pszDataName, PBYTE pInputData, DWORD cbInputData, PBYTE pOutputData, DWORD cbOutputData, PDWORD pcbOutputNeeded)
527 {
528     FIXME("LocalmonXcvDataPort(%p, %S, %p, %lu, %p, %lu, %p)\n", hXcv, pszDataName, pInputData, cbInputData, pOutputData, cbOutputData, pcbOutputNeeded);
529 
530     // Sanity checks
531     if (!pszDataName)
532         return ERROR_INVALID_PARAMETER;
533 
534     // Call the appropriate handler for the requested data name.
535     if (wcscmp(pszDataName, L"AddPort") == 0)
536         return _HandleAddPort((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
537 
538     if (wcscmp(pszDataName, L"ConfigureLPTPortCommandOK") == 0)
539         return _HandleConfigureLPTPortCommandOK((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
540 
541     if (wcscmp(pszDataName, L"DeletePort") == 0)
542         return _HandleDeletePort((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
543 
544     if (wcscmp(pszDataName, L"GetDefaultCommConfig") == 0)
545         return _HandleGetDefaultCommConfig(pInputData, pOutputData, cbOutputData, pcbOutputNeeded);
546 
547     if (wcscmp(pszDataName, L"GetTransmissionRetryTimeout") == 0)
548         return _HandleGetTransmissionRetryTimeout(pOutputData, cbOutputData, pcbOutputNeeded);
549 
550     if (wcscmp(pszDataName, L"MonitorUI") == 0)
551         return _HandleMonitorUI(pOutputData, cbOutputData, pcbOutputNeeded);
552 
553     if (wcscmp(pszDataName, L"PortExists") == 0)
554         return _HandlePortExists(pInputData, pOutputData, cbOutputData, pcbOutputNeeded);
555 
556     if (wcscmp(pszDataName, L"PortIsValid") == 0)
557         return _HandlePortIsValid(pInputData, pOutputData, cbOutputData, pcbOutputNeeded);
558 
559     if (wcscmp(pszDataName, L"SetDefaultCommConfig") == 0)
560         return _HandleSetDefaultCommConfig((PLOCALMON_XCV)hXcv, pInputData, pcbOutputNeeded);
561 
562     return ERROR_INVALID_PARAMETER;
563 }
564 
565 BOOL WINAPI
566 LocalmonXcvOpenPort(HANDLE hMonitor, PCWSTR pwszObject, ACCESS_MASK GrantedAccess, PHANDLE phXcv)
567 {
568     DWORD cbObject = 0;
569     DWORD dwErrorCode;
570     PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
571     PLOCALMON_XCV pXcv;
572 
573     FIXME("LocalmonXcvOpenPort(%p, %S, %lu, %p)\n", hMonitor, pwszObject, GrantedAccess, phXcv);
574 
575     // Sanity checks
576     if (!pLocalmon || !phXcv)
577     {
578         dwErrorCode = ERROR_INVALID_PARAMETER;
579         goto Cleanup;
580     }
581 
582     if (pwszObject)
583         cbObject = (wcslen(pwszObject) + 1) * sizeof(WCHAR);
584 
585     // Create a new LOCALMON_XCV structure and fill the relevant fields.
586     pXcv = DllAllocSplMem(sizeof(LOCALMON_XCV) + cbObject);
587     if (!pXcv)
588     {
589         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
590         ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
591         goto Cleanup;
592     }
593 
594     pXcv->pLocalmon = pLocalmon;
595     pXcv->GrantedAccess = GrantedAccess;
596 
597     if (cbObject)
598     {
599         pXcv->pwszObject = (PWSTR)((PBYTE)pXcv + sizeof(LOCALMON_XCV));
600         CopyMemory(pXcv->pwszObject, pwszObject, cbObject);
601     }
602 
603     InsertTailList(&pLocalmon->XcvHandles, &pXcv->Entry);
604 
605     // Return it as the Xcv handle.
606     *phXcv = (HANDLE)pXcv;
607     dwErrorCode = ERROR_SUCCESS;
608 
609 Cleanup:
610     SetLastError(dwErrorCode);
611     return (dwErrorCode == ERROR_SUCCESS);
612 }
613