1 /*
2  * PROJECT:     ReactOS Local Spooler API Tests
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Test list
5  * COPYRIGHT:   Copyright 2015-2016 Colin Finck (colin@reactos.org)
6  */
7 
8 /*
9  * The original localspl.dll from Windows Server 2003 is not easily testable.
10  * It relies on a proper initialization inside spoolsv.exe, so we can't just load it in an API-Test as usual.
11  * See https://www.reactos.org/pipermail/ros-dev/2015-June/017395.html for more information.
12  *
13  * To make testing possible anyway, this program basically does four things:
14  *     - Injecting our testing code into spoolsv.exe.
15  *     - Registering and running us as a service in the SYSTEM security context like spoolsv.exe, so that injection is possible at all.
16  *     - Sending the test name and receiving the console output over named pipes.
17  *     - Redirecting the received console output to stdout again, so it looks and feels like a standard API-Test.
18  *
19  * To simplify debugging of the injected code, it is entirely separated into a DLL file localspl_apitest.dll.
20  * What we actually inject is a LoadLibraryW call, so that the DLL is loaded gracefully without any hacks.
21  * Therefore, you can just attach your debugger to the spoolsv.exe process and set breakpoints on the localspl_apitest.dll code.
22  */
23 
24 #include <apitest.h>
25 
26 #define WIN32_NO_STATUS
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <windef.h>
30 #include <winbase.h>
31 #include <wingdi.h>
32 #include <winreg.h>
33 #include <winsvc.h>
34 #include <winspool.h>
35 #include <winsplp.h>
36 
37 #include "localspl_apitest.h"
38 
39 
40 static void
41 _RunRemoteTest(const char* szTestName)
42 {
43     BOOL bSuccessful = FALSE;
44     char szBuffer[1024];
45     DWORD cbRead;
46     DWORD cbWritten;
47     HANDLE hCommandPipe = INVALID_HANDLE_VALUE;
48     HANDLE hFind = INVALID_HANDLE_VALUE;
49     HANDLE hOutputPipe = INVALID_HANDLE_VALUE;
50     PWSTR p;
51     SC_HANDLE hSC = NULL;
52     SC_HANDLE hService = NULL;
53     SERVICE_STATUS ServiceStatus;
54     WCHAR wszFilePath[MAX_PATH + 20];
55     WIN32_FIND_DATAW fd;
56 
57     // Do a dummy EnumPrintersW call.
58     // This guarantees that the Spooler Service has actually loaded localspl.dll, which is a requirement for our injected DLL to work properly.
59     EnumPrintersW(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NAME, NULL, 1, NULL, 0, &cbRead, &cbWritten);
60 
61     // Get the full path to our EXE file.
62     if (!GetModuleFileNameW(NULL, wszFilePath, MAX_PATH))
63     {
64         skip("GetModuleFileNameW failed with error %lu!\n", GetLastError());
65         goto Cleanup;
66     }
67 
68     // Replace the extension.
69     p = wcsrchr(wszFilePath, L'.');
70     if (!p)
71     {
72         skip("File path has no file extension: %S\n", wszFilePath);
73         goto Cleanup;
74     }
75 
76     wcscpy(p, L".dll");
77 
78     // Check if the corresponding DLL file exists.
79     hFind = FindFirstFileW(wszFilePath, &fd);
80     if (hFind == INVALID_HANDLE_VALUE)
81     {
82         skip("My DLL file \"%S\" does not exist!\n", wszFilePath);
83         goto Cleanup;
84     }
85 
86     // Change the extension back to .exe and add the parameters.
87     wcscpy(p, L".exe service dummy");
88 
89     // Open a handle to the service manager.
90     hSC = OpenSCManagerW(NULL, NULL, SC_MANAGER_ALL_ACCESS);
91     if (!hSC)
92     {
93         skip("OpenSCManagerW failed with error %lu!\n", GetLastError());
94         goto Cleanup;
95     }
96 
97     // Ensure that the spooler service is running.
98     hService = OpenServiceW(hSC, L"spooler", SERVICE_QUERY_STATUS);
99     if (!hService)
100     {
101         skip("OpenServiceW failed for the spooler service with error %lu!\n", GetLastError());
102         goto Cleanup;
103     }
104 
105     if (!QueryServiceStatus(hService, &ServiceStatus))
106     {
107         skip("QueryServiceStatus failed for the spooler service with error %lu!\n", GetLastError());
108         goto Cleanup;
109     }
110 
111     if (ServiceStatus.dwCurrentState != SERVICE_RUNNING)
112     {
113         skip("Spooler Service is not running!\n");
114         goto Cleanup;
115     }
116 
117     CloseServiceHandle(hService);
118 
119     // Try to open the service if we've created it in a previous run.
120     hService = OpenServiceW(hSC, SERVICE_NAME, SERVICE_ALL_ACCESS);
121     if (!hService)
122     {
123         if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST)
124         {
125             // Create the service.
126             hService = CreateServiceW(hSC, SERVICE_NAME, NULL, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, wszFilePath, NULL, NULL, NULL, NULL, NULL);
127             if (!hService)
128             {
129                 skip("CreateServiceW failed with error %lu!\n", GetLastError());
130                 goto Cleanup;
131             }
132         }
133         else
134         {
135             skip("OpenServiceW failed with error %lu!\n", GetLastError());
136             goto Cleanup;
137         }
138     }
139 
140     // Create pipes for the communication with the injected DLL.
141     hCommandPipe = CreateNamedPipeW(COMMAND_PIPE_NAME, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
142     if (hCommandPipe == INVALID_HANDLE_VALUE)
143     {
144         skip("CreateNamedPipeW failed for the command pipe with error %lu!\n", GetLastError());
145         goto Cleanup;
146     }
147 
148     hOutputPipe = CreateNamedPipeW(OUTPUT_PIPE_NAME, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 1024, 1024, 10000, NULL);
149     if (hOutputPipe == INVALID_HANDLE_VALUE)
150     {
151         skip("CreateNamedPipeW failed for the output pipe with error %lu!\n", GetLastError());
152         goto Cleanup;
153     }
154 
155     // Start the service with "service" and a dummy parameter (to distinguish it from a call by rosautotest to localspl_apitest:service)
156     if (!StartServiceW(hService, 0, NULL))
157     {
158         skip("StartServiceW failed with error %lu!\n", GetLastError());
159         goto Cleanup;
160     }
161 
162     // Wait till it has injected the DLL and the DLL expects its test name.
163     if (!ConnectNamedPipe(hCommandPipe, NULL) && GetLastError() != ERROR_PIPE_CONNECTED)
164     {
165         skip("ConnectNamedPipe failed for the command pipe with error %lu!\n", GetLastError());
166         goto Cleanup;
167     }
168 
169     // Send the test name.
170     if (!WriteFile(hCommandPipe, szTestName, strlen(szTestName) + sizeof(char), &cbWritten, NULL))
171     {
172         skip("WriteFile failed with error %lu!\n", GetLastError());
173         goto Cleanup;
174     }
175 
176     // Now wait for the DLL to connect to the output pipe.
177     if (!ConnectNamedPipe(hOutputPipe, NULL))
178     {
179         skip("ConnectNamedPipe failed for the output pipe with error %lu!\n", GetLastError());
180         goto Cleanup;
181     }
182 
183     // Get all testing messages from the pipe and output them on stdout.
184     while (ReadFile(hOutputPipe, szBuffer, sizeof(szBuffer), &cbRead, NULL) && cbRead)
185         fwrite(szBuffer, sizeof(char), cbRead, stdout);
186 
187     bSuccessful = TRUE;
188 
189 Cleanup:
190     if (hCommandPipe != INVALID_HANDLE_VALUE)
191         CloseHandle(hCommandPipe);
192 
193     if (hOutputPipe != INVALID_HANDLE_VALUE)
194         CloseHandle(hOutputPipe);
195 
196     if (hFind != INVALID_HANDLE_VALUE)
197         FindClose(hFind);
198 
199     if (hService)
200         CloseServiceHandle(hService);
201 
202     if (hSC)
203         CloseServiceHandle(hSC);
204 
205     // If we successfully received test output through the named pipe, we have also output a summary line already.
206     // Prevent the testing framework from outputting another "0 tests executed" line in this case.
207     if (bSuccessful)
208         ExitProcess(0);
209 }
210 
211 START_TEST(fpEnumPrinters)
212 {
213     _RunRemoteTest("fpEnumPrinters");
214 }
215 
216 START_TEST(fpGetPrintProcessorDirectory)
217 {
218     _RunRemoteTest("fpGetPrintProcessorDirectory");
219 }
220 
221 START_TEST(fpSetJob)
222 {
223     _RunRemoteTest("fpSetJob");
224 }
225