1 /*
2  * PROJECT:     ReactOS Local Port Monitor
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions related to ports
5  * COPYRIGHT:   Copyright 2015-2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 // Local Constants
11 static const WCHAR wszNonspooledPrefix[] = L"NONSPOOLED_";
12 static const DWORD cchNonspooledPrefix = _countof(wszNonspooledPrefix) - 1;
13 
14 static DWORD dwPortInfo1Offsets[] = {
15     FIELD_OFFSET(PORT_INFO_1W, pName),
16     MAXDWORD
17 };
18 
19 static DWORD dwPortInfo2Offsets[] = {
20     FIELD_OFFSET(PORT_INFO_2W, pPortName),
21     FIELD_OFFSET(PORT_INFO_2W, pMonitorName),
22     FIELD_OFFSET(PORT_INFO_2W, pDescription),
23     MAXDWORD
24 };
25 
26 
27 /**
28  * @name _GetNonspooledPortName
29  *
30  * Prepends "NONSPOOLED_" to a port name without colon.
31  *
32  * @param pwszPortNameWithoutColon
33  * Result of a previous GetPortNameWithoutColon call.
34  *
35  * @param ppwszNonspooledPortName
36  * Pointer to a buffer that will contain the NONSPOOLED port name.
37  * You have to free this buffer using DllFreeSplMem.
38  *
39  * @return
40  * ERROR_SUCCESS if the NONSPOOLED port name was successfully copied into the buffer.
41  * ERROR_NOT_ENOUGH_MEMORY if memory allocation failed.
42  */
43 static __inline DWORD
44 _GetNonspooledPortName(PCWSTR pwszPortNameWithoutColon, PWSTR* ppwszNonspooledPortName)
45 {
46     DWORD cchPortNameWithoutColon;
47 
48     cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon);
49 
50     *ppwszNonspooledPortName = DllAllocSplMem((cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
51     if (!*ppwszNonspooledPortName)
52     {
53         ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
54         return ERROR_NOT_ENOUGH_MEMORY;
55     }
56 
57     CopyMemory(*ppwszNonspooledPortName, wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR));
58     CopyMemory(&(*ppwszNonspooledPortName)[cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
59 
60     return ERROR_SUCCESS;
61 }
62 
63 /**
64  * @name _IsLegacyPort
65  *
66  * Checks if the given port name is a legacy port (COM or LPT).
67  * This check is extra picky to not cause false positives (like file name ports starting with "COM" or "LPT").
68  *
69  * @param pwszPortName
70  * The port name to check.
71  *
72  * @param pwszPortType
73  * L"COM" or L"LPT"
74  *
75  * @return
76  * TRUE if this is definitely the asked legacy port, FALSE if not.
77  */
78 static __inline BOOL
79 _IsLegacyPort(PCWSTR pwszPortName, PCWSTR pwszPortType)
80 {
81     const DWORD cchPortType = 3;
82     PCWSTR p = pwszPortName;
83 
84     // The port name must begin with pwszPortType.
85     if (_wcsnicmp(p, pwszPortType, cchPortType) != 0)
86         return FALSE;
87 
88     p += cchPortType;
89 
90     // Now an arbitrary number of digits may follow.
91     while (*p >= L'0' && *p <= L'9')
92         p++;
93 
94     // Finally, the legacy port must be terminated by a colon.
95     if (*p != ':')
96         return FALSE;
97 
98     // If this is the end of the string, we have a legacy port.
99     p++;
100     return (*p == L'\0');
101 }
102 
103 /**
104  * @name _ClosePortHandles
105  *
106  * Closes a port of any type if it's open.
107  * Removes any saved mapping or existing definition of a NONSPOOLED device mapping.
108  *
109  * @param pPort
110  * The port you want to close.
111  */
112 static void
113 _ClosePortHandles(PLOCALMON_PORT pPort)
114 {
115     PWSTR pwszNonspooledPortName;
116     PWSTR pwszPortNameWithoutColon;
117 
118     // A port is already fully closed if the file handle is invalid.
119     if (pPort->hFile == INVALID_HANDLE_VALUE)
120         return;
121 
122     // Close the file handle.
123     CloseHandle(pPort->hFile);
124     pPort->hFile = INVALID_HANDLE_VALUE;
125 
126     // A NONSPOOLED port was only created if pwszMapping contains the current port mapping.
127     if (!pPort->pwszMapping)
128         return;
129 
130     // Free the information about the current mapping.
131     DllFreeSplStr(pPort->pwszMapping);
132     pPort->pwszMapping = NULL;
133 
134     // Finally get the required strings and remove the DOS device definition for the NONSPOOLED port.
135     if (GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon) == ERROR_SUCCESS)
136     {
137         if (_GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName) == ERROR_SUCCESS)
138         {
139             DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL);
140             DllFreeSplMem(pwszNonspooledPortName);
141         }
142 
143         DllFreeSplMem(pwszPortNameWithoutColon);
144     }
145 }
146 
147 /**
148  * @name _CreateNonspooledPort
149  *
150  * Queries the system-wide device definition of the given port.
151  * If such a definition exists, it's a legacy port remapped to a named pipe by the spooler.
152  * 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).
153  *
154  * @param pPort
155  * Pointer to the LOCALMON_PORT structure of the desired port.
156  *
157  * @return
158  * TRUE if a NONSPOOLED port was successfully created, FALSE otherwise.
159  * A more specific error code can be obtained through GetLastError.
160  * In particular, if the return value is FALSE and GetLastError returns ERROR_SUCCESS, no NONSPOOLED port is needed for this port.
161  */
162 static BOOL
163 _CreateNonspooledPort(PLOCALMON_PORT pPort)
164 {
165     const WCHAR wszLocalSlashes[] = L"\\\\.\\";
166     const DWORD cchLocalSlashes = _countof(wszLocalSlashes) - 1;
167 
168     const WCHAR wszSpoolerNamedPipe[] = L"\\Device\\NamedPipe\\Spooler\\";
169     const DWORD cchSpoolerNamedPipe = _countof(wszSpoolerNamedPipe) - 1;
170 
171     BOOL bReturnValue = FALSE;
172     DWORD cchPortNameWithoutColon;
173     DWORD dwErrorCode;
174     HANDLE hToken = NULL;
175     PWSTR p;
176     PWSTR pwszDeviceMappings = NULL;
177     PWSTR pwszNonspooledFileName = NULL;
178     PWSTR pwszNonspooledPortName = NULL;
179     PWSTR pwszPipeName = NULL;
180     PWSTR pwszPortNameWithoutColon = NULL;
181 
182     // We need the port name without the trailing colon.
183     dwErrorCode = GetPortNameWithoutColon(pPort->pwszPortName, &pwszPortNameWithoutColon);
184     if (dwErrorCode == ERROR_INVALID_PARAMETER)
185     {
186         // This port has no trailing colon, so we also need no NONSPOOLED mapping for it.
187         dwErrorCode = ERROR_SUCCESS;
188         goto Cleanup;
189     }
190     else if (dwErrorCode != ERROR_SUCCESS)
191     {
192         // Another unexpected failure.
193         goto Cleanup;
194     }
195 
196     cchPortNameWithoutColon = wcslen(pwszPortNameWithoutColon);
197 
198     // The spooler has usually remapped the legacy port to a named pipe of the format in wszSpoolerNamedPipe.
199     // Construct the device name of this pipe.
200     pwszPipeName = DllAllocSplMem((cchSpoolerNamedPipe + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
201     if (!pwszPipeName)
202     {
203         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
204         ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
205         goto Cleanup;
206     }
207 
208     CopyMemory(pwszPipeName, wszSpoolerNamedPipe, cchSpoolerNamedPipe * sizeof(WCHAR));
209     CopyMemory(&pwszPipeName[cchSpoolerNamedPipe], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
210 
211     // 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.
212     // Examples show that a value of MAX_PATH * sizeof(WCHAR) is usually taken here, so we have no other option either.
213     pwszDeviceMappings = DllAllocSplMem(MAX_PATH * sizeof(WCHAR));
214     if (!pwszDeviceMappings)
215     {
216         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
217         ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
218         goto Cleanup;
219     }
220 
221     // Switch to the SYSTEM context, because we're only interested in creating NONSPOOLED ports for system-wide ports.
222     // User-local ports (like _some_ redirected networked ones) aren't remapped by the spooler and can be opened directly.
223     hToken = RevertToPrinterSelf();
224     if (!hToken)
225     {
226         dwErrorCode = GetLastError();
227         ERR("RevertToPrinterSelf failed with error %lu!\n", dwErrorCode);
228         goto Cleanup;
229     }
230 
231     // 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.
232     if (!QueryDosDeviceW(pwszPortNameWithoutColon, pwszDeviceMappings, MAX_PATH))
233     {
234         // No system-wide port exists, so we also need no NONSPOOLED mapping.
235         dwErrorCode = ERROR_SUCCESS;
236         goto Cleanup;
237     }
238 
239     // Check if this port has already been opened by _CreateNonspooledPort previously.
240     if (pPort->pwszMapping)
241     {
242         // In this case, we just need to do something if the mapping has changed.
243         // Therefore, check if the stored mapping equals the current mapping.
244         if (wcscmp(pPort->pwszMapping, pwszDeviceMappings) == 0)
245         {
246             // We don't need to do anything in this case.
247             dwErrorCode = ERROR_SUCCESS;
248             goto Cleanup;
249         }
250         else
251         {
252             // Close the open file handle and free the memory for pwszMapping before remapping.
253             CloseHandle(pPort->hFile);
254             pPort->hFile = INVALID_HANDLE_VALUE;
255 
256             DllFreeSplStr(pPort->pwszMapping);
257             pPort->pwszMapping = NULL;
258         }
259     }
260 
261     // The port is usually mapped to the named pipe and this is how we received our data for printing.
262     // What we now need for accessing the actual port is the most recent mapping different from the named pipe.
263     p = pwszDeviceMappings;
264 
265     for (;;)
266     {
267         if (!*p)
268         {
269             // We reached the end of the list without finding a mapping.
270             ERR("Can't find a suitable mapping for the port \"%S\"!", pPort->pwszPortName);
271             goto Cleanup;
272         }
273 
274         if (_wcsicmp(p, pwszPipeName) != 0)
275             break;
276 
277         // Advance to the next mapping in the list.
278         p += wcslen(p) + 1;
279     }
280 
281     // We now want to create a DOS device "NONSPOOLED_<PortName>" to this mapping, so that we're able to open it through CreateFileW.
282     dwErrorCode = _GetNonspooledPortName(pwszPortNameWithoutColon, &pwszNonspooledPortName);
283     if (dwErrorCode != ERROR_SUCCESS)
284         goto Cleanup;
285 
286     // Delete a possibly existing NONSPOOLED device before creating the new one, so we don't stack up device definitions.
287     DefineDosDeviceW(DDD_REMOVE_DEFINITION, pwszNonspooledPortName, NULL);
288 
289     if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH, pwszNonspooledPortName, p))
290     {
291         dwErrorCode = GetLastError();
292         ERR("DefineDosDeviceW failed with error %lu!\n", dwErrorCode);
293         goto Cleanup;
294     }
295 
296     // This is all we needed to do in SYSTEM context.
297     ImpersonatePrinterClient(hToken);
298     hToken = NULL;
299 
300     // Construct the file name to our created device for CreateFileW.
301     pwszNonspooledFileName = DllAllocSplMem((cchLocalSlashes + cchNonspooledPrefix + cchPortNameWithoutColon + 1) * sizeof(WCHAR));
302     if (!pwszNonspooledFileName)
303     {
304         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
305         ERR("DllAllocSplMem failed with error %lu!\n", GetLastError());
306         goto Cleanup;
307     }
308 
309     CopyMemory(pwszNonspooledFileName, wszLocalSlashes, cchLocalSlashes * sizeof(WCHAR));
310     CopyMemory(&pwszNonspooledFileName[cchLocalSlashes], wszNonspooledPrefix, cchNonspooledPrefix * sizeof(WCHAR));
311     CopyMemory(&pwszNonspooledFileName[cchLocalSlashes + cchNonspooledPrefix], pwszPortNameWithoutColon, (cchPortNameWithoutColon + 1) * sizeof(WCHAR));
312 
313     // Finally open it for reading and writing.
314     pPort->hFile = CreateFileW(pwszNonspooledFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
315     if (pPort->hFile == INVALID_HANDLE_VALUE)
316     {
317         dwErrorCode = GetLastError();
318         ERR("CreateFileW failed with error %lu!\n", dwErrorCode);
319         goto Cleanup;
320     }
321 
322     // Store the current mapping of the port, so that we can check if it has changed.
323     pPort->pwszMapping = AllocSplStr(pwszDeviceMappings);
324     if (!pPort->pwszMapping)
325     {
326         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
327         goto Cleanup;
328     }
329 
330     bReturnValue = TRUE;
331     dwErrorCode = ERROR_SUCCESS;
332 
333 Cleanup:
334     if (hToken)
335         ImpersonatePrinterClient(hToken);
336 
337     if (pwszDeviceMappings)
338         DllFreeSplMem(pwszDeviceMappings);
339 
340     if (pwszNonspooledFileName)
341         DllFreeSplMem(pwszNonspooledFileName);
342 
343     if (pwszNonspooledPortName)
344         DllFreeSplMem(pwszNonspooledPortName);
345 
346     if (pwszPipeName)
347         DllFreeSplMem(pwszPipeName);
348 
349     if (pwszPortNameWithoutColon)
350         DllFreeSplMem(pwszPortNameWithoutColon);
351 
352     SetLastError(dwErrorCode);
353     return bReturnValue;
354 }
355 
356 static PLOCALMON_PORT
357 _FindPort(PLOCALMON_HANDLE pLocalmon, PCWSTR pwszPortName)
358 {
359     PLIST_ENTRY pEntry;
360     PLOCALMON_PORT pPort;
361 
362     for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
363     {
364         pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
365 
366         if (wcscmp(pPort->pwszPortName, pwszPortName) == 0)
367             return pPort;
368     }
369 
370     return NULL;
371 }
372 
373 static void
374 _LocalmonGetPortLevel1(PLOCALMON_PORT pPort, PPORT_INFO_1W* ppPortInfo, PBYTE* ppPortInfoEnd, PDWORD pcbNeeded)
375 {
376     DWORD cbPortName;
377     PCWSTR pwszStrings[1];
378 
379     // Calculate the string lengths.
380     if (!ppPortInfo)
381     {
382         cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
383 
384         *pcbNeeded += sizeof(PORT_INFO_1W) + cbPortName;
385         return;
386     }
387 
388     // Set the pName field.
389     pwszStrings[0] = pPort->pwszPortName;
390 
391     // Copy the structure and advance to the next one in the output buffer.
392     *ppPortInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppPortInfo), dwPortInfo1Offsets, *ppPortInfoEnd);
393     (*ppPortInfo)++;
394 }
395 
396 static void
397 _LocalmonGetPortLevel2(PLOCALMON_PORT pPort, PPORT_INFO_2W* ppPortInfo, PBYTE* ppPortInfoEnd, PDWORD pcbNeeded)
398 {
399     DWORD cbPortName;
400     PCWSTR pwszStrings[3];
401 
402     // Calculate the string lengths.
403     if (!ppPortInfo)
404     {
405         cbPortName = (wcslen(pPort->pwszPortName) + 1) * sizeof(WCHAR);
406 
407         *pcbNeeded += sizeof(PORT_INFO_2W) + cbPortName + cbLocalMonitor + cbLocalPort;
408         return;
409     }
410 
411     // All local ports are writable and readable.
412     (*ppPortInfo)->fPortType = PORT_TYPE_WRITE | PORT_TYPE_READ;
413     (*ppPortInfo)->Reserved = 0;
414 
415     // Set the pPortName field.
416     pwszStrings[0] = pPort->pwszPortName;
417 
418     // Set the pMonitorName field.
419     pwszStrings[1] = (PWSTR)pwszLocalMonitor;
420 
421     // Set the pDescription field.
422     pwszStrings[2] = (PWSTR)pwszLocalPort;
423 
424     // Copy the structure and advance to the next one in the output buffer.
425     *ppPortInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppPortInfo), dwPortInfo2Offsets, *ppPortInfoEnd);
426     (*ppPortInfo)++;
427 }
428 
429 /**
430  * @name _SetTransmissionRetryTimeout
431  *
432  * Checks if the given port is a physical one and sets the transmission retry timeout in this case using the value from registry.
433  *
434  * @param pPort
435  * The port to operate on.
436  *
437  * @return
438  * TRUE if the given port is a physical one, FALSE otherwise.
439  */
440 static BOOL
441 _SetTransmissionRetryTimeout(PLOCALMON_PORT pPort)
442 {
443     COMMTIMEOUTS CommTimeouts;
444 
445     // Get the timeout from the port.
446     if (!GetCommTimeouts(pPort->hFile, &CommTimeouts))
447         return FALSE;
448 
449     // Set the timeout using the value from registry.
450     CommTimeouts.WriteTotalTimeoutConstant = GetLPTTransmissionRetryTimeout() * 1000;
451     SetCommTimeouts(pPort->hFile, &CommTimeouts);
452 
453     return TRUE;
454 }
455 
456 BOOL WINAPI
457 LocalmonClosePort(HANDLE hPort)
458 {
459     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
460 
461     TRACE("LocalmonClosePort(%p)\n", hPort);
462 
463     // Sanity checks
464     if (!pPort)
465     {
466         SetLastError(ERROR_INVALID_PARAMETER);
467         return FALSE;
468     }
469 
470     // Close the file handle, free memory for pwszMapping and delete any NONSPOOLED port.
471     _ClosePortHandles(pPort);
472 
473     // Close any open printer handle.
474     if (pPort->hPrinter)
475     {
476         ClosePrinter(pPort->hPrinter);
477         pPort->hPrinter = NULL;
478     }
479 
480     // Free virtual FILE: ports which were created in LocalmonOpenPort.
481     if (pPort->PortType == PortType_FILE)
482     {
483         EnterCriticalSection(&pPort->pLocalmon->Section);
484         RemoveEntryList(&pPort->Entry);
485         LeaveCriticalSection(&pPort->pLocalmon->Section);
486         DllFreeSplMem(pPort);
487     }
488 
489     SetLastError(ERROR_SUCCESS);
490     return TRUE;
491 }
492 
493 BOOL WINAPI
494 LocalmonEndDocPort(HANDLE hPort)
495 {
496     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
497 
498     TRACE("LocalmonEndDocPort(%p)\n", hPort);
499 
500     // Sanity checks
501     if (!pPort)
502     {
503         SetLastError(ERROR_INVALID_PARAMETER);
504         return FALSE;
505     }
506 
507     // Ending a document requires starting it first :-P
508     if (pPort->bStartedDoc)
509     {
510         // Close all ports opened in StartDocPort.
511         // That is, all but physical LPT ports (opened in OpenPort).
512         if (pPort->PortType != PortType_PhysicalLPT)
513             _ClosePortHandles(pPort);
514 
515         // Report our progress.
516         SetJobW(pPort->hPrinter, pPort->dwJobID, 0, NULL, JOB_CONTROL_SENT_TO_PRINTER);
517 
518         // We're done with the printer.
519         ClosePrinter(pPort->hPrinter);
520         pPort->hPrinter = NULL;
521 
522         // A new document can now be started again.
523         pPort->bStartedDoc = FALSE;
524     }
525 
526     SetLastError(ERROR_SUCCESS);
527     return TRUE;
528 }
529 
530 BOOL WINAPI
531 LocalmonEnumPorts(HANDLE hMonitor, PWSTR pName, DWORD Level, PBYTE pPorts, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
532 {
533     DWORD dwErrorCode;
534     PBYTE pPortInfoEnd;
535     PLIST_ENTRY pEntry;
536     PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
537     PLOCALMON_PORT pPort;
538 
539     TRACE("LocalmonEnumPorts(%p, %S, %lu, %p, %lu, %p, %p)\n", hMonitor, pName, Level, pPorts, cbBuf, pcbNeeded, pcReturned);
540 
541     // Windows Server 2003's Local Port Monitor does absolutely no sanity checks here, not even for the Level parameter.
542     // As we implement a more modern MONITOR2-based Port Monitor, check at least our hMonitor.
543     if (!pLocalmon)
544     {
545         dwErrorCode = ERROR_INVALID_HANDLE;
546         goto Cleanup;
547     }
548 
549     // Begin counting.
550     *pcbNeeded = 0;
551     *pcReturned = 0;
552 
553     EnterCriticalSection(&pLocalmon->Section);
554 
555     // Count the required buffer size and the number of ports.
556     for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
557     {
558         pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
559 
560         if (Level == 1)
561             _LocalmonGetPortLevel1(pPort, NULL, NULL, pcbNeeded);
562         else if (Level == 2)
563             _LocalmonGetPortLevel2(pPort, NULL, NULL, pcbNeeded);
564     }
565 
566     // Check if the supplied buffer is large enough.
567     if (cbBuf < *pcbNeeded)
568     {
569         LeaveCriticalSection(&pLocalmon->Section);
570         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
571         goto Cleanup;
572     }
573 
574     // Copy over the Port information.
575     pPortInfoEnd = &pPorts[*pcbNeeded];
576 
577     for (pEntry = pLocalmon->RegistryPorts.Flink; pEntry != &pLocalmon->RegistryPorts; pEntry = pEntry->Flink)
578     {
579         pPort = CONTAINING_RECORD(pEntry, LOCALMON_PORT, Entry);
580 
581         if (Level == 1)
582             _LocalmonGetPortLevel1(pPort, (PPORT_INFO_1W*)&pPorts, &pPortInfoEnd, NULL);
583         else if (Level == 2)
584             _LocalmonGetPortLevel2(pPort, (PPORT_INFO_2W*)&pPorts, &pPortInfoEnd, NULL);
585 
586         (*pcReturned)++;
587     }
588 
589     LeaveCriticalSection(&pLocalmon->Section);
590     dwErrorCode = ERROR_SUCCESS;
591 
592 Cleanup:
593     SetLastError(dwErrorCode);
594     return (dwErrorCode == ERROR_SUCCESS);
595 }
596 
597 /*
598  * @name LocalmonGetPrinterDataFromPort
599  *
600  * Performs a DeviceIoControl call for the given port.
601  *
602  * @param hPort
603  * The port to operate on.
604  *
605  * @param ControlID
606  * The dwIoControlCode passed to DeviceIoControl. Must not be zero!
607  *
608  * @param pValueName
609  * This parameter is ignored.
610  *
611  * @param lpInBuffer
612  * The lpInBuffer passed to DeviceIoControl.
613  *
614  * @param cbInBuffer
615  * The nInBufferSize passed to DeviceIoControl.
616  *
617  * @param lpOutBuffer
618  * The lpOutBuffer passed to DeviceIoControl.
619  *
620  * @param cbOutBuffer
621  * The nOutBufferSize passed to DeviceIoControl.
622  *
623  * @param lpcbReturned
624  * The lpBytesReturned passed to DeviceIoControl. Must not be zero!
625  *
626  * @return
627  * TRUE if the DeviceIoControl call was successful, FALSE otherwise.
628  * A more specific error code can be obtained through GetLastError.
629  */
630 BOOL WINAPI
631 LocalmonGetPrinterDataFromPort(HANDLE hPort, DWORD ControlID, PWSTR pValueName, PWSTR lpInBuffer, DWORD cbInBuffer, PWSTR lpOutBuffer, DWORD cbOutBuffer, PDWORD lpcbReturned)
632 {
633     BOOL bOpenedPort = FALSE;
634     DWORD dwErrorCode;
635     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
636 
637     TRACE("LocalmonGetPrinterDataFromPort(%p, %lu, %p, %p, %lu, %p, %lu, %p)\n", hPort, ControlID, pValueName, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned);
638 
639     // Sanity checks
640     if (!pPort || !ControlID || !lpcbReturned)
641     {
642         dwErrorCode = ERROR_INVALID_PARAMETER;
643         goto Cleanup;
644     }
645 
646     // If this is a serial port, a temporary file handle may be opened.
647     if (pPort->PortType == PortType_PhysicalCOM)
648     {
649         if (_CreateNonspooledPort(pPort))
650         {
651             bOpenedPort = TRUE;
652         }
653         else if (GetLastError() != ERROR_SUCCESS)
654         {
655             dwErrorCode = GetLastError();
656             goto Cleanup;
657         }
658     }
659     else if (pPort->hFile == INVALID_HANDLE_VALUE)
660     {
661         // All other port types need to be opened already.
662         dwErrorCode = ERROR_INVALID_PARAMETER;
663         goto Cleanup;
664     }
665 
666     // Pass the parameters to DeviceIoControl.
667     if (!DeviceIoControl(pPort->hFile, ControlID, lpInBuffer, cbInBuffer, lpOutBuffer, cbOutBuffer, lpcbReturned, NULL))
668     {
669         dwErrorCode = GetLastError();
670         ERR("DeviceIoControl failed with error %lu!\n", dwErrorCode);
671         goto Cleanup;
672     }
673 
674     dwErrorCode = ERROR_SUCCESS;
675 
676 Cleanup:
677     if (bOpenedPort)
678         _ClosePortHandles(pPort);
679 
680     SetLastError(dwErrorCode);
681     return (dwErrorCode == ERROR_SUCCESS);
682 }
683 
684 BOOL WINAPI
685 LocalmonOpenPort(HANDLE hMonitor, PWSTR pName, PHANDLE pHandle)
686 {
687     DWORD dwErrorCode;
688     PLOCALMON_HANDLE pLocalmon = (PLOCALMON_HANDLE)hMonitor;
689     PLOCALMON_PORT pPort;
690 
691     TRACE("LocalmonOpenPort(%p, %S, %p)\n", hMonitor, pName, pHandle);
692 
693     // Sanity checks
694     if (!pLocalmon || !pName || !pHandle)
695     {
696         dwErrorCode = ERROR_INVALID_PARAMETER;
697         goto Cleanup;
698     }
699 
700     EnterCriticalSection(&pLocalmon->Section);
701 
702     // Check if this is a FILE: port.
703     if (_wcsicmp(pName, L"FILE:") == 0)
704     {
705         // For FILE:, we create a virtual port for each request.
706         pPort = DllAllocSplMem(sizeof(LOCALMON_PORT));
707         pPort->pLocalmon = pLocalmon;
708         pPort->PortType = PortType_FILE;
709         pPort->hFile = INVALID_HANDLE_VALUE;
710 
711         // Add it to the list of file ports.
712         InsertTailList(&pLocalmon->FilePorts, &pPort->Entry);
713     }
714     else
715     {
716         // Check if the port name is valid.
717         pPort = _FindPort(pLocalmon, pName);
718         if (!pPort)
719         {
720             LeaveCriticalSection(&pLocalmon->Section);
721             dwErrorCode = ERROR_UNKNOWN_PORT;
722             goto Cleanup;
723         }
724 
725         // Even if this API is called OpenPort, port file handles aren't always opened here :-P
726         // Windows only does this for physical LPT ports here to enable bidirectional communication with the printer outside of jobs (using ReadPort and WritePort).
727         // The others are only opened per job in StartDocPort.
728         if (_IsLegacyPort(pName, L"LPT"))
729         {
730             // Try to create a NONSPOOLED port and open it.
731             if (_CreateNonspooledPort(pPort))
732             {
733                 // Set the transmission retry timeout for the ReadPort and WritePort calls.
734                 // This also checks if this port is a physical one.
735                 if (_SetTransmissionRetryTimeout(pPort))
736                 {
737                     // This is definitely a physical LPT port!
738                     pPort->PortType = PortType_PhysicalLPT;
739                 }
740                 else
741                 {
742                     // This is no physical port, so don't keep its handle open.
743                     _ClosePortHandles(pPort);
744                 }
745             }
746             else if (GetLastError() != ERROR_SUCCESS)
747             {
748                 LeaveCriticalSection(&pLocalmon->Section);
749                 dwErrorCode = GetLastError();
750                 goto Cleanup;
751             }
752         }
753         else if (_IsLegacyPort(pName, L"COM"))
754         {
755             // COM ports can't be redirected over the network, so this is a physical one.
756             pPort->PortType = PortType_PhysicalCOM;
757         }
758     }
759 
760     LeaveCriticalSection(&pLocalmon->Section);
761 
762     // Return our fetched LOCALMON_PORT structure in the handle.
763     *pHandle = (PHANDLE)pPort;
764     dwErrorCode = ERROR_SUCCESS;
765 
766 Cleanup:
767     SetLastError(dwErrorCode);
768     return (dwErrorCode == ERROR_SUCCESS);
769 }
770 
771 /*
772  * @name LocalmonSetPortTimeOuts
773  *
774  * Performs a SetCommTimeouts call for the given port.
775  *
776  * @param hPort
777  * The port to operate on.
778  *
779  * @param lpCTO
780  * Pointer to a COMMTIMEOUTS structure that is passed to SetCommTimeouts.
781  *
782  * @param Reserved
783  * Reserved parameter, must be 0.
784  *
785  * @return
786  * TRUE if the SetCommTimeouts call was successful, FALSE otherwise.
787  * A more specific error code can be obtained through GetLastError.
788  */
789 BOOL WINAPI
790 LocalmonSetPortTimeOuts(HANDLE hPort, LPCOMMTIMEOUTS lpCTO, DWORD Reserved)
791 {
792     BOOL bOpenedPort = FALSE;
793     DWORD dwErrorCode;
794     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
795 
796     TRACE("LocalmonSetPortTimeOuts(%p, %p, %lu)\n", hPort, lpCTO, Reserved);
797 
798     // Sanity checks
799     if (!pPort || !lpCTO)
800     {
801         dwErrorCode = ERROR_INVALID_PARAMETER;
802         goto Cleanup;
803     }
804 
805     // If this is a serial port, a temporary file handle may be opened.
806     if (pPort->PortType == PortType_PhysicalCOM)
807     {
808         if (_CreateNonspooledPort(pPort))
809         {
810             bOpenedPort = TRUE;
811         }
812         else if (GetLastError() != ERROR_SUCCESS)
813         {
814             dwErrorCode = GetLastError();
815             goto Cleanup;
816         }
817     }
818     else if (pPort->hFile == INVALID_HANDLE_VALUE)
819     {
820         // All other port types need to be opened already.
821         dwErrorCode = ERROR_INVALID_PARAMETER;
822         goto Cleanup;
823     }
824 
825     // Finally pass the parameters to SetCommTimeouts.
826     if (!SetCommTimeouts(pPort->hFile, lpCTO))
827     {
828         dwErrorCode = GetLastError();
829         ERR("SetCommTimeouts failed with error %lu!\n", dwErrorCode);
830         goto Cleanup;
831     }
832 
833     dwErrorCode = ERROR_SUCCESS;
834 
835 Cleanup:
836     if (bOpenedPort)
837         _ClosePortHandles(pPort);
838 
839     SetLastError(dwErrorCode);
840     return (dwErrorCode == ERROR_SUCCESS);
841 }
842 
843 BOOL WINAPI
844 LocalmonReadPort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuffer, PDWORD pcbRead)
845 {
846     BOOL bOpenedPort = FALSE;
847     DWORD dwErrorCode;
848     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
849 
850     TRACE("LocalmonReadPort(%p, %p, %lu, %p)\n", hPort, pBuffer, cbBuffer, pcbRead);
851 
852     // Sanity checks
853     if (!pPort || (cbBuffer && !pBuffer) || !pcbRead)
854     {
855         dwErrorCode = ERROR_INVALID_PARAMETER;
856         goto Cleanup;
857     }
858 
859     // Reading is only supported for physical ports.
860     if (pPort->PortType != PortType_PhysicalCOM && pPort->PortType != PortType_PhysicalLPT)
861     {
862         dwErrorCode = ERROR_INVALID_HANDLE;
863         goto Cleanup;
864     }
865 
866     // If this is a serial port, a temporary file handle may be opened.
867     if (pPort->PortType == PortType_PhysicalCOM)
868     {
869         if (_CreateNonspooledPort(pPort))
870         {
871             bOpenedPort = TRUE;
872         }
873         else if (GetLastError() != ERROR_SUCCESS)
874         {
875             dwErrorCode = GetLastError();
876             goto Cleanup;
877         }
878     }
879 
880     // Pass the parameters to ReadFile.
881     if (!ReadFile(pPort->hFile, pBuffer, cbBuffer, pcbRead, NULL))
882     {
883         dwErrorCode = GetLastError();
884         ERR("ReadFile failed with error %lu!\n", dwErrorCode);
885         goto Cleanup;
886     }
887 
888 Cleanup:
889     if (bOpenedPort)
890         _ClosePortHandles(pPort);
891 
892     SetLastError(dwErrorCode);
893     return (dwErrorCode == ERROR_SUCCESS);
894 }
895 
896 BOOL WINAPI
897 LocalmonStartDocPort(HANDLE hPort, PWSTR pPrinterName, DWORD JobId, DWORD Level, PBYTE pDocInfo)
898 {
899     DWORD dwErrorCode;
900     PDOC_INFO_1W pDocInfo1 = (PDOC_INFO_1W)pDocInfo;        // DOC_INFO_1W is the least common denominator for both DOC_INFO levels.
901     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
902 
903     TRACE("LocalmonStartDocPort(%p, %S, %lu, %lu, %p)\n", hPort, pPrinterName, JobId, Level, pDocInfo);
904 
905     // Sanity checks
906     if (!pPort || !pPrinterName || (pPort->PortType == PortType_FILE && (!pDocInfo1 || !pDocInfo1->pOutputFile || !*pDocInfo1->pOutputFile)))
907     {
908         dwErrorCode = ERROR_INVALID_PARAMETER;
909         goto Cleanup;
910     }
911 
912     if (Level > 2)
913     {
914         dwErrorCode = ERROR_INVALID_LEVEL;
915         goto Cleanup;
916     }
917 
918     // Calling StartDocPort multiple times isn't considered a failure, but we don't need to do anything then.
919     if (pPort->bStartedDoc)
920     {
921         dwErrorCode = ERROR_SUCCESS;
922         goto Cleanup;
923     }
924 
925     // Open a handle to the given printer for later reporting our progress using SetJobW.
926     if (!OpenPrinterW(pPrinterName, &pPort->hPrinter, NULL))
927     {
928         dwErrorCode = GetLastError();
929         ERR("OpenPrinterW failed with error %lu!\n", dwErrorCode);
930         goto Cleanup;
931     }
932 
933     // We need our Job ID for SetJobW as well.
934     pPort->dwJobID = JobId;
935 
936     // Check the port type.
937     if (pPort->PortType == PortType_PhysicalLPT)
938     {
939         // Update the NONSPOOLED mapping if the port mapping has changed since our OpenPort call.
940         if (!_CreateNonspooledPort(pPort) && GetLastError() != ERROR_SUCCESS)
941         {
942             dwErrorCode = GetLastError();
943             goto Cleanup;
944         }
945 
946         // Update the transmission retry timeout as well.
947         _SetTransmissionRetryTimeout(pPort);
948     }
949     else if(pPort->PortType == PortType_FILE)
950     {
951         // This is a FILE: port. Open the output file given in the Document Info.
952         pPort->hFile = CreateFileW(pDocInfo1->pOutputFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL);
953         if (pPort->hFile == INVALID_HANDLE_VALUE)
954         {
955             dwErrorCode = GetLastError();
956             goto Cleanup;
957         }
958     }
959     else
960     {
961         // This can be:
962         //    - a physical COM port
963         //    - a non-physical LPT port (e.g. with "net use LPT1 ...")
964         //    - any other port (e.g. a file or a shared printer installed as a local port)
965         //
966         // For all these cases, we try to create a NONSPOOLED port per job.
967         // If _CreateNonspooledPort reports that no NONSPOOLED port is necessary, we can just open the port name.
968         if (!_CreateNonspooledPort(pPort))
969         {
970             if (GetLastError() == ERROR_SUCCESS)
971             {
972                 pPort->hFile = CreateFileW(pPort->pwszPortName, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
973                 if (pPort->hFile == INVALID_HANDLE_VALUE)
974                 {
975                     dwErrorCode = GetLastError();
976                     goto Cleanup;
977                 }
978             }
979             else
980             {
981                 dwErrorCode = GetLastError();
982                 goto Cleanup;
983             }
984         }
985     }
986 
987     // We were successful!
988     dwErrorCode = ERROR_SUCCESS;
989     pPort->bStartedDoc = TRUE;
990 
991 Cleanup:
992     SetLastError(dwErrorCode);
993     return (dwErrorCode == ERROR_SUCCESS);
994 }
995 
996 BOOL WINAPI
997 LocalmonWritePort(HANDLE hPort, PBYTE pBuffer, DWORD cbBuf, PDWORD pcbWritten)
998 {
999     BOOL bOpenedPort = FALSE;
1000     DWORD dwErrorCode;
1001     PLOCALMON_PORT pPort = (PLOCALMON_PORT)hPort;
1002 
1003     TRACE("LocalmonWritePort(%p, %p, %lu, %p)\n", hPort, pBuffer, cbBuf, pcbWritten);
1004 
1005     // Sanity checks
1006     if (!pPort || (cbBuf && !pBuffer) || !pcbWritten)
1007     {
1008         dwErrorCode = ERROR_INVALID_PARAMETER;
1009         goto Cleanup;
1010     }
1011 
1012     // If this is a serial port, a temporary file handle may be opened.
1013     if (pPort->PortType == PortType_PhysicalCOM)
1014     {
1015         if (_CreateNonspooledPort(pPort))
1016         {
1017             bOpenedPort = TRUE;
1018         }
1019         else if (GetLastError() != ERROR_SUCCESS)
1020         {
1021             dwErrorCode = GetLastError();
1022             goto Cleanup;
1023         }
1024     }
1025     else if (pPort->hFile == INVALID_HANDLE_VALUE)
1026     {
1027         // All other port types need to be opened already.
1028         dwErrorCode = ERROR_INVALID_PARAMETER;
1029         goto Cleanup;
1030     }
1031 
1032     // Pass the parameters to WriteFile.
1033     if (!WriteFile(pPort->hFile, pBuffer, cbBuf, pcbWritten, NULL))
1034     {
1035         dwErrorCode = GetLastError();
1036         ERR("WriteFile failed with error %lu!\n", dwErrorCode);
1037         goto Cleanup;
1038     }
1039 
1040     // If something was written down, we consider that a success, otherwise it's a timeout.
1041     if (*pcbWritten)
1042         dwErrorCode = ERROR_SUCCESS;
1043     else
1044         dwErrorCode = ERROR_TIMEOUT;
1045 
1046 Cleanup:
1047     if (bOpenedPort)
1048         _ClosePortHandles(pPort);
1049 
1050     SetLastError(dwErrorCode);
1051     return (dwErrorCode == ERROR_SUCCESS);
1052 }
1053