1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 #include <windows.h>
6
7 // Needed for CreateToolhelp32Snapshot
8 #include <tlhelp32.h>
9 #ifndef ONLY_SERVICE_LAUNCHING
10
11 #include <stdio.h>
12 #include "shlobj.h"
13 #include "updatehelper.h"
14 #include "uachelper.h"
15 #include "pathhash.h"
16 #include "mozilla/UniquePtr.h"
17
18 // Needed for PathAppendW
19 #include <shlwapi.h>
20
21 using mozilla::MakeUnique;
22 using mozilla::UniquePtr;
23
24 BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
25 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer,
26 LPCWSTR siblingFilePath,
27 LPCWSTR newFileName);
28
29 /**
30 * Obtains the path of a file in the same directory as the specified file.
31 *
32 * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
33 * @param siblingFIlePath The path of another file in the same directory
34 * @param newFileName The filename of another file in the same directory
35 * @return TRUE if successful
36 */
37 BOOL
PathGetSiblingFilePath(LPWSTR destinationBuffer,LPCWSTR siblingFilePath,LPCWSTR newFileName)38 PathGetSiblingFilePath(LPWSTR destinationBuffer,
39 LPCWSTR siblingFilePath,
40 LPCWSTR newFileName)
41 {
42 if (wcslen(siblingFilePath) >= MAX_PATH) {
43 return FALSE;
44 }
45
46 wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH);
47 if (!PathRemoveFileSpecW(destinationBuffer)) {
48 return FALSE;
49 }
50
51 if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH) {
52 return FALSE;
53 }
54
55 return PathAppendSafe(destinationBuffer, newFileName);
56 }
57
58 /**
59 * Starts the upgrade process for update of the service if it is
60 * already installed.
61 *
62 * @param installDir the installation directory where
63 * maintenanceservice_installer.exe is located.
64 * @return TRUE if successful
65 */
66 BOOL
StartServiceUpdate(LPCWSTR installDir)67 StartServiceUpdate(LPCWSTR installDir)
68 {
69 // Get a handle to the local computer SCM database
70 SC_HANDLE manager = OpenSCManager(nullptr, nullptr,
71 SC_MANAGER_ALL_ACCESS);
72 if (!manager) {
73 return FALSE;
74 }
75
76 // Open the service
77 SC_HANDLE svc = OpenServiceW(manager, SVC_NAME,
78 SERVICE_ALL_ACCESS);
79 if (!svc) {
80 CloseServiceHandle(manager);
81 return FALSE;
82 }
83
84 // If we reach here, then the service is installed, so
85 // proceed with upgrading it.
86
87 CloseServiceHandle(manager);
88
89 // The service exists and we opened it, get the config bytes needed
90 DWORD bytesNeeded;
91 if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) &&
92 GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
93 CloseServiceHandle(svc);
94 return FALSE;
95 }
96
97 // Get the service config information, in particular we want the binary
98 // path of the service.
99 UniquePtr<char[]> serviceConfigBuffer = MakeUnique<char[]>(bytesNeeded);
100 if (!QueryServiceConfigW(svc,
101 reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
102 bytesNeeded, &bytesNeeded)) {
103 CloseServiceHandle(svc);
104 return FALSE;
105 }
106
107 CloseServiceHandle(svc);
108
109 QUERY_SERVICE_CONFIGW &serviceConfig =
110 *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
111
112 PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
113
114 // Obtain the temp path of the maintenance service binary
115 WCHAR tmpService[MAX_PATH + 1] = { L'\0' };
116 if (!PathGetSiblingFilePath(tmpService, serviceConfig.lpBinaryPathName,
117 L"maintenanceservice_tmp.exe")) {
118 return FALSE;
119 }
120
121 // Get the new maintenance service path from the install dir
122 WCHAR newMaintServicePath[MAX_PATH + 1] = { L'\0' };
123 wcsncpy(newMaintServicePath, installDir, MAX_PATH);
124 PathAppendSafe(newMaintServicePath,
125 L"maintenanceservice.exe");
126
127 // Copy the temp file in alongside the maintenace service.
128 // This is a requirement for maintenance service upgrades.
129 if (!CopyFileW(newMaintServicePath, tmpService, FALSE)) {
130 return FALSE;
131 }
132
133 // Start the upgrade comparison process
134 STARTUPINFOW si = {0};
135 si.cb = sizeof(STARTUPINFOW);
136 // No particular desktop because no UI
137 si.lpDesktop = L"";
138 PROCESS_INFORMATION pi = {0};
139 WCHAR cmdLine[64] = { '\0' };
140 wcsncpy(cmdLine, L"dummyparam.exe upgrade",
141 sizeof(cmdLine) / sizeof(cmdLine[0]) - 1);
142 BOOL svcUpdateProcessStarted = CreateProcessW(tmpService,
143 cmdLine,
144 nullptr, nullptr, FALSE,
145 0,
146 nullptr, installDir, &si, &pi);
147 if (svcUpdateProcessStarted) {
148 CloseHandle(pi.hProcess);
149 CloseHandle(pi.hThread);
150 }
151 return svcUpdateProcessStarted;
152 }
153
154 #endif
155
156 /**
157 * Executes a maintenance service command
158 *
159 * @param argc The total number of arguments in argv
160 * @param argv An array of null terminated strings to pass to the service,
161 * @return ERROR_SUCCESS if the service command was started.
162 * Less than 16000, a windows system error code from StartServiceW
163 * More than 20000, 20000 + the last state of the service constant if
164 * the last state is something other than stopped.
165 * 17001 if the SCM could not be opened
166 * 17002 if the service could not be opened
167 */
168 DWORD
StartServiceCommand(int argc,LPCWSTR * argv)169 StartServiceCommand(int argc, LPCWSTR* argv)
170 {
171 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
172 if (lastState != SERVICE_STOPPED) {
173 return 20000 + lastState;
174 }
175
176 // Get a handle to the SCM database.
177 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
178 SC_MANAGER_CONNECT |
179 SC_MANAGER_ENUMERATE_SERVICE);
180 if (!serviceManager) {
181 return 17001;
182 }
183
184 // Get a handle to the service.
185 SC_HANDLE service = OpenServiceW(serviceManager,
186 SVC_NAME,
187 SERVICE_START);
188 if (!service) {
189 CloseServiceHandle(serviceManager);
190 return 17002;
191 }
192
193 // Wait at most 5 seconds trying to start the service in case of errors
194 // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
195 const DWORD maxWaitMS = 5000;
196 DWORD currentWaitMS = 0;
197 DWORD lastError = ERROR_SUCCESS;
198 while (currentWaitMS < maxWaitMS) {
199 BOOL result = StartServiceW(service, argc, argv);
200 if (result) {
201 lastError = ERROR_SUCCESS;
202 break;
203 } else {
204 lastError = GetLastError();
205 }
206 Sleep(100);
207 currentWaitMS += 100;
208 }
209 CloseServiceHandle(service);
210 CloseServiceHandle(serviceManager);
211 return lastError;
212 }
213
214 #ifndef ONLY_SERVICE_LAUNCHING
215
216 /**
217 * Launch a service initiated action for a software update with the
218 * specified arguments.
219 *
220 * @param exePath The path of the executable to run
221 * @param argc The total number of arguments in argv
222 * @param argv An array of null terminated strings to pass to the exePath,
223 * argv[0] must be the path to the updater.exe
224 * @return ERROR_SUCCESS if successful
225 */
226 DWORD
LaunchServiceSoftwareUpdateCommand(int argc,LPCWSTR * argv)227 LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv)
228 {
229 // The service command is the same as the updater.exe command line except
230 // it has 2 extra args: 1) The Path to udpater.exe, and 2) the command
231 // being executed which is "software-update"
232 LPCWSTR *updaterServiceArgv = new LPCWSTR[argc + 2];
233 updaterServiceArgv[0] = L"MozillaMaintenance";
234 updaterServiceArgv[1] = L"software-update";
235
236 for (int i = 0; i < argc; ++i) {
237 updaterServiceArgv[i + 2] = argv[i];
238 }
239
240 // Execute the service command by starting the service with
241 // the passed in arguments.
242 DWORD ret = StartServiceCommand(argc + 2, updaterServiceArgv);
243 delete[] updaterServiceArgv;
244 return ret;
245 }
246
247 /**
248 * Joins a base directory path with a filename.
249 *
250 * @param base The base directory path of size MAX_PATH + 1
251 * @param extra The filename to append
252 * @return TRUE if the file name was successful appended to base
253 */
254 BOOL
PathAppendSafe(LPWSTR base,LPCWSTR extra)255 PathAppendSafe(LPWSTR base, LPCWSTR extra)
256 {
257 if (wcslen(base) + wcslen(extra) >= MAX_PATH) {
258 return FALSE;
259 }
260
261 return PathAppendW(base, extra);
262 }
263
264 /**
265 * Sets update.status to a specific failure code
266 *
267 * @param updateDirPath The path of the update directory
268 * @return TRUE if successful
269 */
270 BOOL
WriteStatusFailure(LPCWSTR updateDirPath,int errorCode)271 WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
272 {
273 // The temp file is not removed on failure since there is client code that
274 // will remove it.
275 WCHAR tmpUpdateStatusFilePath[MAX_PATH + 1] = { L'\0' };
276 if (GetTempFileNameW(updateDirPath, L"svc", 0, tmpUpdateStatusFilePath) == 0) {
277 return FALSE;
278 }
279
280 HANDLE tmpStatusFile = CreateFileW(tmpUpdateStatusFilePath, GENERIC_WRITE, 0,
281 nullptr, CREATE_ALWAYS, 0, nullptr);
282 if (tmpStatusFile == INVALID_HANDLE_VALUE) {
283 return FALSE;
284 }
285
286 char failure[32];
287 sprintf(failure, "failed: %d", errorCode);
288 DWORD toWrite = strlen(failure);
289 DWORD wrote;
290 BOOL ok = WriteFile(tmpStatusFile, failure,
291 toWrite, &wrote, nullptr);
292 CloseHandle(tmpStatusFile);
293
294 if (!ok || wrote != toWrite) {
295 return FALSE;
296 }
297
298 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
299 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
300 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) {
301 return FALSE;
302 }
303
304 if (MoveFileExW(tmpUpdateStatusFilePath, updateStatusFilePath,
305 MOVEFILE_REPLACE_EXISTING) == 0) {
306 return FALSE;
307 }
308
309 return TRUE;
310 }
311
312 #endif
313
314 /**
315 * Waits for a service to enter a stopped state.
316 * This function does not stop the service, it just blocks until the service
317 * is stopped.
318 *
319 * @param serviceName The service to wait for.
320 * @param maxWaitSeconds The maximum number of seconds to wait
321 * @return state of the service after a timeout or when stopped.
322 * A value of 255 is returned for an error. Typical values are:
323 * SERVICE_STOPPED 0x00000001
324 * SERVICE_START_PENDING 0x00000002
325 * SERVICE_STOP_PENDING 0x00000003
326 * SERVICE_RUNNING 0x00000004
327 * SERVICE_CONTINUE_PENDING 0x00000005
328 * SERVICE_PAUSE_PENDING 0x00000006
329 * SERVICE_PAUSED 0x00000007
330 * last status not set 0x000000CF
331 * Could no query status 0x000000DF
332 * Could not open service, access denied 0x000000EB
333 * Could not open service, invalid handle 0x000000EC
334 * Could not open service, invalid name 0x000000ED
335 * Could not open service, does not exist 0x000000EE
336 * Could not open service, other error 0x000000EF
337 * Could not open SCM, access denied 0x000000FD
338 * Could not open SCM, database does not exist 0x000000FE;
339 * Could not open SCM, other error 0x000000FF;
340 * Note: The strange choice of error codes above SERVICE_PAUSED are chosen
341 * in case Windows comes out with other service stats higher than 7, they
342 * would likely call it 8 and above. JS code that uses this in TestAUSHelper
343 * only handles values up to 255 so that's why we don't use GetLastError
344 * directly.
345 */
346 DWORD
WaitForServiceStop(LPCWSTR serviceName,DWORD maxWaitSeconds)347 WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
348 {
349 // 0x000000CF is defined above to be not set
350 DWORD lastServiceState = 0x000000CF;
351
352 // Get a handle to the SCM database.
353 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
354 SC_MANAGER_CONNECT |
355 SC_MANAGER_ENUMERATE_SERVICE);
356 if (!serviceManager) {
357 DWORD lastError = GetLastError();
358 switch(lastError) {
359 case ERROR_ACCESS_DENIED:
360 return 0x000000FD;
361 case ERROR_DATABASE_DOES_NOT_EXIST:
362 return 0x000000FE;
363 default:
364 return 0x000000FF;
365 }
366 }
367
368 // Get a handle to the service.
369 SC_HANDLE service = OpenServiceW(serviceManager,
370 serviceName,
371 SERVICE_QUERY_STATUS);
372 if (!service) {
373 DWORD lastError = GetLastError();
374 CloseServiceHandle(serviceManager);
375 switch(lastError) {
376 case ERROR_ACCESS_DENIED:
377 return 0x000000EB;
378 case ERROR_INVALID_HANDLE:
379 return 0x000000EC;
380 case ERROR_INVALID_NAME:
381 return 0x000000ED;
382 case ERROR_SERVICE_DOES_NOT_EXIST:
383 return 0x000000EE;
384 default:
385 return 0x000000EF;
386 }
387 }
388
389 DWORD currentWaitMS = 0;
390 SERVICE_STATUS_PROCESS ssp;
391 ssp.dwCurrentState = lastServiceState;
392 while (currentWaitMS < maxWaitSeconds * 1000) {
393 DWORD bytesNeeded;
394 if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
395 sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded)) {
396 DWORD lastError = GetLastError();
397 switch (lastError) {
398 case ERROR_INVALID_HANDLE:
399 ssp.dwCurrentState = 0x000000D9;
400 break;
401 case ERROR_ACCESS_DENIED:
402 ssp.dwCurrentState = 0x000000DA;
403 break;
404 case ERROR_INSUFFICIENT_BUFFER:
405 ssp.dwCurrentState = 0x000000DB;
406 break;
407 case ERROR_INVALID_PARAMETER:
408 ssp.dwCurrentState = 0x000000DC;
409 break;
410 case ERROR_INVALID_LEVEL:
411 ssp.dwCurrentState = 0x000000DD;
412 break;
413 case ERROR_SHUTDOWN_IN_PROGRESS:
414 ssp.dwCurrentState = 0x000000DE;
415 break;
416 // These 3 errors can occur when the service is not yet stopped but
417 // it is stopping.
418 case ERROR_INVALID_SERVICE_CONTROL:
419 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
420 case ERROR_SERVICE_NOT_ACTIVE:
421 currentWaitMS += 50;
422 Sleep(50);
423 continue;
424 default:
425 ssp.dwCurrentState = 0x000000DF;
426 }
427
428 // We couldn't query the status so just break out
429 break;
430 }
431
432 // The service is already in use.
433 if (ssp.dwCurrentState == SERVICE_STOPPED) {
434 break;
435 }
436 currentWaitMS += 50;
437 Sleep(50);
438 }
439
440 lastServiceState = ssp.dwCurrentState;
441 CloseServiceHandle(service);
442 CloseServiceHandle(serviceManager);
443 return lastServiceState;
444 }
445
446 #ifndef ONLY_SERVICE_LAUNCHING
447
448 /**
449 * Determines if there is at least one process running for the specified
450 * application. A match will be found across any session for any user.
451 *
452 * @param process The process to check for existance
453 * @return ERROR_NOT_FOUND if the process was not found
454 * ERROR_SUCCESS if the process was found and there were no errors
455 * Other Win32 system error code for other errors
456 **/
457 DWORD
IsProcessRunning(LPCWSTR filename)458 IsProcessRunning(LPCWSTR filename)
459 {
460 // Take a snapshot of all processes in the system.
461 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
462 if (INVALID_HANDLE_VALUE == snapshot) {
463 return GetLastError();
464 }
465
466 PROCESSENTRY32W processEntry;
467 processEntry.dwSize = sizeof(PROCESSENTRY32W);
468 if (!Process32FirstW(snapshot, &processEntry)) {
469 DWORD lastError = GetLastError();
470 CloseHandle(snapshot);
471 return lastError;
472 }
473
474 do {
475 if (wcsicmp(filename, processEntry.szExeFile) == 0) {
476 CloseHandle(snapshot);
477 return ERROR_SUCCESS;
478 }
479 } while (Process32NextW(snapshot, &processEntry));
480 CloseHandle(snapshot);
481 return ERROR_NOT_FOUND;
482 }
483
484 /**
485 * Waits for the specified applicaiton to exit.
486 *
487 * @param filename The application to wait for.
488 * @param maxSeconds The maximum amount of seconds to wait for all
489 * instances of the application to exit.
490 * @return ERROR_SUCCESS if no instances of the application exist
491 * WAIT_TIMEOUT if the process is still running after maxSeconds.
492 * Any other Win32 system error code.
493 */
494 DWORD
WaitForProcessExit(LPCWSTR filename,DWORD maxSeconds)495 WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
496 {
497 DWORD applicationRunningError = WAIT_TIMEOUT;
498 for(DWORD i = 0; i < maxSeconds; i++) {
499 DWORD applicationRunningError = IsProcessRunning(filename);
500 if (ERROR_NOT_FOUND == applicationRunningError) {
501 return ERROR_SUCCESS;
502 }
503 Sleep(1000);
504 }
505
506 if (ERROR_SUCCESS == applicationRunningError) {
507 return WAIT_TIMEOUT;
508 }
509
510 return applicationRunningError;
511 }
512
513 /**
514 * Determines if the fallback key exists or not
515 *
516 * @return TRUE if the fallback key exists and there was no error checking
517 */
518 BOOL
DoesFallbackKeyExist()519 DoesFallbackKeyExist()
520 {
521 HKEY testOnlyFallbackKey;
522 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
523 TEST_ONLY_FALLBACK_KEY_PATH, 0,
524 KEY_READ | KEY_WOW64_64KEY,
525 &testOnlyFallbackKey) != ERROR_SUCCESS) {
526 return FALSE;
527 }
528
529 RegCloseKey(testOnlyFallbackKey);
530 return TRUE;
531 }
532
533 #endif
534
535 /**
536 * Determines if the file system for the specified file handle is local
537 * @param file path to check the filesystem type for, must be at most MAX_PATH
538 * @param isLocal out parameter which will hold TRUE if the drive is local
539 * @return TRUE if the call succeeded
540 */
541 BOOL
IsLocalFile(LPCWSTR file,BOOL & isLocal)542 IsLocalFile(LPCWSTR file, BOOL &isLocal)
543 {
544 WCHAR rootPath[MAX_PATH + 1] = { L'\0' };
545 if (wcslen(file) > MAX_PATH) {
546 return FALSE;
547 }
548
549 wcsncpy(rootPath, file, MAX_PATH);
550 PathStripToRootW(rootPath);
551 isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED;
552 return TRUE;
553 }
554
555
556 /**
557 * Determines the DWORD value of a registry key value
558 *
559 * @param key The base key to where the value name exists
560 * @param valueName The name of the value
561 * @param retValue Out parameter which will hold the value
562 * @return TRUE on success
563 */
564 static BOOL
GetDWORDValue(HKEY key,LPCWSTR valueName,DWORD & retValue)565 GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue)
566 {
567 DWORD regDWORDValueSize = sizeof(DWORD);
568 LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr,
569 reinterpret_cast<LPBYTE>(&retValue),
570 ®DWORDValueSize);
571 return ERROR_SUCCESS == retCode;
572 }
573
574 /**
575 * Determines if the the system's elevation type allows
576 * unprmopted elevation.
577 *
578 * @param isUnpromptedElevation Out parameter which specifies if unprompted
579 * elevation is allowed.
580 * @return TRUE if the user can actually elevate and the value was obtained
581 * successfully.
582 */
583 BOOL
IsUnpromptedElevation(BOOL & isUnpromptedElevation)584 IsUnpromptedElevation(BOOL &isUnpromptedElevation)
585 {
586 if (!UACHelper::CanUserElevate()) {
587 return FALSE;
588 }
589
590 LPCWSTR UACBaseRegKey =
591 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
592 HKEY baseKey;
593 LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
594 UACBaseRegKey, 0,
595 KEY_READ, &baseKey);
596 if (retCode != ERROR_SUCCESS) {
597 return FALSE;
598 }
599
600 DWORD consent, secureDesktop;
601 BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin",
602 consent);
603 success = success &&
604 GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
605 isUnpromptedElevation = !consent && !secureDesktop;
606
607 RegCloseKey(baseKey);
608 return success;
609 }
610