1 /*
2  * PROJECT:     ReactOS Local Spooler
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions related to Print Processors
5  * COPYRIGHT:   Copyright 2015-2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 
11 // Local Variables
12 static LIST_ENTRY _PrintProcessorList;
13 
14 /**
15  * @name _OpenEnvironment
16  *
17  * Checks a supplied pEnvironment variable for validity and opens its registry key.
18  *
19  * @param pEnvironment
20  * The pEnvironment variable to check.
21  *
22  * @param hKey
23  * On success, this variable will contain a HKEY to the opened registry key of the environment.
24  * You can use it for further tasks and have to close it with RegCloseKey.
25  *
26  * @return
27  * A Windows Error Code indicating success or failure.
28  */
29 static DWORD
_OpenEnvironment(PCWSTR pEnvironment,PHKEY hKey)30 _OpenEnvironment(PCWSTR pEnvironment, PHKEY hKey)
31 {
32     const WCHAR wszEnvironmentsKey[] = L"SYSTEM\\CurrentControlSet\\Control\\Print\\Environments\\";
33     const DWORD cchEnvironmentsKey = _countof(wszEnvironmentsKey) - 1;
34 
35     DWORD cchEnvironment;
36     DWORD dwErrorCode;
37     PWSTR pwszEnvironmentKey = NULL;
38 
39     // Sanity checks
40     if (!pEnvironment)
41     {
42         dwErrorCode = ERROR_INVALID_ENVIRONMENT;
43         goto Cleanup;
44     }
45 
46     // Construct the registry key of the demanded environment.
47     cchEnvironment = wcslen(pEnvironment);
48     pwszEnvironmentKey = DllAllocSplMem((cchEnvironmentsKey + cchEnvironment + 1) * sizeof(WCHAR));
49     if (!pwszEnvironmentKey)
50     {
51         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
52         ERR("DllAllocSplMem failed!\n");
53         goto Cleanup;
54     }
55 
56     CopyMemory(pwszEnvironmentKey, wszEnvironmentsKey, cchEnvironmentsKey * sizeof(WCHAR));
57     CopyMemory(&pwszEnvironmentKey[cchEnvironmentsKey], pEnvironment, (cchEnvironment + 1) * sizeof(WCHAR));
58 
59     // Open the registry key.
60     dwErrorCode = (DWORD)RegOpenKeyExW(HKEY_LOCAL_MACHINE, pwszEnvironmentKey, 0, KEY_READ, hKey);
61     if (dwErrorCode == ERROR_FILE_NOT_FOUND)
62     {
63         dwErrorCode = ERROR_INVALID_ENVIRONMENT;
64         goto Cleanup;
65     }
66     else if (dwErrorCode != ERROR_SUCCESS)
67     {
68         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
69         goto Cleanup;
70     }
71 
72 Cleanup:
73     if (pwszEnvironmentKey)
74         DllFreeSplMem(pwszEnvironmentKey);
75 
76     return dwErrorCode;
77 }
78 
79 BOOL
FindDatatype(const PLOCAL_PRINT_PROCESSOR pPrintProcessor,PCWSTR pwszDatatype)80 FindDatatype(const PLOCAL_PRINT_PROCESSOR pPrintProcessor, PCWSTR pwszDatatype)
81 {
82     DWORD i;
83     PDATATYPES_INFO_1W pCurrentDatatype = pPrintProcessor->pDatatypesInfo1;
84 
85     TRACE("FindDatatype(%p, %S)\n", pPrintProcessor, pwszDatatype);
86 
87     if (!pwszDatatype)
88         return FALSE;
89 
90     for (i = 0; i < pPrintProcessor->dwDatatypeCount; i++)
91     {
92         if (_wcsicmp(pCurrentDatatype->pName, pwszDatatype) == 0)
93             return TRUE;
94 
95         ++pCurrentDatatype;
96     }
97 
98     return FALSE;
99 }
100 
101 PLOCAL_PRINT_PROCESSOR
FindPrintProcessor(PCWSTR pwszName)102 FindPrintProcessor(PCWSTR pwszName)
103 {
104     PLIST_ENTRY pEntry;
105     PLOCAL_PRINT_PROCESSOR pPrintProcessor;
106 
107     TRACE("FindPrintProcessor(%S)\n", pwszName);
108 
109     if (!pwszName)
110         return NULL;
111 
112     for (pEntry = _PrintProcessorList.Flink; pEntry != &_PrintProcessorList; pEntry = pEntry->Flink)
113     {
114         pPrintProcessor = CONTAINING_RECORD(pEntry, LOCAL_PRINT_PROCESSOR, Entry);
115 
116         if (_wcsicmp(pPrintProcessor->pwszName, pwszName) == 0)
117             return pPrintProcessor;
118     }
119 
120     return NULL;
121 }
122 
123 /**
124  * @name InitializePrintProcessorList
125  *
126  * Initializes a singly linked list of locally available Print Processors.
127  */
128 BOOL
InitializePrintProcessorList(void)129 InitializePrintProcessorList(void)
130 {
131     DWORD cbDatatypes;
132     DWORD cbFileName;
133     DWORD cchPrintProcessorPath;
134     DWORD cchMaxSubKey;
135     DWORD cchPrintProcessorName;
136     DWORD dwErrorCode;
137     DWORD dwSubKeys;
138     DWORD i;
139     HINSTANCE hinstPrintProcessor;
140     HKEY hKey = NULL;
141     HKEY hSubKey = NULL;
142     HKEY hSubSubKey = NULL;
143     PLOCAL_PRINT_PROCESSOR pPrintProcessor = NULL;
144     WCHAR wszFileName[MAX_PATH];
145     WCHAR wszPrintProcessorPath[MAX_PATH];
146 
147     TRACE("InitializePrintProcessorList()\n");
148 
149     // Initialize an empty list for our Print Processors.
150     InitializeListHead(&_PrintProcessorList);
151 
152     // Prepare the path to the Print Processor directory.
153     if (!LocalGetPrintProcessorDirectory(NULL, (PWSTR)wszCurrentEnvironment, 1, (PBYTE)wszPrintProcessorPath, sizeof(wszPrintProcessorPath), &cchPrintProcessorPath))
154     {
155         dwErrorCode = GetLastError();
156         goto Cleanup;
157     }
158 
159     // LocalGetPrintProcessorDirectory returns the number of copied bytes. Convert this into a number of characters without the terminating null-character.
160     cchPrintProcessorPath /= sizeof(WCHAR);
161     --cchPrintProcessorPath;
162 
163     // Append a trailing backslash.
164     wszPrintProcessorPath[cchPrintProcessorPath] = L'\\';
165     ++cchPrintProcessorPath;
166 
167     // Open the environment registry key.
168     dwErrorCode = _OpenEnvironment(wszCurrentEnvironment, &hKey);
169     if (dwErrorCode != ERROR_SUCCESS)
170         goto Cleanup;
171 
172     // Open the "Print Processors" subkey.
173     dwErrorCode = (DWORD)RegOpenKeyExW(hKey, L"Print Processors", 0, KEY_READ, &hSubKey);
174     if (dwErrorCode != ERROR_SUCCESS)
175     {
176         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
177         goto Cleanup;
178     }
179 
180     // Get the number of Print Processors and maximum sub key length.
181     dwErrorCode = (DWORD)RegQueryInfoKeyW(hSubKey, NULL, NULL, NULL, &dwSubKeys, &cchMaxSubKey, NULL, NULL, NULL, NULL, NULL, NULL);
182     if (dwErrorCode != ERROR_SUCCESS)
183     {
184         ERR("RegQueryInfoKeyW failed with status %lu!\n", dwErrorCode);
185         goto Cleanup;
186     }
187 
188     // Loop through all available local Print Processors.
189     for (i = 0; i < dwSubKeys; i++)
190     {
191         // Cleanup tasks from the previous run
192         if (hSubSubKey)
193         {
194             RegCloseKey(hSubSubKey);
195             hSubSubKey = NULL;
196         }
197 
198         if (pPrintProcessor)
199         {
200             if (pPrintProcessor->pwszName)
201                 DllFreeSplStr(pPrintProcessor->pwszName);
202 
203             if (pPrintProcessor->pDatatypesInfo1)
204                 DllFreeSplMem(pPrintProcessor->pDatatypesInfo1);
205 
206             DllFreeSplMem(pPrintProcessor);
207             pPrintProcessor = NULL;
208         }
209 
210         // Create a new LOCAL_PRINT_PROCESSOR structure for it.
211         pPrintProcessor = DllAllocSplMem(sizeof(LOCAL_PRINT_PROCESSOR));
212         if (!pPrintProcessor)
213         {
214             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
215             ERR("DllAllocSplMem failed!\n");
216             goto Cleanup;
217         }
218 
219         // Allocate memory for the Print Monitor Name.
220         pPrintProcessor->pwszName = DllAllocSplMem((cchMaxSubKey + 1) * sizeof(WCHAR));
221         if (!pPrintProcessor->pwszName)
222         {
223             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
224             ERR("DllAllocSplMem failed!\n");
225             goto Cleanup;
226         }
227 
228         // Get the name of this Print Processor.
229         cchPrintProcessorName = cchMaxSubKey + 1;
230         dwErrorCode = (DWORD)RegEnumKeyExW(hSubKey, i, pPrintProcessor->pwszName, &cchPrintProcessorName, NULL, NULL, NULL, NULL);
231         if (dwErrorCode != ERROR_SUCCESS)
232         {
233             ERR("RegEnumKeyExW failed with status %ld!\n", dwErrorCode);
234             continue;
235         }
236 
237         // Open this Print Processor's registry key.
238         dwErrorCode = (DWORD)RegOpenKeyExW(hSubKey, pPrintProcessor->pwszName, 0, KEY_READ, &hSubSubKey);
239         if (dwErrorCode != ERROR_SUCCESS)
240         {
241             ERR("RegOpenKeyExW failed for Print Processor \"%S\" with status %lu!\n", pPrintProcessor->pwszName, dwErrorCode);
242             continue;
243         }
244 
245         // Get the file name of the Print Processor.
246         cbFileName = sizeof(wszFileName);
247         dwErrorCode = (DWORD)RegQueryValueExW(hSubSubKey, L"Driver", NULL, NULL, (PBYTE)wszFileName, &cbFileName);
248         if (dwErrorCode != ERROR_SUCCESS)
249         {
250             ERR("RegQueryValueExW failed for Print Processor \"%S\" with status %lu!\n", pPrintProcessor->pwszName, dwErrorCode);
251             continue;
252         }
253 
254         // Verify that our buffer is large enough.
255         if (cchPrintProcessorPath + cbFileName / sizeof(WCHAR) > MAX_PATH)
256         {
257             ERR("Print Processor directory \"%S\" for Print Processor \"%S\" is too long!\n", wszFileName, pPrintProcessor->pwszName);
258             continue;
259         }
260 
261         // Construct the full path to the Print Processor.
262         CopyMemory(&wszPrintProcessorPath[cchPrintProcessorPath], wszFileName, cbFileName);
263 
264         // Try to load it.
265         hinstPrintProcessor = LoadLibraryW(wszPrintProcessorPath);
266         if (!hinstPrintProcessor)
267         {
268             ERR("LoadLibraryW failed for \"%S\" with error %lu!\n", wszPrintProcessorPath, GetLastError());
269             continue;
270         }
271 
272         // Get and verify all its function pointers.
273         pPrintProcessor->pfnClosePrintProcessor = (PClosePrintProcessor)GetProcAddress(hinstPrintProcessor, "ClosePrintProcessor");
274         if (!pPrintProcessor->pfnClosePrintProcessor)
275         {
276             ERR("Print Processor \"%S\" exports no ClosePrintProcessor!\n", wszPrintProcessorPath);
277             continue;
278         }
279 
280         pPrintProcessor->pfnControlPrintProcessor = (PControlPrintProcessor)GetProcAddress(hinstPrintProcessor, "ControlPrintProcessor");
281         if (!pPrintProcessor->pfnControlPrintProcessor)
282         {
283             ERR("Print Processor \"%S\" exports no ControlPrintProcessor!\n", wszPrintProcessorPath);
284             continue;
285         }
286 
287         pPrintProcessor->pfnEnumPrintProcessorDatatypesW = (PEnumPrintProcessorDatatypesW)GetProcAddress(hinstPrintProcessor, "EnumPrintProcessorDatatypesW");
288         if (!pPrintProcessor->pfnEnumPrintProcessorDatatypesW)
289         {
290             ERR("Print Processor \"%S\" exports no EnumPrintProcessorDatatypesW!\n", wszPrintProcessorPath);
291             continue;
292         }
293 
294         pPrintProcessor->pfnGetPrintProcessorCapabilities = (PGetPrintProcessorCapabilities)GetProcAddress(hinstPrintProcessor, "GetPrintProcessorCapabilities");
295         if (!pPrintProcessor->pfnGetPrintProcessorCapabilities)
296         {
297             ERR("Print Processor \"%S\" exports no GetPrintProcessorCapabilities!\n", wszPrintProcessorPath);
298             continue;
299         }
300 
301         pPrintProcessor->pfnOpenPrintProcessor = (POpenPrintProcessor)GetProcAddress(hinstPrintProcessor, "OpenPrintProcessor");
302         if (!pPrintProcessor->pfnOpenPrintProcessor)
303         {
304             ERR("Print Processor \"%S\" exports no OpenPrintProcessor!\n", wszPrintProcessorPath);
305             continue;
306         }
307 
308         pPrintProcessor->pfnPrintDocumentOnPrintProcessor = (PPrintDocumentOnPrintProcessor)GetProcAddress(hinstPrintProcessor, "PrintDocumentOnPrintProcessor");
309         if (!pPrintProcessor->pfnPrintDocumentOnPrintProcessor)
310         {
311             ERR("Print Processor \"%S\" exports no PrintDocumentOnPrintProcessor!\n", wszPrintProcessorPath);
312             continue;
313         }
314 
315         // Get all supported datatypes.
316         pPrintProcessor->pfnEnumPrintProcessorDatatypesW(NULL, NULL, 1, NULL, 0, &cbDatatypes, &pPrintProcessor->dwDatatypeCount);
317         pPrintProcessor->pDatatypesInfo1 = DllAllocSplMem(cbDatatypes);
318         if (!pPrintProcessor->pDatatypesInfo1)
319         {
320             dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
321             ERR("DllAllocSplMem failed!\n");
322             goto Cleanup;
323         }
324 
325         if (!pPrintProcessor->pfnEnumPrintProcessorDatatypesW(NULL, NULL, 1, (PBYTE)pPrintProcessor->pDatatypesInfo1, cbDatatypes, &cbDatatypes, &pPrintProcessor->dwDatatypeCount))
326         {
327             ERR("EnumPrintProcessorDatatypesW failed for Print Processor \"%S\" with error %lu!\n", wszPrintProcessorPath, GetLastError());
328             continue;
329         }
330 
331         // Add the Print Processor to the list.
332         InsertTailList(&_PrintProcessorList, &pPrintProcessor->Entry);
333 
334         // Don't let the cleanup routines free this.
335         pPrintProcessor = NULL;
336     }
337 
338     dwErrorCode = ERROR_SUCCESS;
339 
340 Cleanup:
341     // Inside the loop
342     if (hSubSubKey)
343         RegCloseKey(hSubSubKey);
344 
345     if (pPrintProcessor)
346     {
347         if (pPrintProcessor->pwszName)
348             DllFreeSplStr(pPrintProcessor->pwszName);
349 
350         if (pPrintProcessor->pDatatypesInfo1)
351             DllFreeSplMem(pPrintProcessor->pDatatypesInfo1);
352 
353         DllFreeSplMem(pPrintProcessor);
354     }
355 
356     // Outside the loop
357     if (hSubKey)
358         RegCloseKey(hSubKey);
359 
360     if (hKey)
361         RegCloseKey(hKey);
362 
363     SetLastError(dwErrorCode);
364     return (dwErrorCode == ERROR_SUCCESS);
365 }
366 
367 /**
368  * @name LocalEnumPrintProcessorDatatypes
369  *
370  * Obtains an array of all datatypes supported by a particular Print Processor.
371  * Print Provider function for EnumPrintProcessorDatatypesA/EnumPrintProcessorDatatypesW.
372  *
373  * @param pName
374  * Server Name. Ignored here, because every caller of LocalEnumPrintProcessorDatatypes is interested in the local directory.
375  *
376  * @param pPrintProcessorName
377  * The (case-insensitive) name of the Print Processor to query.
378  *
379  * @param Level
380  * The level of the structure supplied through pDatatypes. This must be 1.
381  *
382  * @param pDatatypes
383  * Pointer to the buffer that receives an array of DATATYPES_INFO_1W structures.
384  * Can be NULL if you just want to know the required size of the buffer.
385  *
386  * @param cbBuf
387  * Size of the buffer you supplied for pDatatypes, in bytes.
388  *
389  * @param pcbNeeded
390  * Pointer to a variable that receives the required size of the buffer for pDatatypes, in bytes.
391  * This parameter mustn't be NULL!
392  *
393  * @param pcReturned
394  * Pointer to a variable that receives the number of elements of the DATATYPES_INFO_1W array.
395  * This parameter mustn't be NULL!
396  *
397  * @return
398  * TRUE if we successfully copied the array into pDatatypes, FALSE otherwise.
399  * A more specific error code can be obtained through GetLastError.
400  */
401 BOOL WINAPI
LocalEnumPrintProcessorDatatypes(LPWSTR pName,LPWSTR pPrintProcessorName,DWORD Level,LPBYTE pDatatypes,DWORD cbBuf,LPDWORD pcbNeeded,LPDWORD pcReturned)402 LocalEnumPrintProcessorDatatypes(LPWSTR pName, LPWSTR pPrintProcessorName, DWORD Level, LPBYTE pDatatypes, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
403 {
404     DWORD dwErrorCode;
405     PLOCAL_PRINT_PROCESSOR pPrintProcessor;
406 
407     TRACE("LocalEnumPrintProcessorDatatypes(%S, %S, %lu, %p, %lu, %p, %p)\n", pName, pPrintProcessorName, Level, pDatatypes, cbBuf, pcbNeeded, pcReturned);
408 
409     // Sanity checks
410     if (Level != 1)
411     {
412         dwErrorCode = ERROR_INVALID_LEVEL;
413         goto Cleanup;
414     }
415 
416     // Try to find the Print Processor.
417     pPrintProcessor = FindPrintProcessor(pPrintProcessorName);
418     if (!pPrintProcessor)
419     {
420         dwErrorCode = ERROR_UNKNOWN_PRINTPROCESSOR;
421         goto Cleanup;
422     }
423 
424     // Call its EnumPrintProcessorDatatypesW function.
425     if (pPrintProcessor->pfnEnumPrintProcessorDatatypesW(pName, pPrintProcessorName, Level, pDatatypes, cbBuf, pcbNeeded, pcReturned))
426         dwErrorCode = ERROR_SUCCESS;
427     else
428         dwErrorCode = GetLastError();
429 
430 Cleanup:
431     SetLastError(dwErrorCode);
432     return (dwErrorCode == ERROR_SUCCESS);
433 }
434 
435 /**
436  * @name LocalEnumPrintProcessors
437  *
438  * Obtains an array of all available Print Processors on this computer.
439  * Print Provider function for EnumPrintProcessorsA/EnumPrintProcessorsW.
440  *
441  * @param pName
442  * Server Name. Ignored here, because every caller of LocalEnumPrintProcessors is interested in the local directory.
443  *
444  * @param pEnvironment
445  * One of the predefined operating system and architecture "environment" strings (like "Windows NT x86").
446  * Alternatively, NULL to output the Print Processor directory of the current environment.
447  *
448  * @param Level
449  * The level of the structure supplied through pPrintProcessorInfo. This must be 1.
450  *
451  * @param pPrintProcessorInfo
452  * Pointer to the buffer that receives an array of PRINTPROCESSOR_INFO_1W structures.
453  * Can be NULL if you just want to know the required size of the buffer.
454  *
455  * @param cbBuf
456  * Size of the buffer you supplied for pPrintProcessorInfo, in bytes.
457  *
458  * @param pcbNeeded
459  * Pointer to a variable that receives the required size of the buffer for pPrintProcessorInfo, in bytes.
460  * This parameter mustn't be NULL!
461  *
462  * @param pcReturned
463  * Pointer to a variable that receives the number of elements of the PRINTPROCESSOR_INFO_1W array.
464  * This parameter mustn't be NULL!
465  *
466  * @return
467  * TRUE if we successfully copied the array into pPrintProcessorInfo, FALSE otherwise.
468  * A more specific error code can be obtained through GetLastError.
469  */
470 BOOL WINAPI
LocalEnumPrintProcessors(LPWSTR pName,LPWSTR pEnvironment,DWORD Level,LPBYTE pPrintProcessorInfo,DWORD cbBuf,LPDWORD pcbNeeded,LPDWORD pcReturned)471 LocalEnumPrintProcessors(LPWSTR pName, LPWSTR pEnvironment, DWORD Level, LPBYTE pPrintProcessorInfo, DWORD cbBuf, LPDWORD pcbNeeded, LPDWORD pcReturned)
472 {
473     DWORD cchMaxSubKey;
474     DWORD cchPrintProcessor;
475     DWORD dwErrorCode;
476     DWORD dwPrintProcessorCount;
477     DWORD i;
478     HKEY hKey = NULL;
479     HKEY hSubKey = NULL;
480     PBYTE pCurrentOutputPrintProcessor;
481     PBYTE pCurrentOutputPrintProcessorInfo;
482     PRINTPROCESSOR_INFO_1W PrintProcessorInfo1;
483     PWSTR pwszTemp = NULL;
484 
485     TRACE("LocalEnumPrintProcessors(%S, %S, %lu, %p, %lu, %p, %p)\n", pName, pEnvironment, Level, pPrintProcessorInfo, cbBuf, pcbNeeded, pcReturned);
486 
487     // Sanity checks
488     if (Level != 1)
489     {
490         dwErrorCode = ERROR_INVALID_LEVEL;
491         goto Cleanup;
492     }
493 
494     if (!pcbNeeded || !pcReturned)
495     {
496         // This error is also caught by RPC and returned as RPC_X_NULL_REF_POINTER.
497         dwErrorCode = ERROR_INVALID_PARAMETER;
498         goto Cleanup;
499     }
500 
501     // Verify pEnvironment and open its registry key.
502     // We use the registry and not the PrintProcessorList here, because the caller may request information about a different environment.
503     dwErrorCode = _OpenEnvironment(pEnvironment, &hKey);
504     if (dwErrorCode != ERROR_SUCCESS)
505         goto Cleanup;
506 
507     // Open the "Print Processors" subkey.
508     dwErrorCode = (DWORD)RegOpenKeyExW(hKey, L"Print Processors", 0, KEY_READ, &hSubKey);
509     if (dwErrorCode != ERROR_SUCCESS)
510     {
511         ERR("RegOpenKeyExW failed with status %lu!\n", dwErrorCode);
512         goto Cleanup;
513     }
514 
515     // Get the number of Print Processors and maximum sub key length.
516     dwErrorCode = (DWORD)RegQueryInfoKeyW(hSubKey, NULL, NULL, NULL, &dwPrintProcessorCount, &cchMaxSubKey, NULL, NULL, NULL, NULL, NULL, NULL);
517     if (dwErrorCode != ERROR_SUCCESS)
518     {
519         ERR("RegQueryInfoKeyW failed with status %lu!\n", dwErrorCode);
520         goto Cleanup;
521     }
522 
523     // Allocate a temporary buffer to let RegEnumKeyExW succeed.
524     pwszTemp = DllAllocSplMem((cchMaxSubKey + 1) * sizeof(WCHAR));
525     if (!pwszTemp)
526     {
527         dwErrorCode = ERROR_NOT_ENOUGH_MEMORY;
528         ERR("DllAllocSplMem failed!\n");
529         goto Cleanup;
530     }
531 
532     // Determine the required size of the output buffer.
533     *pcbNeeded = 0;
534 
535     for (i = 0; i < dwPrintProcessorCount; i++)
536     {
537         // RegEnumKeyExW sucks! Unlike similar API functions, it only returns the actual numbers of characters copied when you supply a buffer large enough.
538         // So use pwszTemp with its size cchMaxSubKey for this.
539         cchPrintProcessor = cchMaxSubKey + 1;
540         dwErrorCode = (DWORD)RegEnumKeyExW(hSubKey, i, pwszTemp, &cchPrintProcessor, NULL, NULL, NULL, NULL);
541         if (dwErrorCode != ERROR_SUCCESS)
542         {
543             ERR("RegEnumKeyExW failed with status %lu!\n", dwErrorCode);
544             goto Cleanup;
545         }
546 
547         *pcbNeeded += sizeof(PRINTPROCESSOR_INFO_1W) + (cchPrintProcessor + 1) * sizeof(WCHAR);
548     }
549 
550     // Check if the supplied buffer is large enough.
551     if (cbBuf < *pcbNeeded)
552     {
553         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
554         goto Cleanup;
555     }
556 
557     // Put the Print Processor strings right after the last PRINTPROCESSOR_INFO_1W structure.
558     pCurrentOutputPrintProcessorInfo = pPrintProcessorInfo;
559     pCurrentOutputPrintProcessor = pPrintProcessorInfo + dwPrintProcessorCount * sizeof(PRINTPROCESSOR_INFO_1W);
560 
561     // Copy over all Print Processors.
562     for (i = 0; i < dwPrintProcessorCount; i++)
563     {
564         // This isn't really correct, but doesn't cause any harm, because we've extensively checked the size of the supplied buffer above.
565         cchPrintProcessor = cchMaxSubKey + 1;
566 
567         // Copy the Print Processor name.
568         dwErrorCode = (DWORD)RegEnumKeyExW(hSubKey, i, (PWSTR)pCurrentOutputPrintProcessor, &cchPrintProcessor, NULL, NULL, NULL, NULL);
569         if (dwErrorCode != ERROR_SUCCESS)
570         {
571             ERR("RegEnumKeyExW failed with status %lu!\n", dwErrorCode);
572             goto Cleanup;
573         }
574 
575         // Fill and copy the PRINTPROCESSOR_INFO_1W structure belonging to this Print Processor.
576         PrintProcessorInfo1.pName = (PWSTR)pCurrentOutputPrintProcessor;
577         CopyMemory(pCurrentOutputPrintProcessorInfo, &PrintProcessorInfo1, sizeof(PRINTPROCESSOR_INFO_1W));
578 
579         // Advance to the next PRINTPROCESSOR_INFO_1W location and string location in the output buffer.
580         pCurrentOutputPrintProcessor += (cchPrintProcessor + 1) * sizeof(WCHAR);
581         pCurrentOutputPrintProcessorInfo += sizeof(PRINTPROCESSOR_INFO_1W);
582     }
583 
584     // We've finished successfully!
585     *pcReturned = dwPrintProcessorCount;
586     dwErrorCode = ERROR_SUCCESS;
587 
588 Cleanup:
589     if (pwszTemp)
590         DllFreeSplMem(pwszTemp);
591 
592     if (hSubKey)
593         RegCloseKey(hSubKey);
594 
595     if (hKey)
596         RegCloseKey(hKey);
597 
598     SetLastError(dwErrorCode);
599     return (dwErrorCode == ERROR_SUCCESS);
600 }
601 
602 /**
603  * @name LocalGetPrintProcessorDirectory
604  *
605  * Obtains the path to the local Print Processor directory.
606  * Print Provider function for GetPrintProcessorDirectoryA/GetPrintProcessorDirectoryW.
607  *
608  * @param pName
609  * Server Name. Ignored here, because every caller of LocalGetPrintProcessorDirectory is interested in the local directory.
610  *
611  * @param pEnvironment
612  * One of the predefined operating system and architecture "environment" strings (like "Windows NT x86").
613  *
614  * @param Level
615  * The level of the (non-existing) structure supplied through pPrintProcessorInfo. This must be 1.
616  *
617  * @param pPrintProcessorInfo
618  * Pointer to the buffer that receives the full path to the Print Processor directory.
619  * Can be NULL if you just want to know the required size of the buffer.
620  *
621  * @param cbBuf
622  * Size of the buffer you supplied for pPrintProcessorInfo, in bytes.
623  *
624  * @param pcbNeeded
625  * Pointer to a variable that receives the required size of the buffer for pPrintProcessorInfo, in bytes.
626  * This parameter mustn't be NULL!
627  *
628  * @return
629  * TRUE if we successfully copied the directory into pPrintProcessorInfo, FALSE otherwise.
630  * A more specific error code can be obtained through GetLastError.
631  */
632 BOOL WINAPI
LocalGetPrintProcessorDirectory(PWSTR pName,PWSTR pEnvironment,DWORD Level,PBYTE pPrintProcessorInfo,DWORD cbBuf,PDWORD pcbNeeded)633 LocalGetPrintProcessorDirectory(PWSTR pName, PWSTR pEnvironment, DWORD Level, PBYTE pPrintProcessorInfo, DWORD cbBuf, PDWORD pcbNeeded)
634 {
635     const WCHAR wszPath[] = L"\\PRTPROCS\\";
636     const DWORD cchPath = _countof(wszPath) - 1;
637 
638     DWORD cbDirectoryName;
639     DWORD dwErrorCode;
640     HKEY hKey = NULL;
641     PWSTR pwszDirectory = (PWSTR)pPrintProcessorInfo;
642 
643     TRACE("LocalGetPrintProcessorDirectory(%S, %S, %lu, %p, %lu, %p)\n", pName, pEnvironment, Level, pPrintProcessorInfo, cbBuf, pcbNeeded);
644 
645     // Verify pEnvironment and open its registry key.
646     dwErrorCode = _OpenEnvironment(pEnvironment, &hKey);
647     if (dwErrorCode != ERROR_SUCCESS)
648     {
649         ERR("_OpenEnvironment failed with error %lu!\n", dwErrorCode);
650         goto Cleanup;
651     }
652 
653     // Determine the size of the required buffer.
654     dwErrorCode = (DWORD)RegQueryValueExW(hKey, L"Directory", NULL, NULL, NULL, &cbDirectoryName);
655     if (dwErrorCode != ERROR_SUCCESS)
656     {
657         ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
658         goto Cleanup;
659     }
660 
661     *pcbNeeded = (cchSpoolDirectory + cchPath) * sizeof(WCHAR) + cbDirectoryName;
662 
663     // Is the supplied buffer large enough?
664     if (cbBuf < *pcbNeeded)
665     {
666         dwErrorCode = ERROR_INSUFFICIENT_BUFFER;
667         goto Cleanup;
668     }
669 
670     // Copy the path to the "prtprocs" directory into pPrintProcessorInfo
671     CopyMemory(pwszDirectory, wszSpoolDirectory, cchSpoolDirectory * sizeof(WCHAR));
672     CopyMemory(&pwszDirectory[cchSpoolDirectory], wszPath, cchPath * sizeof(WCHAR));
673 
674     // Get the directory name from the registry.
675     dwErrorCode = (DWORD)RegQueryValueExW(hKey, L"Directory", NULL, NULL, (PBYTE)&pwszDirectory[cchSpoolDirectory + cchPath], &cbDirectoryName);
676     if (dwErrorCode != ERROR_SUCCESS)
677     {
678         ERR("RegQueryValueExW failed with status %lu!\n", dwErrorCode);
679         goto Cleanup;
680     }
681 
682     // We've finished successfully!
683     dwErrorCode = ERROR_SUCCESS;
684 
685 Cleanup:
686     if (hKey)
687         RegCloseKey(hKey);
688 
689     SetLastError(dwErrorCode);
690     return (dwErrorCode == ERROR_SUCCESS);
691 }
692