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                                   &regDWORDValueSize);
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