|
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 <shlobj.h> |
|
6 #include <shlwapi.h> |
|
7 #include <wtsapi32.h> |
|
8 #include <userenv.h> |
|
9 #include <shellapi.h> |
|
10 |
|
11 #pragma comment(lib, "wtsapi32.lib") |
|
12 #pragma comment(lib, "userenv.lib") |
|
13 #pragma comment(lib, "shlwapi.lib") |
|
14 #pragma comment(lib, "ole32.lib") |
|
15 #pragma comment(lib, "rpcrt4.lib") |
|
16 |
|
17 #include "nsWindowsHelpers.h" |
|
18 |
|
19 #include "workmonitor.h" |
|
20 #include "serviceinstall.h" |
|
21 #include "servicebase.h" |
|
22 #include "registrycertificates.h" |
|
23 #include "uachelper.h" |
|
24 #include "updatehelper.h" |
|
25 #include "errors.h" |
|
26 |
|
27 // Wait 15 minutes for an update operation to run at most. |
|
28 // Updates usually take less than a minute so this seems like a |
|
29 // significantly large and safe amount of time to wait. |
|
30 static const int TIME_TO_WAIT_ON_UPDATER = 15 * 60 * 1000; |
|
31 char16_t* MakeCommandLine(int argc, char16_t **argv); |
|
32 BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode); |
|
33 BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, |
|
34 LPCWSTR newFileName); |
|
35 |
|
36 /* |
|
37 * Read the update.status file and sets isApplying to true if |
|
38 * the status is set to applying |
|
39 * |
|
40 * @param updateDirPath The directory where update.status is stored |
|
41 * @param isApplying Out parameter for specifying if the status |
|
42 * is set to applying or not. |
|
43 * @return TRUE if the information was filled. |
|
44 */ |
|
45 static BOOL |
|
46 IsStatusApplying(LPCWSTR updateDirPath, BOOL &isApplying) |
|
47 { |
|
48 isApplying = FALSE; |
|
49 WCHAR updateStatusFilePath[MAX_PATH + 1] = {L'\0'}; |
|
50 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH); |
|
51 if (!PathAppendSafe(updateStatusFilePath, L"update.status")) { |
|
52 LOG_WARN(("Could not append path for update.status file")); |
|
53 return FALSE; |
|
54 } |
|
55 |
|
56 nsAutoHandle statusFile(CreateFileW(updateStatusFilePath, GENERIC_READ, |
|
57 FILE_SHARE_READ | |
|
58 FILE_SHARE_WRITE | |
|
59 FILE_SHARE_DELETE, |
|
60 nullptr, OPEN_EXISTING, 0, nullptr)); |
|
61 |
|
62 if (INVALID_HANDLE_VALUE == statusFile) { |
|
63 LOG_WARN(("Could not open update.status file")); |
|
64 return FALSE; |
|
65 } |
|
66 |
|
67 char buf[32] = { 0 }; |
|
68 DWORD read; |
|
69 if (!ReadFile(statusFile, buf, sizeof(buf), &read, nullptr)) { |
|
70 LOG_WARN(("Could not read from update.status file")); |
|
71 return FALSE; |
|
72 } |
|
73 |
|
74 LOG(("updater.exe returned status: %s", buf)); |
|
75 |
|
76 const char kApplying[] = "applying"; |
|
77 isApplying = strncmp(buf, kApplying, |
|
78 sizeof(kApplying) - 1) == 0; |
|
79 return TRUE; |
|
80 } |
|
81 |
|
82 /** |
|
83 * Determines whether we're staging an update. |
|
84 * |
|
85 * @param argc The argc value normally sent to updater.exe |
|
86 * @param argv The argv value normally sent to updater.exe |
|
87 * @param boolean True if we're staging an update |
|
88 */ |
|
89 static bool |
|
90 IsUpdateBeingStaged(int argc, LPWSTR *argv) |
|
91 { |
|
92 // PID will be set to -1 if we're supposed to stage an update. |
|
93 return argc == 4 && !wcscmp(argv[3], L"-1"); |
|
94 } |
|
95 |
|
96 /** |
|
97 * Gets the installation directory from the arguments passed to updater.exe. |
|
98 * |
|
99 * @param argcTmp The argc value normally sent to updater.exe |
|
100 * @param argvTmp The argv value normally sent to updater.exe |
|
101 * @param aResultDir Buffer to hold the installation directory. |
|
102 */ |
|
103 static BOOL |
|
104 GetInstallationDir(int argcTmp, LPWSTR *argvTmp, WCHAR aResultDir[MAX_PATH + 1]) |
|
105 { |
|
106 if (argcTmp < 2) { |
|
107 return FALSE; |
|
108 } |
|
109 wcsncpy(aResultDir, argvTmp[2], MAX_PATH); |
|
110 WCHAR* backSlash = wcsrchr(aResultDir, L'\\'); |
|
111 // Make sure that the path does not include trailing backslashes |
|
112 if (backSlash && (backSlash[1] == L'\0')) { |
|
113 *backSlash = L'\0'; |
|
114 } |
|
115 bool backgroundUpdate = IsUpdateBeingStaged(argcTmp, argvTmp); |
|
116 bool replaceRequest = (argcTmp >= 4 && wcsstr(argvTmp[3], L"/replace")); |
|
117 if (backgroundUpdate || replaceRequest) { |
|
118 return PathRemoveFileSpecW(aResultDir); |
|
119 } |
|
120 return TRUE; |
|
121 } |
|
122 |
|
123 /** |
|
124 * Runs an update process as the service using the SYSTEM account. |
|
125 * |
|
126 * @param argc The number of arguments in argv |
|
127 * @param argv The arguments normally passed to updater.exe |
|
128 * argv[0] must be the path to updater.exe |
|
129 * @param processStarted Set to TRUE if the process was started. |
|
130 * @return TRUE if the update process was run had a return code of 0. |
|
131 */ |
|
132 BOOL |
|
133 StartUpdateProcess(int argc, |
|
134 LPWSTR *argv, |
|
135 LPCWSTR installDir, |
|
136 BOOL &processStarted) |
|
137 { |
|
138 LOG(("Starting update process as the service in session 0.")); |
|
139 STARTUPINFO si = {0}; |
|
140 si.cb = sizeof(STARTUPINFO); |
|
141 si.lpDesktop = L"winsta0\\Default"; |
|
142 PROCESS_INFORMATION pi = {0}; |
|
143 |
|
144 // The updater command line is of the form: |
|
145 // updater.exe update-dir apply [wait-pid [callback-dir callback-path args]] |
|
146 LPWSTR cmdLine = MakeCommandLine(argc, argv); |
|
147 |
|
148 // If we're about to start the update process from session 0, |
|
149 // then we should not show a GUI. This only really needs to be done |
|
150 // on Vista and higher, but it's better to keep everything consistent |
|
151 // across all OS if it's of no harm. |
|
152 if (argc >= 2 ) { |
|
153 // Setting the desktop to blank will ensure no GUI is displayed |
|
154 si.lpDesktop = L""; |
|
155 si.dwFlags |= STARTF_USESHOWWINDOW; |
|
156 si.wShowWindow = SW_HIDE; |
|
157 } |
|
158 |
|
159 // We move the updater.ini file out of the way because we will handle |
|
160 // executing PostUpdate through the service. We handle PostUpdate from |
|
161 // the service because there are some per user things that happen that |
|
162 // can't run in session 0 which we run updater.exe in. |
|
163 // Once we are done running updater.exe we rename updater.ini back so |
|
164 // that if there were any errors the next updater.exe will run correctly. |
|
165 WCHAR updaterINI[MAX_PATH + 1]; |
|
166 WCHAR updaterINITemp[MAX_PATH + 1]; |
|
167 BOOL selfHandlePostUpdate = FALSE; |
|
168 // We use the updater.ini from the same directory as the updater.exe |
|
169 // because of background updates. |
|
170 if (PathGetSiblingFilePath(updaterINI, argv[0], L"updater.ini") && |
|
171 PathGetSiblingFilePath(updaterINITemp, argv[0], L"updater.tmp")) { |
|
172 selfHandlePostUpdate = MoveFileExW(updaterINI, updaterINITemp, |
|
173 MOVEFILE_REPLACE_EXISTING); |
|
174 } |
|
175 |
|
176 // Add an env var for MOZ_USING_SERVICE so the updater.exe can |
|
177 // do anything special that it needs to do for service updates. |
|
178 // Search in updater.cpp for more info on MOZ_USING_SERVICE. |
|
179 putenv(const_cast<char*>("MOZ_USING_SERVICE=1")); |
|
180 LOG(("Starting service with cmdline: %ls", cmdLine)); |
|
181 processStarted = CreateProcessW(argv[0], cmdLine, |
|
182 nullptr, nullptr, FALSE, |
|
183 CREATE_DEFAULT_ERROR_MODE, |
|
184 nullptr, |
|
185 nullptr, &si, &pi); |
|
186 // Empty value on putenv is how you remove an env variable in Windows |
|
187 putenv(const_cast<char*>("MOZ_USING_SERVICE=")); |
|
188 |
|
189 BOOL updateWasSuccessful = FALSE; |
|
190 if (processStarted) { |
|
191 // Wait for the updater process to finish |
|
192 LOG(("Process was started... waiting on result.")); |
|
193 DWORD waitRes = WaitForSingleObject(pi.hProcess, TIME_TO_WAIT_ON_UPDATER); |
|
194 if (WAIT_TIMEOUT == waitRes) { |
|
195 // We waited a long period of time for updater.exe and it never finished |
|
196 // so kill it. |
|
197 TerminateProcess(pi.hProcess, 1); |
|
198 } else { |
|
199 // Check the return code of updater.exe to make sure we get 0 |
|
200 DWORD returnCode; |
|
201 if (GetExitCodeProcess(pi.hProcess, &returnCode)) { |
|
202 LOG(("Process finished with return code %d.", returnCode)); |
|
203 // updater returns 0 if successful. |
|
204 updateWasSuccessful = (returnCode == 0); |
|
205 } else { |
|
206 LOG_WARN(("Process finished but could not obtain return code.")); |
|
207 } |
|
208 } |
|
209 CloseHandle(pi.hProcess); |
|
210 CloseHandle(pi.hThread); |
|
211 |
|
212 // Check just in case updater.exe didn't change the status from |
|
213 // applying. If this is the case we report an error. |
|
214 BOOL isApplying = FALSE; |
|
215 if (IsStatusApplying(argv[1], isApplying) && isApplying) { |
|
216 if (updateWasSuccessful) { |
|
217 LOG(("update.status is still applying even know update " |
|
218 " was successful.")); |
|
219 if (!WriteStatusFailure(argv[1], |
|
220 SERVICE_STILL_APPLYING_ON_SUCCESS)) { |
|
221 LOG_WARN(("Could not write update.status still applying on" |
|
222 " success error.")); |
|
223 } |
|
224 // Since we still had applying we know updater.exe didn't do its |
|
225 // job correctly. |
|
226 updateWasSuccessful = FALSE; |
|
227 } else { |
|
228 LOG_WARN(("update.status is still applying and update was not successful.")); |
|
229 if (!WriteStatusFailure(argv[1], |
|
230 SERVICE_STILL_APPLYING_ON_FAILURE)) { |
|
231 LOG_WARN(("Could not write update.status still applying on" |
|
232 " success error.")); |
|
233 } |
|
234 } |
|
235 } |
|
236 } else { |
|
237 DWORD lastError = GetLastError(); |
|
238 LOG_WARN(("Could not create process as current user, " |
|
239 "updaterPath: %ls; cmdLine: %ls. (%d)", |
|
240 argv[0], cmdLine, lastError)); |
|
241 } |
|
242 |
|
243 // Now that we're done with the update, restore back the updater.ini file |
|
244 // We use it ourselves, and also we want it back in case we had any type |
|
245 // of error so that the normal update process can use it. |
|
246 if (selfHandlePostUpdate) { |
|
247 MoveFileExW(updaterINITemp, updaterINI, MOVEFILE_REPLACE_EXISTING); |
|
248 |
|
249 // Only run the PostUpdate if the update was successful |
|
250 if (updateWasSuccessful && argc > 2) { |
|
251 LPCWSTR updateInfoDir = argv[1]; |
|
252 bool backgroundUpdate = IsUpdateBeingStaged(argc, argv); |
|
253 |
|
254 // Launch the PostProcess with admin access in session 0. This is |
|
255 // actually launching the post update process but it takes in the |
|
256 // callback app path to figure out where to apply to. |
|
257 // The PostUpdate process with user only access will be done inside |
|
258 // the unelevated updater.exe after the update process is complete |
|
259 // from the service. We don't know here which session to start |
|
260 // the user PostUpdate process from. |
|
261 // Note that we don't need to do this if we're just staging the |
|
262 // update in the background, as the PostUpdate step runs when |
|
263 // performing the replacing in that case. |
|
264 if (!backgroundUpdate) { |
|
265 LOG(("Launching post update process as the service in session 0.")); |
|
266 if (!LaunchWinPostProcess(installDir, updateInfoDir, true, nullptr)) { |
|
267 LOG_WARN(("The post update process could not be launched." |
|
268 " installDir: %ls, updateInfoDir: %ls", |
|
269 installDir, updateInfoDir)); |
|
270 } |
|
271 } |
|
272 } |
|
273 } |
|
274 |
|
275 free(cmdLine); |
|
276 return updateWasSuccessful; |
|
277 } |
|
278 |
|
279 /** |
|
280 * Processes a software update command |
|
281 * |
|
282 * @param argc The number of arguments in argv |
|
283 * @param argv The arguments normally passed to updater.exe |
|
284 * argv[0] must be the path to updater.exe |
|
285 * @return TRUE if the update was successful. |
|
286 */ |
|
287 BOOL |
|
288 ProcessSoftwareUpdateCommand(DWORD argc, LPWSTR *argv) |
|
289 { |
|
290 BOOL result = TRUE; |
|
291 if (argc < 3) { |
|
292 LOG_WARN(("Not enough command line parameters specified. " |
|
293 "Updating update.status.")); |
|
294 |
|
295 // We can only update update.status if argv[1] exists. argv[1] is |
|
296 // the directory where the update.status file exists. |
|
297 if (argc < 2 || |
|
298 !WriteStatusFailure(argv[1], |
|
299 SERVICE_NOT_ENOUGH_COMMAND_LINE_ARGS)) { |
|
300 LOG_WARN(("Could not write update.status service update failure. (%d)", |
|
301 GetLastError())); |
|
302 } |
|
303 return FALSE; |
|
304 } |
|
305 |
|
306 WCHAR installDir[MAX_PATH + 1] = {L'\0'}; |
|
307 if (!GetInstallationDir(argc, argv, installDir)) { |
|
308 LOG_WARN(("Could not get the installation directory")); |
|
309 if (!WriteStatusFailure(argv[1], |
|
310 SERVICE_INSTALLDIR_ERROR)) { |
|
311 LOG_WARN(("Could not write update.status for GetInstallationDir failure.")); |
|
312 } |
|
313 return FALSE; |
|
314 } |
|
315 |
|
316 // Make sure the path to the updater to use for the update is local. |
|
317 // We do this check to make sure that file locking is available for |
|
318 // race condition security checks. |
|
319 BOOL isLocal = FALSE; |
|
320 if (!IsLocalFile(argv[0], isLocal) || !isLocal) { |
|
321 LOG_WARN(("Filesystem in path %ls is not supported (%d)", |
|
322 argv[0], GetLastError())); |
|
323 if (!WriteStatusFailure(argv[1], |
|
324 SERVICE_UPDATER_NOT_FIXED_DRIVE)) { |
|
325 LOG_WARN(("Could not write update.status service update failure. (%d)", |
|
326 GetLastError())); |
|
327 } |
|
328 return FALSE; |
|
329 } |
|
330 |
|
331 nsAutoHandle noWriteLock(CreateFileW(argv[0], GENERIC_READ, FILE_SHARE_READ, |
|
332 nullptr, OPEN_EXISTING, 0, nullptr)); |
|
333 if (INVALID_HANDLE_VALUE == noWriteLock) { |
|
334 LOG_WARN(("Could not set no write sharing access on file. (%d)", |
|
335 GetLastError())); |
|
336 if (!WriteStatusFailure(argv[1], |
|
337 SERVICE_COULD_NOT_LOCK_UPDATER)) { |
|
338 LOG_WARN(("Could not write update.status service update failure. (%d)", |
|
339 GetLastError())); |
|
340 } |
|
341 return FALSE; |
|
342 } |
|
343 |
|
344 // Verify that the updater.exe that we are executing is the same |
|
345 // as the one in the installation directory which we are updating. |
|
346 // The installation dir that we are installing to is installDir. |
|
347 WCHAR installDirUpdater[MAX_PATH + 1] = { L'\0' }; |
|
348 wcsncpy(installDirUpdater, installDir, MAX_PATH); |
|
349 if (!PathAppendSafe(installDirUpdater, L"updater.exe")) { |
|
350 LOG_WARN(("Install directory updater could not be determined.")); |
|
351 result = FALSE; |
|
352 } |
|
353 |
|
354 BOOL updaterIsCorrect; |
|
355 if (result && !VerifySameFiles(argv[0], installDirUpdater, |
|
356 updaterIsCorrect)) { |
|
357 LOG_WARN(("Error checking if the updaters are the same.\n" |
|
358 "Path 1: %ls\nPath 2: %ls", argv[0], installDirUpdater)); |
|
359 result = FALSE; |
|
360 } |
|
361 |
|
362 if (result && !updaterIsCorrect) { |
|
363 LOG_WARN(("The updaters do not match, updater will not run.")); |
|
364 result = FALSE; |
|
365 } |
|
366 |
|
367 if (result) { |
|
368 LOG(("updater.exe was compared successfully to the installation directory" |
|
369 " updater.exe.")); |
|
370 } else { |
|
371 if (!WriteStatusFailure(argv[1], |
|
372 SERVICE_UPDATER_COMPARE_ERROR)) { |
|
373 LOG_WARN(("Could not write update.status updater compare failure.")); |
|
374 } |
|
375 return FALSE; |
|
376 } |
|
377 |
|
378 // Check to make sure the updater.exe module has the unique updater identity. |
|
379 // This is a security measure to make sure that the signed executable that |
|
380 // we will run is actually an updater. |
|
381 HMODULE updaterModule = LoadLibraryEx(argv[0], nullptr, |
|
382 LOAD_LIBRARY_AS_DATAFILE); |
|
383 if (!updaterModule) { |
|
384 LOG_WARN(("updater.exe module could not be loaded. (%d)", GetLastError())); |
|
385 result = FALSE; |
|
386 } else { |
|
387 char updaterIdentity[64]; |
|
388 if (!LoadStringA(updaterModule, IDS_UPDATER_IDENTITY, |
|
389 updaterIdentity, sizeof(updaterIdentity))) { |
|
390 LOG_WARN(("The updater.exe application does not contain the Mozilla" |
|
391 " updater identity.")); |
|
392 result = FALSE; |
|
393 } |
|
394 |
|
395 if (strcmp(updaterIdentity, UPDATER_IDENTITY_STRING)) { |
|
396 LOG_WARN(("The updater.exe identity string is not valid.")); |
|
397 result = FALSE; |
|
398 } |
|
399 FreeLibrary(updaterModule); |
|
400 } |
|
401 |
|
402 if (result) { |
|
403 LOG(("The updater.exe application contains the Mozilla" |
|
404 " updater identity.")); |
|
405 } else { |
|
406 if (!WriteStatusFailure(argv[1], |
|
407 SERVICE_UPDATER_IDENTITY_ERROR)) { |
|
408 LOG_WARN(("Could not write update.status no updater identity.")); |
|
409 } |
|
410 return TRUE; |
|
411 } |
|
412 |
|
413 // Check for updater.exe sign problems |
|
414 BOOL updaterSignProblem = FALSE; |
|
415 #ifndef DISABLE_UPDATER_AUTHENTICODE_CHECK |
|
416 updaterSignProblem = !DoesBinaryMatchAllowedCertificates(installDir, |
|
417 argv[0]); |
|
418 #endif |
|
419 |
|
420 // Only proceed with the update if we have no signing problems |
|
421 if (!updaterSignProblem) { |
|
422 BOOL updateProcessWasStarted = FALSE; |
|
423 if (StartUpdateProcess(argc, argv, installDir, |
|
424 updateProcessWasStarted)) { |
|
425 LOG(("updater.exe was launched and run successfully!")); |
|
426 LogFlush(); |
|
427 |
|
428 // Don't attempt to update the service when the update is being staged. |
|
429 if (!IsUpdateBeingStaged(argc, argv)) { |
|
430 // We might not execute code after StartServiceUpdate because |
|
431 // the service installer will stop the service if it is running. |
|
432 StartServiceUpdate(installDir); |
|
433 } |
|
434 } else { |
|
435 result = FALSE; |
|
436 LOG_WARN(("Error running update process. Updating update.status (%d)", |
|
437 GetLastError())); |
|
438 LogFlush(); |
|
439 |
|
440 // If the update process was started, then updater.exe is responsible for |
|
441 // setting the failure code. If it could not be started then we do the |
|
442 // work. We set an error instead of directly setting status pending |
|
443 // so that the app.update.service.errors pref can be updated when |
|
444 // the callback app restarts. |
|
445 if (!updateProcessWasStarted) { |
|
446 if (!WriteStatusFailure(argv[1], |
|
447 SERVICE_UPDATER_COULD_NOT_BE_STARTED)) { |
|
448 LOG_WARN(("Could not write update.status service update failure. (%d)", |
|
449 GetLastError())); |
|
450 } |
|
451 } |
|
452 } |
|
453 } else { |
|
454 result = FALSE; |
|
455 LOG_WARN(("Could not start process due to certificate check error on " |
|
456 "updater.exe. Updating update.status. (%d)", GetLastError())); |
|
457 |
|
458 // When there is a certificate check error on the updater.exe application, |
|
459 // we want to write out the error. |
|
460 if (!WriteStatusFailure(argv[1], |
|
461 SERVICE_UPDATER_SIGN_ERROR)) { |
|
462 LOG_WARN(("Could not write pending state to update.status. (%d)", |
|
463 GetLastError())); |
|
464 } |
|
465 } |
|
466 |
|
467 return result; |
|
468 } |
|
469 |
|
470 /** |
|
471 * Obtains the updater path alongside a subdir of the service binary. |
|
472 * The purpose of this function is to return a path that is likely high |
|
473 * integrity and therefore more safe to execute code from. |
|
474 * |
|
475 * @param serviceUpdaterPath Out parameter for the path where the updater |
|
476 * should be copied to. |
|
477 * @return TRUE if a file path was obtained. |
|
478 */ |
|
479 BOOL |
|
480 GetSecureUpdaterPath(WCHAR serviceUpdaterPath[MAX_PATH + 1]) |
|
481 { |
|
482 if (!GetModuleFileNameW(nullptr, serviceUpdaterPath, MAX_PATH)) { |
|
483 LOG_WARN(("Could not obtain module filename when attempting to " |
|
484 "use a secure updater path. (%d)", GetLastError())); |
|
485 return FALSE; |
|
486 } |
|
487 |
|
488 if (!PathRemoveFileSpecW(serviceUpdaterPath)) { |
|
489 LOG_WARN(("Couldn't remove file spec when attempting to use a secure " |
|
490 "updater path. (%d)", GetLastError())); |
|
491 return FALSE; |
|
492 } |
|
493 |
|
494 if (!PathAppendSafe(serviceUpdaterPath, L"update")) { |
|
495 LOG_WARN(("Couldn't append file spec when attempting to use a secure " |
|
496 "updater path. (%d)", GetLastError())); |
|
497 return FALSE; |
|
498 } |
|
499 |
|
500 CreateDirectoryW(serviceUpdaterPath, nullptr); |
|
501 |
|
502 if (!PathAppendSafe(serviceUpdaterPath, L"updater.exe")) { |
|
503 LOG_WARN(("Couldn't append file spec when attempting to use a secure " |
|
504 "updater path. (%d)", GetLastError())); |
|
505 return FALSE; |
|
506 } |
|
507 |
|
508 return TRUE; |
|
509 } |
|
510 |
|
511 /** |
|
512 * Deletes the passed in updater path and the associated updater.ini file. |
|
513 * |
|
514 * @param serviceUpdaterPath The path to delete. |
|
515 * @return TRUE if a file was deleted. |
|
516 */ |
|
517 BOOL |
|
518 DeleteSecureUpdater(WCHAR serviceUpdaterPath[MAX_PATH + 1]) |
|
519 { |
|
520 BOOL result = FALSE; |
|
521 if (serviceUpdaterPath[0]) { |
|
522 result = DeleteFileW(serviceUpdaterPath); |
|
523 if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && |
|
524 GetLastError() != ERROR_FILE_NOT_FOUND) { |
|
525 LOG_WARN(("Could not delete service updater path: '%ls'.", |
|
526 serviceUpdaterPath)); |
|
527 } |
|
528 |
|
529 WCHAR updaterINIPath[MAX_PATH + 1] = { L'\0' }; |
|
530 if (PathGetSiblingFilePath(updaterINIPath, serviceUpdaterPath, |
|
531 L"updater.ini")) { |
|
532 result = DeleteFileW(updaterINIPath); |
|
533 if (!result && GetLastError() != ERROR_PATH_NOT_FOUND && |
|
534 GetLastError() != ERROR_FILE_NOT_FOUND) { |
|
535 LOG_WARN(("Could not delete service updater INI path: '%ls'.", |
|
536 updaterINIPath)); |
|
537 } |
|
538 } |
|
539 } |
|
540 return result; |
|
541 } |
|
542 |
|
543 /** |
|
544 * Executes a service command. |
|
545 * |
|
546 * @param argc The number of arguments in argv |
|
547 * @param argv The service command line arguments, argv[0] and argv[1] |
|
548 * and automatically included by Windows. argv[2] is the |
|
549 * service command. |
|
550 * |
|
551 * @return FALSE if there was an error executing the service command. |
|
552 */ |
|
553 BOOL |
|
554 ExecuteServiceCommand(int argc, LPWSTR *argv) |
|
555 { |
|
556 if (argc < 3) { |
|
557 LOG_WARN(("Not enough command line arguments to execute a service command")); |
|
558 return FALSE; |
|
559 } |
|
560 |
|
561 // The tests work by making sure the log has changed, so we put a |
|
562 // unique ID in the log. |
|
563 RPC_WSTR guidString = RPC_WSTR(L""); |
|
564 GUID guid; |
|
565 HRESULT hr = CoCreateGuid(&guid); |
|
566 if (SUCCEEDED(hr)) { |
|
567 UuidToString(&guid, &guidString); |
|
568 } |
|
569 LOG(("Executing service command %ls, ID: %ls", |
|
570 argv[2], reinterpret_cast<LPCWSTR>(guidString))); |
|
571 RpcStringFree(&guidString); |
|
572 |
|
573 BOOL result = FALSE; |
|
574 if (!lstrcmpi(argv[2], L"software-update")) { |
|
575 |
|
576 // Use the passed in command line arguments for the update, except for the |
|
577 // path to updater.exe. We copy updater.exe to a the directory of the |
|
578 // MozillaMaintenance service so that a low integrity process cannot |
|
579 // replace the updater.exe at any point and use that for the update. |
|
580 // It also makes DLL injection attacks harder. |
|
581 LPWSTR oldUpdaterPath = argv[3]; |
|
582 WCHAR secureUpdaterPath[MAX_PATH + 1] = { L'\0' }; |
|
583 result = GetSecureUpdaterPath(secureUpdaterPath); // Does its own logging |
|
584 if (result) { |
|
585 LOG(("Passed in path: '%ls'; Using this path for updating: '%ls'.", |
|
586 oldUpdaterPath, secureUpdaterPath)); |
|
587 DeleteSecureUpdater(secureUpdaterPath); |
|
588 result = CopyFileW(oldUpdaterPath, secureUpdaterPath, FALSE); |
|
589 } |
|
590 |
|
591 if (!result) { |
|
592 LOG_WARN(("Could not copy path to secure location. (%d)", |
|
593 GetLastError())); |
|
594 if (argc > 4 && !WriteStatusFailure(argv[4], |
|
595 SERVICE_COULD_NOT_COPY_UPDATER)) { |
|
596 LOG_WARN(("Could not write update.status could not copy updater error")); |
|
597 } |
|
598 } else { |
|
599 |
|
600 // We obtained the path and copied it successfully, update the path to |
|
601 // use for the service update. |
|
602 argv[3] = secureUpdaterPath; |
|
603 |
|
604 WCHAR oldUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; |
|
605 WCHAR secureUpdaterINIPath[MAX_PATH + 1] = { L'\0' }; |
|
606 if (PathGetSiblingFilePath(secureUpdaterINIPath, secureUpdaterPath, |
|
607 L"updater.ini") && |
|
608 PathGetSiblingFilePath(oldUpdaterINIPath, oldUpdaterPath, |
|
609 L"updater.ini")) { |
|
610 // This is non fatal if it fails there is no real harm |
|
611 if (!CopyFileW(oldUpdaterINIPath, secureUpdaterINIPath, FALSE)) { |
|
612 LOG_WARN(("Could not copy updater.ini from: '%ls' to '%ls'. (%d)", |
|
613 oldUpdaterINIPath, secureUpdaterINIPath, GetLastError())); |
|
614 } |
|
615 } |
|
616 |
|
617 result = ProcessSoftwareUpdateCommand(argc - 3, argv + 3); |
|
618 DeleteSecureUpdater(secureUpdaterPath); |
|
619 } |
|
620 |
|
621 // We might not reach here if the service install succeeded |
|
622 // because the service self updates itself and the service |
|
623 // installer will stop the service. |
|
624 LOG(("Service command %ls complete.", argv[2])); |
|
625 } else { |
|
626 LOG_WARN(("Service command not recognized: %ls.", argv[2])); |
|
627 // result is already set to FALSE |
|
628 } |
|
629 |
|
630 LOG(("service command %ls complete with result: %ls.", |
|
631 argv[1], (result ? L"Success" : L"Failure"))); |
|
632 return TRUE; |
|
633 } |