1 /*
2  * PROJECT:     ReactOS Local Spooler
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions related to Print Monitors
5  * COPYRIGHT:   Copyright 2015-2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 // Global Variables
11 LIST_ENTRY PrintMonitorList;
12 
13 // Local Constants
14 static DWORD dwMonitorInfo1Offsets[] = {
15     FIELD_OFFSET(MONITOR_INFO_1W, pName),
16     MAXDWORD
17 };
18 
19 static DWORD dwMonitorInfo2Offsets[] = {
20     FIELD_OFFSET(MONITOR_INFO_2W, pName),
21     FIELD_OFFSET(MONITOR_INFO_2W, pEnvironment),
22     FIELD_OFFSET(MONITOR_INFO_2W, pDLLName),
23     MAXDWORD
24 };
25 
26 
27 PLOCAL_PRINT_MONITOR
28 FindPrintMonitor(PCWSTR pwszName)
29 {
30     PLIST_ENTRY pEntry;
31     PLOCAL_PRINT_MONITOR pPrintMonitor;
32 
33     TRACE("FindPrintMonitor(%S)\n", pwszName);
34 
35     if (!pwszName)
36         return NULL;
37 
38     for (pEntry = PrintMonitorList.Flink; pEntry != &PrintMonitorList; pEntry = pEntry->Flink)
39     {
40         pPrintMonitor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_MONITOR, Entry);
41 
42         if (_wcsicmp(pPrintMonitor->pwszName, pwszName) == 0)
43             return pPrintMonitor;
44     }
45 
46     return NULL;
47 }
48 
49 BOOL
50 InitializePrintMonitorList(void)
51 {
52     const WCHAR wszMonitorsPath[] = L"SYSTEM\\CurrentControlSet\\Control\\Print\\Monitors";
53     const DWORD cchMonitorsPath = _countof(wszMonitorsPath) - 1;
54 
55     DWORD cchMaxSubKey;
56     DWORD cchPrintMonitorName;
57     DWORD dwErrorCode;
58     DWORD dwSubKeys;
59     DWORD i;
60     HINSTANCE hinstPrintMonitor = NULL;
61     HKEY hKey = NULL;
62     HKEY hSubKey = NULL;
63     MONITORINIT MonitorInit;
64     PInitializePrintMonitor pfnInitializePrintMonitor;
65     PInitializePrintMonitor2 pfnInitializePrintMonitor2;
66     PLOCAL_PRINT_MONITOR pPrintMonitor = NULL;
67     PWSTR pwszRegistryPath = NULL;
68 
69     TRACE("InitializePrintMonitorList()\n");
70 
71     // Initialize an empty list for our Print Monitors.
72     InitializeListHead(&PrintMonitorList);
73 
74     // Open the key containing Print Monitors.
75     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, wszMonitorsPath, 0, KEY_READ, &hKey);
76     if (dwErrorCode != ERROR_SUCCESS)
77     {
78         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
79         goto Cleanup;
80     }
81 
82     // Get the number of Print Providers and maximum sub key length.
83     dwErrorCode = (DWORD)RegQueryInfoKeyW(hKey, NULL, NULL, NULL, &dwSubKeys, &cchMaxSubKey, NULL, NULL, NULL, NULL, NULL, NULL);
84     if (dwErrorCode != ERROR_SUCCESS)
85     {
86         ERR("RegQueryInfoKeyW failed with status %lu!\n", dwErrorCode);
87         goto Cleanup;
88     }
89 
90     // Loop through all available Print Providers.
91     for (i = 0; i < dwSubKeys; i++)
92     {
93         // Cleanup tasks from the previous run
94         if (hSubKey)
95         {
96             RegCloseKey(hSubKey);
97             hSubKey = NULL;
98         }
99 
100         if (pwszRegistryPath)
101         {
102             DllFreeSplMem(pwszRegistryPath);
103             pwszRegistryPath = NULL;
104         }
105 
106         if (pPrintMonitor)
107         {
108             if (pPrintMonitor->pwszFileName)
109                 DllFreeSplMem(pPrintMonitor->pwszFileName);
110 
111             if (pPrintMonitor->pwszName)
112                 DllFreeSplMem(pPrintMonitor->pwszName);
113 
114             DllFreeSplMem(pPrintMonitor);
115             pPrintMonitor = NULL;
116         }
117 
118         // Create a new LOCAL_PRINT_MONITOR structure for it.
119         pPrintMonitor = DllAllocSplMem(sizeof(LOCAL_PRINT_MONITOR));
120         if (!pPrintMonitor)
121         {
122             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
123             ERR("DllAllocSplMem failed!\n");
124             goto Cleanup;
125         }
126 
127         // Allocate memory for the Print Monitor Name.
128         pPrintMonitor->pwszName = DllAllocSplMem((cchMaxSubKey + 1) * sizeof(WCHAR));
129         if (!pPrintMonitor->pwszName)
130         {
131             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
132             ERR("DllAllocSplMem failed!\n");
133             goto Cleanup;
134         }
135 
136         // Get the name of this Print Monitor.
137         cchPrintMonitorName = cchMaxSubKey + 1;
138         dwErrorCode = (DWORD)RegEnumKeyExW(hKey, i, pPrintMonitor->pwszName, &cchPrintMonitorName, NULL, NULL, NULL, NULL);
139         if (dwErrorCode != ERROR_SUCCESS)
140         {
141             ERR("RegEnumKeyExW failed for iteration %lu with status %lu!\n", i, dwErrorCode);
142             continue;
143         }
144 
145         // Open this Print Monitor's registry key.
146         dwErrorCode = (DWORD)RegOpenKeyExW(hKey, pPrintMonitor->pwszName, 0, KEY_READ, &hSubKey);
147         if (dwErrorCode != ERROR_SUCCESS)
148         {
149             ERR("RegOpenKeyExW failed for Print Provider \"%S\" with status %lu!\n", pPrintMonitor->pwszName, dwErrorCode);
150             continue;
151         }
152 
153         // Get the file name of the Print Monitor.
154         pPrintMonitor->pwszFileName = AllocAndRegQueryWSZ(hSubKey, L"Driver");
155         if (!pPrintMonitor->pwszFileName)
156             continue;
157 
158         // Try to load it.
159         hinstPrintMonitor = LoadLibraryW(pPrintMonitor->pwszFileName);
160         if (!hinstPrintMonitor)
161         {
162             ERR("LoadLibraryW failed for \"%S\" with error %lu!\n", pPrintMonitor->pwszFileName, GetLastError());
163             continue;
164         }
165 
166         // Try to find a Level 2 initialization routine first.
167         pfnInitializePrintMonitor2 = (PInitializePrintMonitor2)GetProcAddress(hinstPrintMonitor, "InitializePrintMonitor2");
168         if (pfnInitializePrintMonitor2)
169         {
170             // Prepare a MONITORINIT structure.
171             MonitorInit.cbSize = sizeof(MONITORINIT);
172             MonitorInit.bLocal = TRUE;
173 
174             // TODO: Fill the other fields.
175 
176             // Call the Level 2 initialization routine.
177             pPrintMonitor->pMonitor = (PMONITOR2)pfnInitializePrintMonitor2(&MonitorInit, &pPrintMonitor->hMonitor);
178             if (!pPrintMonitor->pMonitor)
179             {
180                 ERR("InitializePrintMonitor2 failed for \"%S\" with error %lu!\n", pPrintMonitor->pwszFileName, GetLastError());
181                 continue;
182             }
183 
184             pPrintMonitor->bIsLevel2 = TRUE;
185         }
186         else
187         {
188             // Try to find a Level 1 initialization routine then.
189             pfnInitializePrintMonitor = (PInitializePrintMonitor)GetProcAddress(hinstPrintMonitor, "InitializePrintMonitor");
190             if (pfnInitializePrintMonitor)
191             {
192                 // Construct the registry path.
193                 pwszRegistryPath = DllAllocSplMem((cchMonitorsPath + 1 + cchPrintMonitorName + 1) * sizeof(WCHAR));
194                 if (!pwszRegistryPath)
195                 {
196                     dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
197                     ERR("DllAllocSplMem failed!\n");
198                     goto Cleanup;
199                 }
200 
201                 CopyMemory(pwszRegistryPath, wszMonitorsPath, cchMonitorsPath * sizeof(WCHAR));
202                 pwszRegistryPath[cchMonitorsPath] = L'\\';
203                 CopyMemory(&pwszRegistryPath[cchMonitorsPath + 1], pPrintMonitor->pwszName, (cchPrintMonitorName + 1) * sizeof(WCHAR));
204 
205                 // Call the Level 1 initialization routine.
206                 pPrintMonitor->pMonitor = (LPMONITOREX)pfnInitializePrintMonitor(pwszRegistryPath);
207                 if (!pPrintMonitor->pMonitor)
208                 {
209                     ERR("InitializePrintMonitor failed for \"%S\" with error %lu!\n", pPrintMonitor->pwszFileName, GetLastError());
210                     continue;
211                 }
212             }
213             else
214             {
215                 ERR("No initialization routine found for \"%S\"!\n", pPrintMonitor->pwszFileName);
216                 continue;
217             }
218         }
219 
220         // Add this Print Monitor to the list.
221         InsertTailList(&PrintMonitorList, &pPrintMonitor->Entry);
222 
223         // Don't let the cleanup routine free this.
224         pPrintMonitor = NULL;
225     }
226 
227     dwErrorCode = ERROR_SUCCESS;
228 
229 Cleanup:
230     // Inside the loop
231     if (hSubKey)
232         RegCloseKey(hSubKey);
233 
234     if (pwszRegistryPath)
235         DllFreeSplMem(pwszRegistryPath);
236 
237     if (pPrintMonitor)
238     {
239         if (pPrintMonitor->pwszFileName)
240             DllFreeSplMem(pPrintMonitor->pwszFileName);
241 
242         if (pPrintMonitor->pwszName)
243             DllFreeSplMem(pPrintMonitor->pwszName);
244 
245         DllFreeSplMem(pPrintMonitor);
246     }
247 
248     // Outside the loop
249     if (hKey)
250         RegCloseKey(hKey);
251 
252     SetLastError(dwErrorCode);
253     return (dwErrorCode == ERROR_SUCCESS);
254 }
255 
256 static void
257 _LocalGetMonitorLevel1(PLOCAL_PRINT_MONITOR pPrintMonitor, PMONITOR_INFO_1W* ppMonitorInfo, PBYTE* ppMonitorInfoEnd, PDWORD pcbNeeded)
258 {
259     DWORD cbMonitorName;
260     PCWSTR pwszStrings[1];
261 
262     // Calculate the string lengths.
263     if (!ppMonitorInfo)
264     {
265         cbMonitorName = (wcslen(pPrintMonitor->pwszName) + 1) * sizeof(WCHAR);
266 
267         *pcbNeeded += sizeof(MONITOR_INFO_1W) + cbMonitorName;
268         return;
269     }
270 
271     // Set the pName field.
272     pwszStrings[0] = pPrintMonitor->pwszName;
273 
274     // Copy the structure and advance to the next one in the output buffer.
275     *ppMonitorInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppMonitorInfo), dwMonitorInfo1Offsets, *ppMonitorInfoEnd);
276     (*ppMonitorInfo)++;
277 }
278 
279 static void
280 _LocalGetMonitorLevel2(PLOCAL_PRINT_MONITOR pPrintMonitor, PMONITOR_INFO_2W* ppMonitorInfo, PBYTE* ppMonitorInfoEnd, PDWORD pcbNeeded)
281 {
282     DWORD cbFileName;
283     DWORD cbMonitorName;
284     PCWSTR pwszStrings[3];
285 
286     // Calculate the string lengths.
287     if (!ppMonitorInfo)
288     {
289         cbMonitorName = (wcslen(pPrintMonitor->pwszName) + 1) * sizeof(WCHAR);
290         cbFileName = (wcslen(pPrintMonitor->pwszFileName) + 1) * sizeof(WCHAR);
291 
292         *pcbNeeded += sizeof(MONITOR_INFO_2W) + cbMonitorName + cbCurrentEnvironment + cbFileName;
293         return;
294     }
295 
296     // Set the pName field.
297     pwszStrings[0] = pPrintMonitor->pwszName;
298 
299     // Set the pEnvironment field.
300     pwszStrings[1] = (PWSTR)wszCurrentEnvironment;
301 
302     // Set the pDLLName field.
303     pwszStrings[2] = pPrintMonitor->pwszFileName;
304 
305     // Copy the structure and advance to the next one in the output buffer.
306     *ppMonitorInfoEnd = PackStrings(pwszStrings, (PBYTE)(*ppMonitorInfo), dwMonitorInfo2Offsets, *ppMonitorInfoEnd);
307     (*ppMonitorInfo)++;
308 }
309 
310 BOOL WINAPI
311 LocalEnumMonitors(PWSTR pName, DWORD Level, PBYTE pMonitors, DWORD cbBuf, PDWORD pcbNeeded, PDWORD pcReturned)
312 {
313     DWORD dwErrorCode;
314     PBYTE pMonitorInfoEnd;
315     PLIST_ENTRY pEntry;
316     PLOCAL_PRINT_MONITOR pPrintMonitor;
317 
318     TRACE("LocalEnumMonitors(%S, %lu, %p, %lu, %p, %p)\n", pName, Level, pMonitors, cbBuf, pcbNeeded, pcReturned);
319 
320     // Sanity checks.
321     if (Level > 2)
322     {
323         dwErrorCode = ERROR_INVALID_LEVEL;
324         goto Cleanup;
325     }
326 
327     // Begin counting.
328     *pcbNeeded = 0;
329     *pcReturned = 0;
330 
331     // Count the required buffer size and the number of monitors.
332     for (pEntry = PrintMonitorList.Flink; pEntry != &PrintMonitorList; pEntry = pEntry->Flink)
333     {
334         pPrintMonitor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_MONITOR, Entry);
335 
336         if (Level == 1)
337             _LocalGetMonitorLevel1(pPrintMonitor, NULL, NULL, pcbNeeded);
338         else if (Level == 2)
339             _LocalGetMonitorLevel2(pPrintMonitor, NULL, NULL, pcbNeeded);
340     }
341 
342     // Check if the supplied buffer is large enough.
343     if (cbBuf < *pcbNeeded)
344     {
345         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
346         goto Cleanup;
347     }
348 
349     // Copy over the Monitor information.
350     pMonitorInfoEnd = &pMonitors[*pcbNeeded];
351 
352     for (pEntry = PrintMonitorList.Flink; pEntry != &PrintMonitorList; pEntry = pEntry->Flink)
353     {
354         pPrintMonitor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_MONITOR, Entry);
355 
356         if (Level == 1)
357             _LocalGetMonitorLevel1(pPrintMonitor, (PMONITOR_INFO_1W*)&pMonitors, &pMonitorInfoEnd, NULL);
358         else if (Level == 2)
359             _LocalGetMonitorLevel2(pPrintMonitor, (PMONITOR_INFO_2W*)&pMonitors, &pMonitorInfoEnd, NULL);
360 
361         (*pcReturned)++;
362     }
363 
364     dwErrorCode = ERROR_SUCCESS;
365 
366 Cleanup:
367     SetLastError(dwErrorCode);
368     return (dwErrorCode == ERROR_SUCCESS);
369 }
370