1 /*
2  * PROJECT:     ReactOS Local Spooler
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Functions related to Printer Configuration Data
5  * COPYRIGHT:   Copyright 2017 Colin Finck (colin@reactos.org)
6  */
7 
8 #include "precomp.h"
9 
10 DWORD WINAPI
11 LocalGetPrinterData(HANDLE hPrinter, PWSTR pValueName, PDWORD pType, PBYTE pData, DWORD nSize, PDWORD pcbNeeded)
12 {
13     TRACE("LocalGetPrinterData(%p, %S, %p, %p, %lu, %p)\n", hPrinter, pValueName, pType, pData, nSize, pcbNeeded);
14 
15     // The ReactOS Printing Stack forwards all GetPrinterData calls to GetPrinterDataEx as soon as possible.
16     // This function may only be called if localspl.dll is used together with Windows Printing Stack components.
17     WARN("This function should never be called!\n");
18     return LocalGetPrinterDataEx(hPrinter, L"PrinterDriverData", pValueName, pType, pData, nSize, pcbNeeded);
19 }
20 
21 static DWORD
22 _MakePrinterSubKey(PLOCAL_PRINTER_HANDLE pPrinterHandle, PCWSTR pKeyName, PWSTR* ppwszSubKey)
23 {
24     const WCHAR wszBackslash[] = L"\\";
25 
26     size_t cbSubKey;
27     PWSTR p;
28 
29     // Sanity check
30     if (!pKeyName || !*pKeyName)
31         return ERROR_INVALID_PARAMETER;
32 
33     // Allocate a buffer for the subkey "PrinterName\KeyName".
34     cbSubKey = (wcslen(pPrinterHandle->pPrinter->pwszPrinterName) + 1 + wcslen(pKeyName) + 1) * sizeof(WCHAR);
35     *ppwszSubKey = DllAllocSplMem(cbSubKey);
36     if (!*ppwszSubKey)
37         return ERROR_NOT_ENOUGH_MEMORY;
38 
39     // Concatenate the subkey.
40     p = *ppwszSubKey;
41     StringCbCopyExW(p, cbSubKey, pPrinterHandle->pPrinter->pwszPrinterName, &p, &cbSubKey, 0);
42     StringCbCopyExW(p, cbSubKey, wszBackslash, &p, &cbSubKey, 0);
43     StringCbCopyExW(p, cbSubKey, pKeyName, &p, &cbSubKey, 0);
44 
45     return ERROR_SUCCESS;
46 }
47 
48 static DWORD
49 _LocalGetPrinterHandleData(PLOCAL_PRINTER_HANDLE pPrinterHandle, PCWSTR pKeyName, PCWSTR pValueName, PDWORD pType, PBYTE pData, DWORD nSize, PDWORD pcbNeeded)
50 {
51     DWORD dwErrorCode;
52     HKEY hKey = NULL;
53     PWSTR pwszSubKey = NULL;
54 
55     dwErrorCode = _MakePrinterSubKey(pPrinterHandle, pKeyName, &pwszSubKey);
56     if (dwErrorCode != ERROR_SUCCESS)
57         goto Cleanup;
58 
59     // Open the subkey.
60     dwErrorCode = (DWORD)RegOpenKeyExW(hPrintersKey, pwszSubKey, 0, KEY_READ, &hKey);
61     if (dwErrorCode != ERROR_SUCCESS)
62     {
63         ERR("RegOpenKeyExW failed for \"%S\" with error %lu!\n", pwszSubKey, dwErrorCode);
64         goto Cleanup;
65     }
66 
67     // Query the desired value.
68     *pcbNeeded = nSize;
69     dwErrorCode = (DWORD)RegQueryValueExW(hKey, pValueName, NULL, pType, pData, pcbNeeded);
70 
71 Cleanup:
72     if (hKey)
73         RegCloseKey(hKey);
74 
75     if (pwszSubKey)
76         DllFreeSplMem(pwszSubKey);
77 
78     return dwErrorCode;
79 }
80 
81 static DWORD
82 _LocalGetPrintServerHandleData(PCWSTR pValueName, PDWORD pType, PBYTE pData, DWORD nSize, PDWORD pcbNeeded)
83 {
84     DWORD dwErrorCode;
85 
86     if (_wcsicmp(pValueName, SPLREG_DEFAULT_SPOOL_DIRECTORY) == 0 ||
87         _wcsicmp(pValueName, SPLREG_PORT_THREAD_PRIORITY) == 0 ||
88         _wcsicmp(pValueName, SPLREG_SCHEDULER_THREAD_PRIORITY) == 0 ||
89         _wcsicmp(pValueName, SPLREG_BEEP_ENABLED) == 0 ||
90         _wcsicmp(pValueName, SPLREG_ALLOW_USER_MANAGEFORMS) == 0)
91     {
92         *pcbNeeded = nSize;
93         return (DWORD)RegQueryValueExW(hPrintersKey, pValueName, NULL, pType, pData, pcbNeeded);
94     }
95     else if (_wcsicmp(pValueName, SPLREG_PORT_THREAD_PRIORITY_DEFAULT) == 0 ||
96         _wcsicmp(pValueName, SPLREG_SCHEDULER_THREAD_PRIORITY_DEFAULT) == 0)
97     {
98         // Store a DWORD value as REG_NONE.
99         *pType = REG_NONE;
100         *pcbNeeded = sizeof(DWORD);
101         if (nSize < *pcbNeeded)
102             return ERROR_MORE_DATA;
103 
104         // Apparently, these values don't serve a purpose anymore.
105         *((PDWORD)pData) = 0;
106         return ERROR_SUCCESS;
107     }
108     else if (_wcsicmp(pValueName, SPLREG_NET_POPUP) == 0 ||
109         _wcsicmp(pValueName, SPLREG_RETRY_POPUP) == 0 ||
110         _wcsicmp(pValueName, SPLREG_NET_POPUP_TO_COMPUTER) == 0 ||
111         _wcsicmp(pValueName, SPLREG_EVENT_LOG) == 0 ||
112         _wcsicmp(pValueName, SPLREG_RESTART_JOB_ON_POOL_ERROR) == 0 ||
113         _wcsicmp(pValueName, SPLREG_RESTART_JOB_ON_POOL_ENABLED) == 0)
114     {
115         HKEY hKey;
116 
117         dwErrorCode = (DWORD)RegOpenKeyExW(hPrintKey, L"Providers", 0, KEY_READ, &hKey);
118         if (dwErrorCode != ERROR_SUCCESS)
119         {
120             ERR("RegOpenKeyExW failed for \"Providers\" with error %lu!\n", dwErrorCode);
121             return dwErrorCode;
122         }
123 
124         *pcbNeeded = nSize;
125         dwErrorCode = (DWORD)RegQueryValueExW(hKey, pValueName, NULL, pType, pData, pcbNeeded);
126         RegCloseKey(hKey);
127         return dwErrorCode;
128     }
129     else if (_wcsicmp(pValueName, SPLREG_MAJOR_VERSION) == 0)
130     {
131         // Store a DWORD value as REG_NONE.
132         *pType = REG_NONE;
133         *pcbNeeded = sizeof(DWORD);
134         if (nSize < *pcbNeeded)
135             return ERROR_MORE_DATA;
136 
137         // Apparently, these values don't serve a purpose anymore.
138         *((PDWORD)pData) = dwSpoolerMajorVersion;
139         return ERROR_SUCCESS;
140     }
141     else if (_wcsicmp(pValueName, SPLREG_MINOR_VERSION) == 0)
142     {
143         // Store a DWORD value as REG_NONE.
144         *pType = REG_NONE;
145         *pcbNeeded = sizeof(DWORD);
146         if (nSize < *pcbNeeded)
147             return ERROR_MORE_DATA;
148 
149         // Apparently, these values don't serve a purpose anymore.
150         *((PDWORD)pData) = dwSpoolerMinorVersion;
151         return ERROR_SUCCESS;
152     }
153     else if (_wcsicmp(pValueName, SPLREG_ARCHITECTURE) == 0)
154     {
155         // Store a string as REG_NONE with the length of the environment name string.
156         *pType = REG_NONE;
157         *pcbNeeded = cbCurrentEnvironment;
158         if (nSize < *pcbNeeded)
159             return ERROR_MORE_DATA;
160 
161         // Copy the environment name as the output value for SPLREG_ARCHITECTURE.
162         CopyMemory(pData, wszCurrentEnvironment, cbCurrentEnvironment);
163         return ERROR_SUCCESS;
164     }
165     else if (_wcsicmp(pValueName, SPLREG_OS_VERSION) == 0)
166     {
167         POSVERSIONINFOW pInfo = (POSVERSIONINFOW)pData;
168 
169         // Store the OSVERSIONINFOW structure as REG_NONE.
170         *pType = REG_NONE;
171         *pcbNeeded = sizeof(OSVERSIONINFOW);
172         if (nSize < *pcbNeeded)
173             return ERROR_MORE_DATA;
174 
175         // Return OS version information.
176         pInfo->dwOSVersionInfoSize = sizeof(OSVERSIONINFOW);
177         GetVersionExW(pInfo);
178         return ERROR_SUCCESS;
179     }
180     else if (_wcsicmp(pValueName, SPLREG_OS_VERSIONEX) == 0)
181     {
182         POSVERSIONINFOEXW pInfo = (POSVERSIONINFOEXW)pData;
183 
184         // Store the OSVERSIONINFOEXW structure as REG_NONE.
185         *pType = REG_NONE;
186         *pcbNeeded = sizeof(OSVERSIONINFOEXW);
187         if (nSize < *pcbNeeded)
188             return ERROR_MORE_DATA;
189 
190         // Return extended OS version information.
191         pInfo->dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
192         GetVersionExW((POSVERSIONINFOW)pInfo);
193         return ERROR_SUCCESS;
194     }
195     else if (_wcsicmp(pValueName, SPLREG_DS_PRESENT) == 0)
196     {
197         PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pInfo;
198 
199         // We want to store a REG_DWORD value.
200         *pType = REG_DWORD;
201         *pcbNeeded = sizeof(DWORD);
202         if (nSize < *pcbNeeded)
203             return ERROR_MORE_DATA;
204 
205         // Get information about the domain membership of this computer.
206         dwErrorCode = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE*)&pInfo);
207         if (dwErrorCode != ERROR_SUCCESS)
208         {
209             ERR("DsRoleGetPrimaryDomainInformation failed with error %lu!\n", dwErrorCode);
210             return dwErrorCode;
211         }
212 
213         // Return whether this computer is a workstation or server inside a domain.
214         *((PDWORD)pData) = (pInfo->MachineRole == DsRole_RoleMemberWorkstation || pInfo->MachineRole == DsRole_RoleMemberServer);
215         DsRoleFreeMemory(pInfo);
216         return ERROR_SUCCESS;
217     }
218     else if (_wcsicmp(pValueName, SPLREG_DS_PRESENT_FOR_USER) == 0)
219     {
220         DWORD cch;
221         PWSTR p;
222         WCHAR wszComputerName[MAX_COMPUTERNAME_LENGTH + 1];
223         WCHAR wszUserSam[UNLEN + 1];
224 
225         // We want to store a REG_DWORD value.
226         *pType = REG_DWORD;
227         *pcbNeeded = sizeof(DWORD);
228         if (nSize < *pcbNeeded)
229             return ERROR_MORE_DATA;
230 
231         // Get the local Computer Name.
232         cch = MAX_COMPUTERNAME_LENGTH + 1;
233         if (!GetComputerNameW(wszComputerName, &cch))
234         {
235             dwErrorCode = GetLastError();
236             ERR("GetComputerNameW failed with error %lu!\n", dwErrorCode);
237             return dwErrorCode;
238         }
239 
240         // Get the User Name in the SAM format.
241         // This could either be:
242         //     COMPUTERNAME\User
243         //     DOMAINNAME\User
244         cch = UNLEN + 1;
245         if (!GetUserNameExW(NameSamCompatible, wszUserSam, &cch))
246         {
247             dwErrorCode = GetLastError();
248             ERR("GetUserNameExW failed with error %lu!\n", dwErrorCode);
249             return dwErrorCode;
250         }
251 
252         // Terminate the SAM-formatted User Name at the backslash.
253         p = wcschr(wszUserSam, L'\\');
254         *p = 0;
255 
256         // Compare it with the Computer Name.
257         // If they differ, this User is part of a domain.
258         *((PDWORD)pData) = (wcscmp(wszUserSam, wszComputerName) != 0);
259         return ERROR_SUCCESS;
260     }
261     else if (_wcsicmp(pValueName, SPLREG_REMOTE_FAX) == 0)
262     {
263         // Store a DWORD value as REG_NONE.
264         *pType = REG_NONE;
265         *pcbNeeded = sizeof(DWORD);
266         if (nSize < *pcbNeeded)
267             return ERROR_MORE_DATA;
268 
269         // TODO: We don't support any fax service yet, but let's return the same value as Windows Server 2003 here.
270         *((PDWORD)pData) = 1;
271         return ERROR_SUCCESS;
272     }
273     else if (_wcsicmp(pValueName, SPLREG_DNS_MACHINE_NAME) == 0)
274     {
275         DWORD cchDnsName = 0;
276 
277         // Get the length of the fully-qualified computer DNS name.
278         GetComputerNameExW(ComputerNameDnsFullyQualified, NULL, &cchDnsName);
279         dwErrorCode = GetLastError();
280         if (dwErrorCode != ERROR_MORE_DATA)
281         {
282             ERR("GetComputerNameExW failed with error %lu!\n", dwErrorCode);
283             return dwErrorCode;
284         }
285 
286         // Check if our supplied buffer is large enough.
287         *pType = REG_SZ;
288         *pcbNeeded = cchDnsName * sizeof(WCHAR);
289         if (nSize < *pcbNeeded)
290             return ERROR_MORE_DATA;
291 
292         // Get the actual DNS name.
293         if (!GetComputerNameExW(ComputerNameDnsFullyQualified, (PWSTR)pData, &cchDnsName))
294         {
295             dwErrorCode = GetLastError();
296             ERR("GetComputerNameExW failed with error %lu!\n", dwErrorCode);
297             return dwErrorCode;
298         }
299 
300         // Lowercase the output just like Windows does.
301         _wcslwr((PWSTR)pData);
302         return ERROR_SUCCESS;
303     }
304     else
305     {
306         // For all other, unknown settings, we just return ERROR_INVALID_PARAMETER.
307         // That also includes SPLREG_WEBSHAREMGMT, which is supported in Windows Server 2003 according to the documentation,
308         // but is actually not!
309         return ERROR_INVALID_PARAMETER;
310     }
311 }
312 
313 DWORD WINAPI
314 LocalGetPrinterDataEx(HANDLE hPrinter, PCWSTR pKeyName, PCWSTR pValueName, PDWORD pType, PBYTE pData, DWORD nSize, PDWORD pcbNeeded)
315 {
316     BYTE Temp;
317     DWORD dwErrorCode;
318     DWORD dwTemp;
319     PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
320 
321     TRACE("LocalGetPrinterDataEx(%p, %S, %S, %p, %p, %lu, %p)\n", hPrinter, pKeyName, pValueName, pType, pData, nSize, pcbNeeded);
322 
323     // Even if GetPrinterDataExW in winspool ensures that the RPC function is never called without a valid pointer for pType,
324     // it's officially optional. Windows' fpGetPrinterDataEx also works with NULL for pType!
325     // Ensure here that it is always set to simplify the code later.
326     if (!pType)
327         pType = &dwTemp;
328 
329     // pData is later fed to RegQueryValueExW in many cases. When calling it with zero buffer size, RegQueryValueExW returns a
330     // different error code based on whether pData is NULL or something else.
331     // Ensure here that ERROR_MORE_DATA is always returned.
332     if (!pData)
333         pData = &Temp;
334 
335     if (!pHandle)
336     {
337         dwErrorCode = ERROR_INVALID_HANDLE;
338     }
339     else if (!pcbNeeded)
340     {
341         dwErrorCode = ERROR_INVALID_PARAMETER;
342     }
343     else if (pHandle->HandleType == HandleType_Printer)
344     {
345         dwErrorCode = _LocalGetPrinterHandleData(pHandle->pSpecificHandle, pKeyName, pValueName, pType, pData, nSize, pcbNeeded);
346     }
347     else if (pHandle->HandleType == HandleType_PrintServer)
348     {
349         dwErrorCode = _LocalGetPrintServerHandleData(pValueName, pType, pData, nSize, pcbNeeded);
350     }
351     else
352     {
353         dwErrorCode = ERROR_INVALID_HANDLE;
354     }
355 
356     SetLastError(dwErrorCode);
357     return dwErrorCode;
358 }
359 
360 DWORD WINAPI
361 LocalSetPrinterData(HANDLE hPrinter, PWSTR pValueName, DWORD Type, PBYTE pData, DWORD cbData)
362 {
363     TRACE("LocalSetPrinterData(%p, %S, %lu, %p, %lu)\n", hPrinter, pValueName, Type, pData, cbData);
364 
365     // The ReactOS Printing Stack forwards all SetPrinterData calls to SetPrinterDataEx as soon as possible.
366     // This function may only be called if localspl.dll is used together with Windows Printing Stack components.
367     WARN("This function should never be called!\n");
368     return LocalSetPrinterDataEx(hPrinter, L"PrinterDriverData", pValueName, Type, pData, cbData);
369 }
370 
371 static DWORD
372 _LocalSetPrinterHandleData(PLOCAL_PRINTER_HANDLE pPrinterHandle, PCWSTR pKeyName, PCWSTR pValueName, DWORD Type, PBYTE pData, DWORD cbData)
373 {
374     DWORD dwErrorCode;
375     HKEY hKey = NULL;
376     PWSTR pwszSubKey = NULL;
377 
378     dwErrorCode = _MakePrinterSubKey(pPrinterHandle, pKeyName, &pwszSubKey);
379     if (dwErrorCode != ERROR_SUCCESS)
380         goto Cleanup;
381 
382     // Open the subkey.
383     dwErrorCode = (DWORD)RegOpenKeyExW(hPrintersKey, pwszSubKey, 0, KEY_SET_VALUE, &hKey);
384     if (dwErrorCode != ERROR_SUCCESS)
385     {
386         ERR("RegOpenKeyExW failed for \"%S\" with error %lu!\n", pwszSubKey, dwErrorCode);
387         goto Cleanup;
388     }
389 
390     // Set the value.
391     dwErrorCode = (DWORD)RegSetValueExW(hKey, pValueName, 0, Type, pData, cbData);
392 
393 Cleanup:
394     if (hKey)
395         RegCloseKey(hKey);
396 
397     if (pwszSubKey)
398         DllFreeSplMem(pwszSubKey);
399 
400     return dwErrorCode;
401 }
402 
403 static DWORD
404 _LocalSetPrintServerHandleData(PCWSTR pValueName, DWORD Type, PBYTE pData, DWORD cbData)
405 {
406     DWORD dwErrorCode;
407 
408     if (_wcsicmp(pValueName, SPLREG_DEFAULT_SPOOL_DIRECTORY) == 0 ||
409         _wcsicmp(pValueName, SPLREG_PORT_THREAD_PRIORITY) == 0 ||
410         _wcsicmp(pValueName, SPLREG_SCHEDULER_THREAD_PRIORITY) == 0 ||
411         _wcsicmp(pValueName, SPLREG_BEEP_ENABLED) == 0 ||
412         _wcsicmp(pValueName, SPLREG_ALLOW_USER_MANAGEFORMS) == 0)
413     {
414         return (DWORD)RegSetValueExW(hPrintersKey, pValueName, 0, Type, pData, cbData);
415     }
416     else if (_wcsicmp(pValueName, SPLREG_NET_POPUP) == 0 ||
417         _wcsicmp(pValueName, SPLREG_RETRY_POPUP) == 0 ||
418         _wcsicmp(pValueName, SPLREG_NET_POPUP_TO_COMPUTER) == 0 ||
419         _wcsicmp(pValueName, SPLREG_EVENT_LOG) == 0 ||
420         _wcsicmp(pValueName, SPLREG_RESTART_JOB_ON_POOL_ERROR) == 0 ||
421         _wcsicmp(pValueName, SPLREG_RESTART_JOB_ON_POOL_ENABLED) == 0 ||
422         _wcsicmp(pValueName, L"NoRemotePrinterDrivers") == 0)
423     {
424         HKEY hKey;
425 
426         dwErrorCode = (DWORD)RegOpenKeyExW(hPrintKey, L"Providers", 0, KEY_SET_VALUE, &hKey);
427         if (dwErrorCode != ERROR_SUCCESS)
428         {
429             ERR("RegOpenKeyExW failed for \"Providers\" with error %lu!\n", dwErrorCode);
430             return dwErrorCode;
431         }
432 
433         dwErrorCode = (DWORD)RegSetValueExW(hKey, pValueName, 0, Type, pData, cbData);
434         RegCloseKey(hKey);
435         return dwErrorCode;
436     }
437     else if (_wcsicmp(pValueName, SPLREG_WEBSHAREMGMT) == 0)
438     {
439         WARN("Attempting to set WebShareMgmt, which is based on IIS and therefore not supported. Returning fake success!\n");
440         return ERROR_SUCCESS;
441     }
442     else
443     {
444         // For all other, unknown settings, we just return ERROR_INVALID_PARAMETER.
445         return ERROR_INVALID_PARAMETER;
446     }
447 }
448 
449 DWORD WINAPI
450 LocalSetPrinterDataEx(HANDLE hPrinter, PCWSTR pKeyName, PCWSTR pValueName, DWORD Type, PBYTE pData, DWORD cbData)
451 {
452     DWORD dwErrorCode;
453     PLOCAL_HANDLE pHandle = (PLOCAL_HANDLE)hPrinter;
454 
455     TRACE("LocalSetPrinterDataEx(%p, %S, %S, %lu, %p, %lu)\n", hPrinter, pKeyName, pValueName, Type, pData, cbData);
456 
457     if (!pHandle)
458     {
459         dwErrorCode = ERROR_INVALID_HANDLE;
460     }
461     else if (pHandle->HandleType == HandleType_Printer)
462     {
463         dwErrorCode = _LocalSetPrinterHandleData(pHandle->pSpecificHandle, pKeyName, pValueName, Type, pData, cbData);
464     }
465     else if (pHandle->HandleType == HandleType_PrintServer)
466     {
467         dwErrorCode = _LocalSetPrintServerHandleData(pValueName, Type, pData, cbData);
468     }
469     else
470     {
471         dwErrorCode = ERROR_INVALID_HANDLE;
472     }
473 
474     SetLastError(dwErrorCode);
475     return dwErrorCode;
476 }
477