[ROSTESTS] Use the new header with SPDX license identifier for my printing-related...
[reactos.git] / rostests / apitests / localspl / service.c
1 /*
2 * PROJECT: ReactOS Local Spooler API Tests
3 * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4 * PURPOSE: Functions needed to run our code as a service. This is needed to run in SYSTEM security context.
5 * COPYRIGHT: Copyright 2015 Colin Finck (colin@reactos.org)
6 */
7
8 #include <apitest.h>
9
10 #define WIN32_NO_STATUS
11 #include <windef.h>
12 #include <winbase.h>
13 #include <wingdi.h>
14 #include <winreg.h>
15 #include <winsvc.h>
16 #include <winspool.h>
17 #include <winsplp.h>
18 #include <tlhelp32.h>
19
20 #include "localspl_apitest.h"
21
22 //#define NDEBUG
23 #include <debug.h>
24
25
26 static void
27 _DoDLLInjection()
28 {
29 DWORD cbDLLPath;
30 HANDLE hProcess;
31 HANDLE hSnapshot;
32 HANDLE hThread;
33 PROCESSENTRY32W pe;
34 PVOID pLoadLibraryAddress;
35 PVOID pLoadLibraryArgument;
36 PWSTR p;
37 WCHAR wszFilePath[MAX_PATH];
38
39 // Get the full path to our EXE file.
40 if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath)))
41 {
42 DPRINT("GetModuleFileNameW failed with error %lu!\n", GetLastError());
43 return;
44 }
45
46 // Replace the extension.
47 p = wcsrchr(wszFilePath, L'.');
48 if (!p)
49 {
50 DPRINT("File path has no file extension: %S\n", wszFilePath);
51 return;
52 }
53
54 wcscpy(p, L".dll");
55 cbDLLPath = (wcslen(wszFilePath) + 1) * sizeof(WCHAR);
56
57 // Create a snapshot of the currently running processes.
58 hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
59 if (hSnapshot == INVALID_HANDLE_VALUE)
60 {
61 DPRINT("CreateToolhelp32Snapshot failed with error %lu!\n", GetLastError());
62 return;
63 }
64
65 // Enumerate through all running processes.
66 pe.dwSize = sizeof(pe);
67 if (!Process32FirstW(hSnapshot, &pe))
68 {
69 DPRINT("Process32FirstW failed with error %lu!\n", GetLastError());
70 return;
71 }
72
73 do
74 {
75 // Check if this is the spooler server process.
76 if (wcsicmp(pe.szExeFile, L"spoolsv.exe") != 0)
77 continue;
78
79 // Open a handle to the process.
80 hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe.th32ProcessID);
81 if (!hProcess)
82 {
83 DPRINT("OpenProcess failed with error %lu!\n", GetLastError());
84 return;
85 }
86
87 // Get the address of LoadLibraryW.
88 pLoadLibraryAddress = (PVOID)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");
89 if (!pLoadLibraryAddress)
90 {
91 DPRINT("GetProcAddress failed with error %lu!\n", GetLastError());
92 return;
93 }
94
95 // Allocate memory for the DLL path in the spooler process.
96 pLoadLibraryArgument = VirtualAllocEx(hProcess, NULL, cbDLLPath, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
97 if (!pLoadLibraryArgument)
98 {
99 DPRINT("VirtualAllocEx failed with error %lu!\n", GetLastError());
100 return;
101 }
102
103 // Write the DLL path to the process memory.
104 if (!WriteProcessMemory(hProcess, pLoadLibraryArgument, wszFilePath, cbDLLPath, NULL))
105 {
106 DPRINT("WriteProcessMemory failed with error %lu!\n", GetLastError());
107 return;
108 }
109
110 // Create a new thread in the spooler process that calls LoadLibraryW as the start routine with our DLL as the argument.
111 // This effectively injects our DLL into the spooler process and we can inspect localspl.dll there just like the spooler.
112 hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryAddress, pLoadLibraryArgument, 0, NULL);
113 if (!hThread)
114 {
115 DPRINT("CreateRemoteThread failed with error %lu!\n", GetLastError());
116 return;
117 }
118
119 CloseHandle(hThread);
120 break;
121 }
122 while (Process32NextW(hSnapshot, &pe));
123 }
124
125 static DWORD WINAPI
126 _ServiceControlHandlerEx(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext)
127 {
128 return NO_ERROR;
129 }
130
131 static void WINAPI
132 _ServiceMain(DWORD dwArgc, LPWSTR* lpszArgv)
133 {
134 SERVICE_STATUS_HANDLE hServiceStatus;
135 SERVICE_STATUS ServiceStatus;
136
137 UNREFERENCED_PARAMETER(dwArgc);
138 UNREFERENCED_PARAMETER(lpszArgv);
139
140 // Register our service for control.
141 hServiceStatus = RegisterServiceCtrlHandlerExW(SERVICE_NAME, _ServiceControlHandlerEx, NULL);
142
143 // Report SERVICE_RUNNING status.
144 ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
145 ServiceStatus.dwServiceSpecificExitCode = 0;
146 ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
147 ServiceStatus.dwWaitHint = 4000;
148 ServiceStatus.dwWin32ExitCode = NO_ERROR;
149 ServiceStatus.dwCurrentState = SERVICE_RUNNING;
150 SetServiceStatus(hServiceStatus, &ServiceStatus);
151
152 // Do our funky crazy stuff.
153 _DoDLLInjection();
154
155 // Our work is done.
156 ServiceStatus.dwCurrentState = SERVICE_STOPPED;
157 SetServiceStatus(hServiceStatus, &ServiceStatus);
158 }
159
160 START_TEST(service)
161 {
162 int argc;
163 char** argv;
164
165 SERVICE_TABLE_ENTRYW ServiceTable[] =
166 {
167 { SERVICE_NAME, _ServiceMain },
168 { NULL, NULL }
169 };
170
171 // This is no real test, but an easy way to integrate the service handler routines into the API-Test executable.
172 // Therefore, bail out if someone tries to run "service" as a usual test.
173 argc = winetest_get_mainargs(&argv);
174 if (argc != 3)
175 return;
176
177 // If we have exactly 3 arguments, we're run as a service, so initialize the corresponding service handler functions.
178 StartServiceCtrlDispatcherW(ServiceTable);
179
180 // Prevent the testing framework from outputting a "0 tests executed" line here.
181 ExitProcess(0);
182 }