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/. */
8 // Needed for CreateToolhelp32Snapshot
10 #ifndef ONLY_SERVICE_LAUNCHING
14 #include "updatehelper.h"
15 #include "uachelper.h"
20 // Needed for PathAppendW
23 BOOL
PathAppendSafe(LPWSTR base
, LPCWSTR extra
);
26 * Obtains the path of a file in the same directory as the specified file.
28 * @param destinationBuffer A buffer of size MAX_PATH + 1 to store the result.
29 * @param siblingFIlePath The path of another file in the same directory
30 * @param newFileName The filename of another file in the same directory
31 * @return TRUE if successful
34 PathGetSiblingFilePath(LPWSTR destinationBuffer
,
35 LPCWSTR siblingFilePath
,
38 if (wcslen(siblingFilePath
) >= MAX_PATH
)
43 wcsncpy(destinationBuffer
, siblingFilePath
, MAX_PATH
);
44 if (!PathRemoveFileSpecW(destinationBuffer
))
49 if (wcslen(destinationBuffer
) + wcslen(newFileName
) >= MAX_PATH
)
54 return PathAppendSafe(destinationBuffer
, newFileName
);
58 * Launch the post update application as the specified user (helper.exe).
59 * It takes in the path of the callback application to calculate the path
60 * of helper.exe. For service updates this is called from both the system
61 * account and the current user account.
63 * @param installationDir The path to the callback application binary.
64 * @param updateInfoDir The directory where update info is stored.
65 * @param forceSync If true even if the ini file specifies async, the
66 * process will wait for termination of PostUpdate.
67 * @param userToken The user token to run as, if nullptr the current
69 * @return TRUE if there was no error starting the process.
72 LaunchWinPostProcess(const WCHAR
*installationDir
,
73 const WCHAR
*updateInfoDir
,
77 WCHAR workingDirectory
[MAX_PATH
+ 1] = { L
'\0' };
78 wcsncpy(workingDirectory
, installationDir
, MAX_PATH
);
80 // Launch helper.exe to perform post processing (e.g. registry and log file
81 // modifications) for the update.
82 WCHAR inifile
[MAX_PATH
+ 1] = { L
'\0' };
83 wcsncpy(inifile
, installationDir
, MAX_PATH
);
84 if (!PathAppendSafe(inifile
, L
"updater.ini"))
89 WCHAR exefile
[MAX_PATH
+ 1];
90 WCHAR exearg
[MAX_PATH
+ 1];
93 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeRelPath", nullptr,
94 exefile
, MAX_PATH
+ 1, inifile
))
99 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeArg", nullptr, exearg
,
100 MAX_PATH
+ 1, inifile
))
105 if (!GetPrivateProfileStringW(L
"PostUpdateWin", L
"ExeAsync", L
"TRUE",
107 sizeof(exeasync
)/sizeof(exeasync
[0]),
113 WCHAR exefullpath
[MAX_PATH
+ 1] = { L
'\0' };
114 wcsncpy(exefullpath
, installationDir
, MAX_PATH
);
115 if (!PathAppendSafe(exefullpath
, exefile
))
120 WCHAR dlogFile
[MAX_PATH
+ 1];
121 if (!PathGetSiblingFilePath(dlogFile
, exefullpath
, L
"uninstall.update"))
126 WCHAR slogFile
[MAX_PATH
+ 1] = { L
'\0' };
127 wcsncpy(slogFile
, updateInfoDir
, MAX_PATH
);
128 if (!PathAppendSafe(slogFile
, L
"update.log"))
133 WCHAR dummyArg
[14] = { L
'\0' };
134 wcsncpy(dummyArg
, L
"argv0ignored ", sizeof(dummyArg
) / sizeof(dummyArg
[0]) - 1);
136 size_t len
= wcslen(exearg
) + wcslen(dummyArg
);
137 WCHAR
*cmdline
= (WCHAR
*) malloc((len
+ 1) * sizeof(WCHAR
));
143 wcsncpy(cmdline
, dummyArg
, len
);
144 wcscat(cmdline
, exearg
);
147 !_wcsnicmp(exeasync
, L
"false", 6) ||
148 !_wcsnicmp(exeasync
, L
"0", 2))
153 // We want to launch the post update helper app to update the Windows
154 // registry even if there is a failure with removing the uninstall.update
155 // file or copying the update.log file.
156 CopyFileW(slogFile
, dlogFile
, false);
158 STARTUPINFOW si
= {sizeof(si
), 0};
160 PROCESS_INFORMATION pi
= {0};
165 ok
= CreateProcessAsUserW(userToken
,
168 nullptr, // no special security attributes
169 nullptr, // no special thread attributes
170 false, // don't inherit filehandles
171 0, // No special process creation flags
172 nullptr, // inherit my environment
179 ok
= CreateProcessW(exefullpath
,
181 nullptr, // no special security attributes
182 nullptr, // no special thread attributes
183 false, // don't inherit filehandles
184 0, // No special process creation flags
185 nullptr, // inherit my environment
194 WaitForSingleObject(pi
.hProcess
, INFINITE
);
195 CloseHandle(pi
.hProcess
);
196 CloseHandle(pi
.hThread
);
202 * Starts the upgrade process for update of the service if it is
205 * @param installDir the installation directory where
206 * maintenanceservice_installer.exe is located.
207 * @return TRUE if successful
210 StartServiceUpdate(LPCWSTR installDir
)
212 // Get a handle to the local computer SCM database
213 SC_HANDLE manager
= OpenSCManager(nullptr, nullptr,
214 SC_MANAGER_ALL_ACCESS
);
221 SC_HANDLE svc
= OpenServiceW(manager
, SVC_NAME
,
225 CloseServiceHandle(manager
);
229 // If we reach here, then the service is installed, so
230 // proceed with upgrading it.
232 CloseServiceHandle(manager
);
234 // The service exists and we opened it, get the config bytes needed
236 if (!QueryServiceConfigW(svc
, nullptr, 0, &bytesNeeded
) &&
237 GetLastError() != ERROR_INSUFFICIENT_BUFFER
)
239 CloseServiceHandle(svc
);
243 // Get the service config information, in particular we want the binary
244 // path of the service.
245 std::unique_ptr
<char[]> serviceConfigBuffer
= std::make_unique
<char[]>(bytesNeeded
);
246 if (!QueryServiceConfigW(svc
,
247 reinterpret_cast<QUERY_SERVICE_CONFIGW
*>(serviceConfigBuffer
.get()),
248 bytesNeeded
, &bytesNeeded
))
250 CloseServiceHandle(svc
);
254 CloseServiceHandle(svc
);
256 QUERY_SERVICE_CONFIGW
&serviceConfig
=
257 *reinterpret_cast<QUERY_SERVICE_CONFIGW
*>(serviceConfigBuffer
.get());
259 PathUnquoteSpacesW(serviceConfig
.lpBinaryPathName
);
261 // Obtain the temp path of the maintenance service binary
262 WCHAR tmpService
[MAX_PATH
+ 1] = { L
'\0' };
263 if (!PathGetSiblingFilePath(tmpService
, serviceConfig
.lpBinaryPathName
,
264 L
"maintenanceservice_tmp.exe"))
269 // Get the new maintenance service path from the install dir
270 WCHAR newMaintServicePath
[MAX_PATH
+ 1] = { L
'\0' };
271 wcsncpy(newMaintServicePath
, installDir
, MAX_PATH
);
272 PathAppendSafe(newMaintServicePath
,
273 L
"maintenanceservice.exe");
275 // Copy the temp file in alongside the maintenance service.
276 // This is a requirement for maintenance service upgrades.
277 if (!CopyFileW(newMaintServicePath
, tmpService
, FALSE
))
282 // Start the upgrade comparison process
283 STARTUPINFOW si
= {0};
284 si
.cb
= sizeof(STARTUPINFOW
);
285 // No particular desktop because no UI
287 PROCESS_INFORMATION pi
= {0};
288 WCHAR cmdLine
[64] = { '\0' };
289 wcsncpy(cmdLine
, L
"dummyparam.exe upgrade",
290 sizeof(cmdLine
) / sizeof(cmdLine
[0]) - 1);
291 BOOL svcUpdateProcessStarted
= CreateProcessW(tmpService
,
293 nullptr, nullptr, FALSE
,
295 nullptr, installDir
, &si
, &pi
);
296 if (svcUpdateProcessStarted
)
298 CloseHandle(pi
.hProcess
);
299 CloseHandle(pi
.hThread
);
301 return svcUpdateProcessStarted
;
307 * Executes a maintenance service command
309 * @param argc The total number of arguments in argv
310 * @param argv An array of null terminated strings to pass to the service,
311 * @return ERROR_SUCCESS if the service command was started.
312 * Less than 16000, a windows system error code from StartServiceW
313 * More than 20000, 20000 + the last state of the service constant if
314 * the last state is something other than stopped.
315 * 17001 if the SCM could not be opened
316 * 17002 if the service could not be opened
319 StartServiceCommand(int argc
, LPCWSTR
* argv
)
321 DWORD lastState
= WaitForServiceStop(SVC_NAME
, 5);
322 if (lastState
!= SERVICE_STOPPED
)
324 return 20000 + lastState
;
327 // Get a handle to the SCM database.
328 SC_HANDLE serviceManager
= OpenSCManager(nullptr, nullptr,
330 SC_MANAGER_ENUMERATE_SERVICE
);
336 // Get a handle to the service.
337 SC_HANDLE service
= OpenServiceW(serviceManager
,
342 CloseServiceHandle(serviceManager
);
346 // Wait at most 5 seconds trying to start the service in case of errors
347 // like ERROR_SERVICE_DATABASE_LOCKED or ERROR_SERVICE_REQUEST_TIMEOUT.
348 const DWORD maxWaitMS
= 5000;
349 DWORD currentWaitMS
= 0;
350 DWORD lastError
= ERROR_SUCCESS
;
351 while (currentWaitMS
< maxWaitMS
)
353 BOOL result
= StartServiceW(service
, argc
, argv
);
356 lastError
= ERROR_SUCCESS
;
361 lastError
= GetLastError();
364 currentWaitMS
+= 100;
366 CloseServiceHandle(service
);
367 CloseServiceHandle(serviceManager
);
371 #ifndef ONLY_SERVICE_LAUNCHING
374 * Launch a service initiated action for a software update with the
375 * specified arguments.
377 * @param exePath The path of the executable to run
378 * @param argc The total number of arguments in argv
379 * @param argv An array of null terminated strings to pass to the exePath,
380 * argv[0] must be the path to the updater.exe
381 * @return ERROR_SUCCESS if successful
384 LaunchServiceSoftwareUpdateCommand(int argc
, LPCWSTR
* argv
)
386 // The service command is the same as the updater.exe command line except
387 // it has 2 extra args: 1) The Path to updater.exe, and 2) the command
388 // being executed which is "software-update"
389 LPCWSTR
*updaterServiceArgv
= new LPCWSTR
[argc
+ 2];
390 updaterServiceArgv
[0] = L
"MozillaMaintenance";
391 updaterServiceArgv
[1] = L
"software-update";
393 for (int i
= 0; i
< argc
; ++i
)
395 updaterServiceArgv
[i
+ 2] = argv
[i
];
398 // Execute the service command by starting the service with
399 // the passed in arguments.
400 DWORD ret
= StartServiceCommand(argc
+ 2, updaterServiceArgv
);
401 delete[] updaterServiceArgv
;
406 * Joins a base directory path with a filename.
408 * @param base The base directory path of size MAX_PATH + 1
409 * @param extra The filename to append
410 * @return TRUE if the file name was successful appended to base
413 PathAppendSafe(LPWSTR base
, LPCWSTR extra
)
415 if (wcslen(base
) + wcslen(extra
) >= MAX_PATH
)
420 return PathAppendW(base
, extra
);
424 * Sets update.status to pending so that the next startup will not use
425 * the service and instead will attempt an update the with a UAC prompt.
427 * @param updateDirPath The path of the update directory
428 * @return TRUE if successful
431 WriteStatusPending(LPCWSTR updateDirPath
)
433 WCHAR updateStatusFilePath
[MAX_PATH
+ 1] = { L
'\0' };
434 wcsncpy(updateStatusFilePath
, updateDirPath
, MAX_PATH
);
435 if (!PathAppendSafe(updateStatusFilePath
, L
"update.status"))
440 const char pending
[] = "pending";
441 HANDLE statusFile
= CreateFileW(updateStatusFilePath
, GENERIC_WRITE
, 0,
442 nullptr, CREATE_ALWAYS
, 0, nullptr);
443 if (statusFile
== INVALID_HANDLE_VALUE
)
449 BOOL ok
= WriteFile(statusFile
, pending
,
450 sizeof(pending
) - 1, &wrote
, nullptr);
451 CloseHandle(statusFile
);
452 return ok
&& (wrote
== sizeof(pending
) - 1);
456 * Sets update.status to a specific failure code
458 * @param updateDirPath The path of the update directory
459 * @return TRUE if successful
462 WriteStatusFailure(LPCWSTR updateDirPath
, int errorCode
)
464 WCHAR updateStatusFilePath
[MAX_PATH
+ 1] = { L
'\0' };
465 wcsncpy(updateStatusFilePath
, updateDirPath
, MAX_PATH
);
466 if (!PathAppendSafe(updateStatusFilePath
, L
"update.status"))
471 HANDLE statusFile
= CreateFileW(updateStatusFilePath
, GENERIC_WRITE
, 0,
472 nullptr, CREATE_ALWAYS
, 0, nullptr);
473 if (statusFile
== INVALID_HANDLE_VALUE
)
478 sprintf(failure
, "failed: %d", errorCode
);
480 DWORD toWrite
= strlen(failure
);
482 BOOL ok
= WriteFile(statusFile
, failure
,
483 toWrite
, &wrote
, nullptr);
484 CloseHandle(statusFile
);
485 return ok
&& wrote
== toWrite
;
491 * Waits for a service to enter a stopped state.
492 * This function does not stop the service, it just blocks until the service
495 * @param serviceName The service to wait for.
496 * @param maxWaitSeconds The maximum number of seconds to wait
497 * @return state of the service after a timeout or when stopped.
498 * A value of 255 is returned for an error. Typical values are:
499 * SERVICE_STOPPED 0x00000001
500 * SERVICE_START_PENDING 0x00000002
501 * SERVICE_STOP_PENDING 0x00000003
502 * SERVICE_RUNNING 0x00000004
503 * SERVICE_CONTINUE_PENDING 0x00000005
504 * SERVICE_PAUSE_PENDING 0x00000006
505 * SERVICE_PAUSED 0x00000007
506 * last status not set 0x000000CF
507 * Could no query status 0x000000DF
508 * Could not open service, access denied 0x000000EB
509 * Could not open service, invalid handle 0x000000EC
510 * Could not open service, invalid name 0x000000ED
511 * Could not open service, does not exist 0x000000EE
512 * Could not open service, other error 0x000000EF
513 * Could not open SCM, access denied 0x000000FD
514 * Could not open SCM, database does not exist 0x000000FE;
515 * Could not open SCM, other error 0x000000FF;
516 * Note: The strange choice of error codes above SERVICE_PAUSED are chosen
517 * in case Windows comes out with other service stats higher than 7, they
518 * would likely call it 8 and above. JS code that uses this in TestAUSHelper
519 * only handles values up to 255 so that's why we don't use GetLastError
523 WaitForServiceStop(LPCWSTR serviceName
, DWORD maxWaitSeconds
)
525 // 0x000000CF is defined above to be not set
526 DWORD lastServiceState
= 0x000000CF;
528 // Get a handle to the SCM database.
529 SC_HANDLE serviceManager
= OpenSCManager(nullptr, nullptr,
531 SC_MANAGER_ENUMERATE_SERVICE
);
534 DWORD lastError
= GetLastError();
537 case ERROR_ACCESS_DENIED
:
539 case ERROR_DATABASE_DOES_NOT_EXIST
:
546 // Get a handle to the service.
547 SC_HANDLE service
= OpenServiceW(serviceManager
,
549 SERVICE_QUERY_STATUS
);
552 DWORD lastError
= GetLastError();
553 CloseServiceHandle(serviceManager
);
556 case ERROR_ACCESS_DENIED
:
558 case ERROR_INVALID_HANDLE
:
560 case ERROR_INVALID_NAME
:
562 case ERROR_SERVICE_DOES_NOT_EXIST
:
569 DWORD currentWaitMS
= 0;
570 SERVICE_STATUS_PROCESS ssp
;
571 ssp
.dwCurrentState
= lastServiceState
;
572 while (currentWaitMS
< maxWaitSeconds
* 1000)
575 if (!QueryServiceStatusEx(service
, SC_STATUS_PROCESS_INFO
, (LPBYTE
)&ssp
,
576 sizeof(SERVICE_STATUS_PROCESS
), &bytesNeeded
))
578 DWORD lastError
= GetLastError();
581 case ERROR_INVALID_HANDLE
:
582 ssp
.dwCurrentState
= 0x000000D9;
584 case ERROR_ACCESS_DENIED
:
585 ssp
.dwCurrentState
= 0x000000DA;
587 case ERROR_INSUFFICIENT_BUFFER
:
588 ssp
.dwCurrentState
= 0x000000DB;
590 case ERROR_INVALID_PARAMETER
:
591 ssp
.dwCurrentState
= 0x000000DC;
593 case ERROR_INVALID_LEVEL
:
594 ssp
.dwCurrentState
= 0x000000DD;
596 case ERROR_SHUTDOWN_IN_PROGRESS
:
597 ssp
.dwCurrentState
= 0x000000DE;
599 // These 3 errors can occur when the service is not yet stopped but
601 case ERROR_INVALID_SERVICE_CONTROL
:
602 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL
:
603 case ERROR_SERVICE_NOT_ACTIVE
:
608 ssp
.dwCurrentState
= 0x000000DF;
611 // We couldn't query the status so just break out
615 // The service is already in use.
616 if (ssp
.dwCurrentState
== SERVICE_STOPPED
)
624 lastServiceState
= ssp
.dwCurrentState
;
625 CloseServiceHandle(service
);
626 CloseServiceHandle(serviceManager
);
627 return lastServiceState
;
630 #ifndef ONLY_SERVICE_LAUNCHING
633 * Determines if there is at least one process running for the specified
634 * application. A match will be found across any session for any user.
636 * @param process The process to check for existence
637 * @return ERROR_NOT_FOUND if the process was not found
638 * ERROR_SUCCESS if the process was found and there were no errors
639 * Other Win32 system error code for other errors
642 IsProcessRunning(LPCWSTR filename
)
644 // Take a snapshot of all processes in the system.
645 HANDLE snapshot
= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS
, 0);
646 if (INVALID_HANDLE_VALUE
== snapshot
)
648 return GetLastError();
651 PROCESSENTRY32W processEntry
;
652 processEntry
.dwSize
= sizeof(PROCESSENTRY32W
);
653 if (!Process32FirstW(snapshot
, &processEntry
))
655 DWORD lastError
= GetLastError();
656 CloseHandle(snapshot
);
662 if (wcsicmp(filename
, processEntry
.szExeFile
) == 0)
664 CloseHandle(snapshot
);
665 return ERROR_SUCCESS
;
668 while (Process32NextW(snapshot
, &processEntry
));
669 CloseHandle(snapshot
);
670 return ERROR_NOT_FOUND
;
674 * Waits for the specified application to exit.
676 * @param filename The application to wait for.
677 * @param maxSeconds The maximum amount of seconds to wait for all
678 * instances of the application to exit.
679 * @return ERROR_SUCCESS if no instances of the application exist
680 * WAIT_TIMEOUT if the process is still running after maxSeconds.
681 * Any other Win32 system error code.
684 WaitForProcessExit(LPCWSTR filename
, DWORD maxSeconds
)
686 DWORD applicationRunningError
= WAIT_TIMEOUT
;
687 for (DWORD i
= 0; i
< maxSeconds
; i
++)
689 applicationRunningError
= IsProcessRunning(filename
);
690 if (ERROR_NOT_FOUND
== applicationRunningError
)
692 return ERROR_SUCCESS
;
697 if (ERROR_SUCCESS
== applicationRunningError
)
702 return applicationRunningError
;
706 * Determines if the fallback key exists or not
708 * @return TRUE if the fallback key exists and there was no error checking
711 DoesFallbackKeyExist()
713 HKEY testOnlyFallbackKey
;
714 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
715 TEST_ONLY_FALLBACK_KEY_PATH
, 0,
716 KEY_READ
| KEY_WOW64_64KEY
,
717 &testOnlyFallbackKey
) != ERROR_SUCCESS
)
722 RegCloseKey(testOnlyFallbackKey
);
729 * Determines if the file system for the specified file handle is local
730 * @param file path to check the filesystem type for, must be at most MAX_PATH
731 * @param isLocal out parameter which will hold TRUE if the drive is local
732 * @return TRUE if the call succeeded
735 IsLocalFile(LPCWSTR file
, BOOL
&isLocal
)
737 WCHAR rootPath
[MAX_PATH
+ 1] = { L
'\0' };
738 if (wcslen(file
) > MAX_PATH
)
743 wcsncpy(rootPath
, file
, MAX_PATH
);
744 PathStripToRootW(rootPath
);
745 isLocal
= GetDriveTypeW(rootPath
) == DRIVE_FIXED
;
751 * Determines the DWORD value of a registry key value
753 * @param key The base key to where the value name exists
754 * @param valueName The name of the value
755 * @param retValue Out parameter which will hold the value
756 * @return TRUE on success
759 GetDWORDValue(HKEY key
, LPCWSTR valueName
, DWORD
&retValue
)
761 DWORD regDWORDValueSize
= sizeof(DWORD
);
762 LONG retCode
= RegQueryValueExW(key
, valueName
, 0, nullptr,
763 reinterpret_cast<LPBYTE
>(&retValue
),
765 return ERROR_SUCCESS
== retCode
;
769 * Determines if the system's elevation type allows
770 * unprompted elevation.
772 * @param isUnpromptedElevation Out parameter which specifies if unprompted
773 * elevation is allowed.
774 * @return TRUE if the user can actually elevate and the value was obtained
778 IsUnpromptedElevation(BOOL
&isUnpromptedElevation
)
780 if (!UACHelper::CanUserElevate())
785 LPCWSTR UACBaseRegKey
=
786 L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
788 LONG retCode
= RegOpenKeyExW(HKEY_LOCAL_MACHINE
,
791 if (retCode
!= ERROR_SUCCESS
)
797 DWORD secureDesktop
= 0;
798 BOOL success
= GetDWORDValue(baseKey
, L
"ConsentPromptBehaviorAdmin",
801 GetDWORDValue(baseKey
, L
"PromptOnSecureDesktop", secureDesktop
);
802 isUnpromptedElevation
= !consent
&& !secureDesktop
;
804 RegCloseKey(baseKey
);