LibreOffice Module onlineupdate (master) 1
updatehelper.cxx
Go to the documentation of this file.
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#ifdef _WIN32
6#include <windows.h>
7
8// Needed for CreateToolhelp32Snapshot
9#include <tlhelp32.h>
10#ifndef ONLY_SERVICE_LAUNCHING
11
12#include <stdio.h>
13#include "shlobj.h"
14#include "updatehelper.h"
15#include "uachelper.h"
16#include "pathhash.h"
17
18#include <memory>
19
20// Needed for PathAppendW
21#include <shlwapi.h>
22
23BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra);
24
33BOOL
34PathGetSiblingFilePath(LPWSTR destinationBuffer,
35 LPCWSTR siblingFilePath,
36 LPCWSTR newFileName)
37{
38 if (wcslen(siblingFilePath) >= MAX_PATH)
39 {
40 return FALSE;
41 }
42
43 wcsncpy(destinationBuffer, siblingFilePath, MAX_PATH);
44 if (!PathRemoveFileSpecW(destinationBuffer))
45 {
46 return FALSE;
47 }
48
49 if (wcslen(destinationBuffer) + wcslen(newFileName) >= MAX_PATH)
50 {
51 return FALSE;
52 }
53
54 return PathAppendSafe(destinationBuffer, newFileName);
55}
56
71BOOL
72LaunchWinPostProcess(const WCHAR *installationDir,
73 const WCHAR *updateInfoDir,
74 bool forceSync,
75 HANDLE userToken)
76{
77 WCHAR workingDirectory[MAX_PATH + 1] = { L'\0' };
78 wcsncpy(workingDirectory, installationDir, MAX_PATH);
79
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"))
85 {
86 return FALSE;
87 }
88
89 WCHAR exefile[MAX_PATH + 1];
90 WCHAR exearg[MAX_PATH + 1];
91 WCHAR exeasync[10];
92 bool async = true;
93 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeRelPath", nullptr,
94 exefile, MAX_PATH + 1, inifile))
95 {
96 return FALSE;
97 }
98
99 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeArg", nullptr, exearg,
100 MAX_PATH + 1, inifile))
101 {
102 return FALSE;
103 }
104
105 if (!GetPrivateProfileStringW(L"PostUpdateWin", L"ExeAsync", L"TRUE",
106 exeasync,
107 sizeof(exeasync)/sizeof(exeasync[0]),
108 inifile))
109 {
110 return FALSE;
111 }
112
113 WCHAR exefullpath[MAX_PATH + 1] = { L'\0' };
114 wcsncpy(exefullpath, installationDir, MAX_PATH);
115 if (!PathAppendSafe(exefullpath, exefile))
116 {
117 return false;
118 }
119
120 WCHAR dlogFile[MAX_PATH + 1];
121 if (!PathGetSiblingFilePath(dlogFile, exefullpath, L"uninstall.update"))
122 {
123 return FALSE;
124 }
125
126 WCHAR slogFile[MAX_PATH + 1] = { L'\0' };
127 wcsncpy(slogFile, updateInfoDir, MAX_PATH);
128 if (!PathAppendSafe(slogFile, L"update.log"))
129 {
130 return FALSE;
131 }
132
133 WCHAR dummyArg[14] = { L'\0' };
134 wcsncpy(dummyArg, L"argv0ignored ", sizeof(dummyArg) / sizeof(dummyArg[0]) - 1);
135
136 size_t len = wcslen(exearg) + wcslen(dummyArg);
137 WCHAR *cmdline = (WCHAR *) malloc((len + 1) * sizeof(WCHAR));
138 if (!cmdline)
139 {
140 return FALSE;
141 }
142
143 wcsncpy(cmdline, dummyArg, len);
144 wcscat(cmdline, exearg);
145
146 if (forceSync ||
147 !_wcsnicmp(exeasync, L"false", 6) ||
148 !_wcsnicmp(exeasync, L"0", 2))
149 {
150 async = false;
151 }
152
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);
157
158 STARTUPINFOW si = {sizeof(si), 0};
159 si.lpDesktop = L"";
160 PROCESS_INFORMATION pi = {0};
161
162 bool ok;
163 if (userToken)
164 {
165 ok = CreateProcessAsUserW(userToken,
166 exefullpath,
167 cmdline,
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
173 workingDirectory,
174 &si,
175 &pi);
176 }
177 else
178 {
179 ok = CreateProcessW(exefullpath,
180 cmdline,
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
186 workingDirectory,
187 &si,
188 &pi);
189 }
190 free(cmdline);
191 if (ok)
192 {
193 if (!async)
194 WaitForSingleObject(pi.hProcess, INFINITE);
195 CloseHandle(pi.hProcess);
196 CloseHandle(pi.hThread);
197 }
198 return ok;
199}
200
209BOOL
210StartServiceUpdate(LPCWSTR installDir)
211{
212 // Get a handle to the local computer SCM database
213 SC_HANDLE manager = OpenSCManager(nullptr, nullptr,
214 SC_MANAGER_ALL_ACCESS);
215 if (!manager)
216 {
217 return FALSE;
218 }
219
220 // Open the service
221 SC_HANDLE svc = OpenServiceW(manager, SVC_NAME,
222 SERVICE_ALL_ACCESS);
223 if (!svc)
224 {
225 CloseServiceHandle(manager);
226 return FALSE;
227 }
228
229 // If we reach here, then the service is installed, so
230 // proceed with upgrading it.
231
232 CloseServiceHandle(manager);
233
234 // The service exists and we opened it, get the config bytes needed
235 DWORD bytesNeeded;
236 if (!QueryServiceConfigW(svc, nullptr, 0, &bytesNeeded) &&
237 GetLastError() != ERROR_INSUFFICIENT_BUFFER)
238 {
239 CloseServiceHandle(svc);
240 return FALSE;
241 }
242
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))
249 {
250 CloseServiceHandle(svc);
251 return FALSE;
252 }
253
254 CloseServiceHandle(svc);
255
256 QUERY_SERVICE_CONFIGW &serviceConfig =
257 *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
258
259 PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
260
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"))
265 {
266 return FALSE;
267 }
268
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");
274
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))
278 {
279 return FALSE;
280 }
281
282 // Start the upgrade comparison process
283 STARTUPINFOW si = {0};
284 si.cb = sizeof(STARTUPINFOW);
285 // No particular desktop because no UI
286 si.lpDesktop = L"";
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,
292 cmdLine,
293 nullptr, nullptr, FALSE,
294 0,
295 nullptr, installDir, &si, &pi);
296 if (svcUpdateProcessStarted)
297 {
298 CloseHandle(pi.hProcess);
299 CloseHandle(pi.hThread);
300 }
301 return svcUpdateProcessStarted;
302}
303
304#endif
305
318DWORD
319StartServiceCommand(int argc, LPCWSTR* argv)
320{
321 DWORD lastState = WaitForServiceStop(SVC_NAME, 5);
322 if (lastState != SERVICE_STOPPED)
323 {
324 return 20000 + lastState;
325 }
326
327 // Get a handle to the SCM database.
328 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
329 SC_MANAGER_CONNECT |
330 SC_MANAGER_ENUMERATE_SERVICE);
331 if (!serviceManager)
332 {
333 return 17001;
334 }
335
336 // Get a handle to the service.
337 SC_HANDLE service = OpenServiceW(serviceManager,
338 SVC_NAME,
339 SERVICE_START);
340 if (!service)
341 {
342 CloseServiceHandle(serviceManager);
343 return 17002;
344 }
345
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)
352 {
353 BOOL result = StartServiceW(service, argc, argv);
354 if (result)
355 {
356 lastError = ERROR_SUCCESS;
357 break;
358 }
359 else
360 {
361 lastError = GetLastError();
362 }
363 Sleep(100);
364 currentWaitMS += 100;
365 }
366 CloseServiceHandle(service);
367 CloseServiceHandle(serviceManager);
368 return lastError;
369}
370
371#ifndef ONLY_SERVICE_LAUNCHING
372
383DWORD
384LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR* argv)
385{
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";
392
393 for (int i = 0; i < argc; ++i)
394 {
395 updaterServiceArgv[i + 2] = argv[i];
396 }
397
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;
402 return ret;
403}
404
412BOOL
413PathAppendSafe(LPWSTR base, LPCWSTR extra)
414{
415 if (wcslen(base) + wcslen(extra) >= MAX_PATH)
416 {
417 return FALSE;
418 }
419
420 return PathAppendW(base, extra);
421}
422
430BOOL
431WriteStatusPending(LPCWSTR updateDirPath)
432{
433 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
434 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
435 if (!PathAppendSafe(updateStatusFilePath, L"update.status"))
436 {
437 return FALSE;
438 }
439
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)
444 {
445 return FALSE;
446 }
447
448 DWORD wrote;
449 BOOL ok = WriteFile(statusFile, pending,
450 sizeof(pending) - 1, &wrote, nullptr);
451 CloseHandle(statusFile);
452 return ok && (wrote == sizeof(pending) - 1);
453}
454
461BOOL
462WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)
463{
464 WCHAR updateStatusFilePath[MAX_PATH + 1] = { L'\0' };
465 wcsncpy(updateStatusFilePath, updateDirPath, MAX_PATH);
466 if (!PathAppendSafe(updateStatusFilePath, L"update.status"))
467 {
468 return FALSE;
469 }
470
471 HANDLE statusFile = CreateFileW(updateStatusFilePath, GENERIC_WRITE, 0,
472 nullptr, CREATE_ALWAYS, 0, nullptr);
473 if (statusFile == INVALID_HANDLE_VALUE)
474 {
475 return FALSE;
476 }
477 char failure[32];
478 sprintf(failure, "failed: %d", errorCode);
479
480 DWORD toWrite = strlen(failure);
481 DWORD wrote;
482 BOOL ok = WriteFile(statusFile, failure,
483 toWrite, &wrote, nullptr);
484 CloseHandle(statusFile);
485 return ok && wrote == toWrite;
486}
487
488#endif
489
522DWORD
523WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
524{
525 // 0x000000CF is defined above to be not set
526 DWORD lastServiceState = 0x000000CF;
527
528 // Get a handle to the SCM database.
529 SC_HANDLE serviceManager = OpenSCManager(nullptr, nullptr,
530 SC_MANAGER_CONNECT |
531 SC_MANAGER_ENUMERATE_SERVICE);
532 if (!serviceManager)
533 {
534 DWORD lastError = GetLastError();
535 switch (lastError)
536 {
537 case ERROR_ACCESS_DENIED:
538 return 0x000000FD;
539 case ERROR_DATABASE_DOES_NOT_EXIST:
540 return 0x000000FE;
541 default:
542 return 0x000000FF;
543 }
544 }
545
546 // Get a handle to the service.
547 SC_HANDLE service = OpenServiceW(serviceManager,
548 serviceName,
549 SERVICE_QUERY_STATUS);
550 if (!service)
551 {
552 DWORD lastError = GetLastError();
553 CloseServiceHandle(serviceManager);
554 switch (lastError)
555 {
556 case ERROR_ACCESS_DENIED:
557 return 0x000000EB;
558 case ERROR_INVALID_HANDLE:
559 return 0x000000EC;
560 case ERROR_INVALID_NAME:
561 return 0x000000ED;
562 case ERROR_SERVICE_DOES_NOT_EXIST:
563 return 0x000000EE;
564 default:
565 return 0x000000EF;
566 }
567 }
568
569 DWORD currentWaitMS = 0;
570 SERVICE_STATUS_PROCESS ssp;
571 ssp.dwCurrentState = lastServiceState;
572 while (currentWaitMS < maxWaitSeconds * 1000)
573 {
574 DWORD bytesNeeded;
575 if (!QueryServiceStatusEx(service, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp,
576 sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded))
577 {
578 DWORD lastError = GetLastError();
579 switch (lastError)
580 {
581 case ERROR_INVALID_HANDLE:
582 ssp.dwCurrentState = 0x000000D9;
583 break;
584 case ERROR_ACCESS_DENIED:
585 ssp.dwCurrentState = 0x000000DA;
586 break;
587 case ERROR_INSUFFICIENT_BUFFER:
588 ssp.dwCurrentState = 0x000000DB;
589 break;
590 case ERROR_INVALID_PARAMETER:
591 ssp.dwCurrentState = 0x000000DC;
592 break;
593 case ERROR_INVALID_LEVEL:
594 ssp.dwCurrentState = 0x000000DD;
595 break;
596 case ERROR_SHUTDOWN_IN_PROGRESS:
597 ssp.dwCurrentState = 0x000000DE;
598 break;
599 // These 3 errors can occur when the service is not yet stopped but
600 // it is stopping.
601 case ERROR_INVALID_SERVICE_CONTROL:
602 case ERROR_SERVICE_CANNOT_ACCEPT_CTRL:
603 case ERROR_SERVICE_NOT_ACTIVE:
604 currentWaitMS += 50;
605 Sleep(50);
606 continue;
607 default:
608 ssp.dwCurrentState = 0x000000DF;
609 }
610
611 // We couldn't query the status so just break out
612 break;
613 }
614
615 // The service is already in use.
616 if (ssp.dwCurrentState == SERVICE_STOPPED)
617 {
618 break;
619 }
620 currentWaitMS += 50;
621 Sleep(50);
622 }
623
624 lastServiceState = ssp.dwCurrentState;
625 CloseServiceHandle(service);
626 CloseServiceHandle(serviceManager);
627 return lastServiceState;
628}
629
630#ifndef ONLY_SERVICE_LAUNCHING
631
641DWORD
642IsProcessRunning(LPCWSTR filename)
643{
644 // Take a snapshot of all processes in the system.
645 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
646 if (INVALID_HANDLE_VALUE == snapshot)
647 {
648 return GetLastError();
649 }
650
651 PROCESSENTRY32W processEntry;
652 processEntry.dwSize = sizeof(PROCESSENTRY32W);
653 if (!Process32FirstW(snapshot, &processEntry))
654 {
655 DWORD lastError = GetLastError();
656 CloseHandle(snapshot);
657 return lastError;
658 }
659
660 do
661 {
662 if (wcsicmp(filename, processEntry.szExeFile) == 0)
663 {
664 CloseHandle(snapshot);
665 return ERROR_SUCCESS;
666 }
667 }
668 while (Process32NextW(snapshot, &processEntry));
669 CloseHandle(snapshot);
670 return ERROR_NOT_FOUND;
671}
672
683DWORD
684WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
685{
686 DWORD applicationRunningError = WAIT_TIMEOUT;
687 for (DWORD i = 0; i < maxSeconds; i++)
688 {
689 applicationRunningError = IsProcessRunning(filename);
690 if (ERROR_NOT_FOUND == applicationRunningError)
691 {
692 return ERROR_SUCCESS;
693 }
694 Sleep(1000);
695 }
696
697 if (ERROR_SUCCESS == applicationRunningError)
698 {
699 return WAIT_TIMEOUT;
700 }
701
702 return applicationRunningError;
703}
704
710BOOL
712{
713 HKEY testOnlyFallbackKey;
714 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE,
716 KEY_READ | KEY_WOW64_64KEY,
717 &testOnlyFallbackKey) != ERROR_SUCCESS)
718 {
719 return FALSE;
720 }
721
722 RegCloseKey(testOnlyFallbackKey);
723 return TRUE;
724}
725
726#endif
727
734BOOL
735IsLocalFile(LPCWSTR file, BOOL &isLocal)
736{
737 WCHAR rootPath[MAX_PATH + 1] = { L'\0' };
738 if (wcslen(file) > MAX_PATH)
739 {
740 return FALSE;
741 }
742
743 wcsncpy(rootPath, file, MAX_PATH);
744 PathStripToRootW(rootPath);
745 isLocal = GetDriveTypeW(rootPath) == DRIVE_FIXED;
746 return TRUE;
747}
748
749
758static BOOL
759GetDWORDValue(HKEY key, LPCWSTR valueName, DWORD &retValue)
760{
761 DWORD regDWORDValueSize = sizeof(DWORD);
762 LONG retCode = RegQueryValueExW(key, valueName, 0, nullptr,
763 reinterpret_cast<LPBYTE>(&retValue),
764 &regDWORDValueSize);
765 return ERROR_SUCCESS == retCode;
766}
767
777BOOL
778IsUnpromptedElevation(BOOL &isUnpromptedElevation)
779{
781 {
782 return FALSE;
783 }
784
785 LPCWSTR UACBaseRegKey =
786 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
787 HKEY baseKey;
788 LONG retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
789 UACBaseRegKey, 0,
790 KEY_READ, &baseKey);
791 if (retCode != ERROR_SUCCESS)
792 {
793 return FALSE;
794 }
795
796 DWORD consent;
797 DWORD secureDesktop = 0;
798 BOOL success = GetDWORDValue(baseKey, L"ConsentPromptBehaviorAdmin",
799 consent);
800 success = success &&
801 GetDWORDValue(baseKey, L"PromptOnSecureDesktop", secureDesktop);
802 isUnpromptedElevation = !consent && !secureDesktop;
803
804 RegCloseKey(baseKey);
805 return success;
806}
807#endif
static bool CanUserElevate()
rtl::Reference< ParseManager > manager
#define TRUE
#define FALSE
#define MAX_PATH
int i
int sprintf(char(&s)[N], char const *format, T &&... arguments)
const wchar_t *typedef BOOL
LONG
BOOL PathAppendSafe(LPWSTR base, LPCWSTR extra)
Any result
BOOL LaunchWinPostProcess(const WCHAR *installationDir, const WCHAR *updateInfoDir, bool forceSync, HANDLE userToken)
BOOL DoesFallbackKeyExist()
BOOL StartServiceUpdate(LPCWSTR installDir)
BOOL IsUnpromptedElevation(BOOL &isUnpromptedElevation)
DWORD WaitForProcessExit(LPCWSTR filename, DWORD maxSeconds)
DWORD StartServiceCommand(int argc, LPCWSTR *argv)
#define SVC_NAME
Definition: updatehelper.h:21
BOOL IsLocalFile(LPCWSTR file, BOOL &isLocal)
DWORD WaitForServiceStop(LPCWSTR serviceName, DWORD maxWaitSeconds)
BOOL WriteStatusPending(LPCWSTR updateDirPath)
#define TEST_ONLY_FALLBACK_KEY_PATH
Definition: updatehelper.h:32
DWORD LaunchServiceSoftwareUpdateCommand(int argc, LPCWSTR *argv)
BOOL PathGetSiblingFilePath(LPWSTR destinationBuffer, LPCWSTR siblingFilePath, LPCWSTR newFileName)
BOOL WriteStatusFailure(LPCWSTR updateDirPath, int errorCode)