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 #include <aclapi.h>
7 #include <stdlib.h>
8 #include <shlwapi.h>
9 
10 // Used for DNLEN and UNLEN
11 #include <lm.h>
12 
13 #include "serviceinstall.hxx"
14 #include "servicebase.hxx"
15 #include "updatehelper.h"
16 #include "shellapi.h"
17 #include "readstrings.h"
18 #include "errors.h"
19 
20 #include <memory>
21 
22 #pragma comment(lib, "version.lib")
23 
24 namespace {
25 
26 struct AutoServiceHandle
27 {
AutoServiceHandle__anon09c117650111::AutoServiceHandle28     AutoServiceHandle(SC_HANDLE handle):
29         mHandle(handle)
30     {
31     }
32 
~AutoServiceHandle__anon09c117650111::AutoServiceHandle33     ~AutoServiceHandle()
34     {
35         releaseHandle(mHandle);
36     }
37 
releaseHandle__anon09c117650111::AutoServiceHandle38     void releaseHandle(SC_HANDLE handle)
39     {
40         if (handle != nullptr)
41             CloseServiceHandle(handle);
42     }
43 
get__anon09c117650111::AutoServiceHandle44     SC_HANDLE get() const
45     {
46         return mHandle;
47     }
48 
operator bool__anon09c117650111::AutoServiceHandle49     operator bool() const
50     {
51         return mHandle != nullptr;
52     }
53 
reset__anon09c117650111::AutoServiceHandle54     void reset(SC_HANDLE handle = nullptr)
55     {
56         releaseHandle(mHandle);
57         mHandle = handle;
58     }
59 
60     SC_HANDLE mHandle;
61 };
62 
63 }
64 
65 /**
66  * A wrapper function to read strings for the maintenance service.
67  *
68  * @param path    The path of the ini file to read from
69  * @param results The maintenance service strings that were read
70  * @return OK on success
71 */
72 static int
ReadMaintenanceServiceStrings(LPCWSTR path,MaintenanceServiceStringTable * results)73 ReadMaintenanceServiceStrings(LPCWSTR path,
74                               MaintenanceServiceStringTable *results)
75 {
76     // Read in the maintenance service description string if specified.
77     const unsigned int kNumStrings = 1;
78     // TODO: moggi: needs adaptation for LibreOffice
79     const char *kServiceKeys = "MozillaMaintenanceDescription\0";
80     char serviceStrings[kNumStrings][MAX_TEXT_LEN];
81     int result = ReadStrings(path, kServiceKeys,
82                              kNumStrings, serviceStrings);
83     if (result != OK)
84     {
85         serviceStrings[0][0] = '\0';
86     }
87     strncpy(results->serviceDescription,
88             serviceStrings[0], MAX_TEXT_LEN - 1);
89     results->serviceDescription[MAX_TEXT_LEN - 1] = '\0';
90     return result;
91 }
92 
93 /**
94  * Obtains the version number from the specified PE file's version information
95  * Version Format: A.B.C.D (Example 10.0.0.300)
96  *
97  * @param  path The path of the file to check the version on
98  * @param  A    The first part of the version number
99  * @param  B    The second part of the version number
100  * @param  C    The third part of the version number
101  * @param  D    The fourth part of the version number
102  * @return TRUE if successful
103  */
104 static BOOL
GetVersionNumberFromPath(LPWSTR path,DWORD & A,DWORD & B,DWORD & C,DWORD & D)105 GetVersionNumberFromPath(LPWSTR path, DWORD &A, DWORD &B,
106                          DWORD &C, DWORD &D)
107 {
108     DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(path, 0);
109     std::unique_ptr<char[]> fileVersionInfo(new char[fileVersionInfoSize]);
110     if (!GetFileVersionInfoW(path, 0, fileVersionInfoSize,
111                              fileVersionInfo.get()))
112     {
113         LOG_WARN(("Could not obtain file info of old service.  (%d)",
114                   GetLastError()));
115         return FALSE;
116     }
117 
118     VS_FIXEDFILEINFO *fixedFileInfo =
119         reinterpret_cast<VS_FIXEDFILEINFO *>(fileVersionInfo.get());
120     UINT size;
121     if (!VerQueryValueW(fileVersionInfo.get(), L"\\",
122                         reinterpret_cast<LPVOID*>(&fixedFileInfo), &size))
123     {
124         LOG_WARN(("Could not query file version info of old service.  (%d)",
125                   GetLastError()));
126         return FALSE;
127     }
128 
129     A = HIWORD(fixedFileInfo->dwFileVersionMS);
130     B = LOWORD(fixedFileInfo->dwFileVersionMS);
131     C = HIWORD(fixedFileInfo->dwFileVersionLS);
132     D = LOWORD(fixedFileInfo->dwFileVersionLS);
133     return TRUE;
134 }
135 
136 /**
137  * Updates the service description with what is stored in updater.ini
138  * at the same path as the currently executing module binary.
139  *
140  * @param serviceHandle A handle to an opened service with
141  *                      SERVICE_CHANGE_CONFIG access right
142  * @param TRUE on success.
143 */
144 BOOL
UpdateServiceDescription(SC_HANDLE serviceHandle)145 UpdateServiceDescription(SC_HANDLE serviceHandle)
146 {
147     WCHAR updaterINIPath[MAX_PATH + 1];
148     if (!GetModuleFileNameW(nullptr, updaterINIPath,
149                             sizeof(updaterINIPath) /
150                             sizeof(updaterINIPath[0])))
151     {
152         LOG_WARN(("Could not obtain module filename when attempting to "
153                   "modify service description.  (%d)", GetLastError()));
154         return FALSE;
155     }
156 
157     if (!PathRemoveFileSpecW(updaterINIPath))
158     {
159         LOG_WARN(("Could not remove file spec when attempting to "
160                   "modify service description.  (%d)", GetLastError()));
161         return FALSE;
162     }
163 
164     if (!PathAppendSafe(updaterINIPath, L"updater.ini"))
165     {
166         LOG_WARN(("Could not append updater.ini filename when attempting to "
167                   "modify service description.  (%d)", GetLastError()));
168         return FALSE;
169     }
170 
171     if (GetFileAttributesW(updaterINIPath) == INVALID_FILE_ATTRIBUTES)
172     {
173         LOG_WARN(("updater.ini file does not exist, will not modify "
174                   "service description.  (%d)", GetLastError()));
175         return FALSE;
176     }
177 
178     MaintenanceServiceStringTable serviceStrings;
179     int rv = ReadMaintenanceServiceStrings(updaterINIPath, &serviceStrings);
180     if (rv != OK || !strlen(serviceStrings.serviceDescription))
181     {
182         LOG_WARN(("updater.ini file does not contain a maintenance "
183                   "service description."));
184         return FALSE;
185     }
186 
187     WCHAR serviceDescription[MAX_TEXT_LEN];
188     if (!MultiByteToWideChar(CP_UTF8, 0,
189                              serviceStrings.serviceDescription, -1,
190                              serviceDescription,
191                              sizeof(serviceDescription) /
192                              sizeof(serviceDescription[0])))
193     {
194         LOG_WARN(("Could not convert description to wide string format.  (%d)",
195                   GetLastError()));
196         return FALSE;
197     }
198 
199     SERVICE_DESCRIPTIONW descriptionConfig;
200     descriptionConfig.lpDescription = serviceDescription;
201     if (!ChangeServiceConfig2W(serviceHandle,
202                                SERVICE_CONFIG_DESCRIPTION,
203                                &descriptionConfig))
204     {
205         LOG_WARN(("Could not change service config.  (%d)", GetLastError()));
206         return FALSE;
207     }
208 
209     LOG(("The service description was updated successfully."));
210     return TRUE;
211 }
212 
213 /**
214  * Determines if the MozillaMaintenance service path needs to be updated
215  * and fixes it if it is wrong.
216  *
217  * @param service             A handle to the service to fix.
218  * @param currentServicePath  The current (possibly wrong) path that is used.
219  * @param servicePathWasWrong Out parameter set to TRUE if a fix was needed.
220  * @return TRUE if the service path is now correct.
221 */
222 BOOL
FixServicePath(SC_HANDLE service,LPCWSTR currentServicePath,BOOL & servicePathWasWrong)223 FixServicePath(SC_HANDLE service,
224                LPCWSTR currentServicePath,
225                BOOL &servicePathWasWrong)
226 {
227     // When we originally upgraded the MozillaMaintenance service we
228     // would uninstall the service on each upgrade.  This had an
229     // intermittent error which could cause the service to use the file
230     // maintenanceservice_tmp.exe as the install path.  Only a small number
231     // of Nightly users would be affected by this, but we check for this
232     // state here and fix the user if they are affected.
233     //
234     // We also fix the path in the case of the path not being quoted.
235     size_t currentServicePathLen = wcslen(currentServicePath);
236     bool doesServiceHaveCorrectPath =
237         currentServicePathLen > 2 &&
238         !wcsstr(currentServicePath, L"maintenanceservice_tmp.exe") &&
239         currentServicePath[0] == L'\"' &&
240         currentServicePath[currentServicePathLen - 1] == L'\"';
241 
242     if (doesServiceHaveCorrectPath)
243     {
244         LOG(("The MozillaMaintenance service path is correct."));
245         servicePathWasWrong = FALSE;
246         return TRUE;
247     }
248     // This is a recoverable situation so not logging as a warning
249     LOG(("The MozillaMaintenance path is NOT correct. It was: %ls",
250          currentServicePath));
251 
252     servicePathWasWrong = TRUE;
253     WCHAR fixedPath[MAX_PATH + 1] = { L'\0' };
254     wcsncpy(fixedPath, currentServicePath, MAX_PATH);
255     PathUnquoteSpacesW(fixedPath);
256     if (!PathRemoveFileSpecW(fixedPath))
257     {
258         LOG_WARN(("Couldn't remove file spec.  (%d)", GetLastError()));
259         return FALSE;
260     }
261     if (!PathAppendSafe(fixedPath, L"maintenanceservice.exe"))
262     {
263         LOG_WARN(("Couldn't append file spec.  (%d)", GetLastError()));
264         return FALSE;
265     }
266     PathQuoteSpacesW(fixedPath);
267 
268 
269     if (!ChangeServiceConfigW(service, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE,
270                               SERVICE_NO_CHANGE, fixedPath, nullptr, nullptr,
271                               nullptr, nullptr, nullptr, nullptr))
272     {
273         LOG_WARN(("Could not fix service path.  (%d)", GetLastError()));
274         return FALSE;
275     }
276 
277     LOG(("Fixed service path to: %ls.", fixedPath));
278     return TRUE;
279 }
280 
281 /**
282  * Installs or upgrades the SVC_NAME service.
283  * If an existing service is already installed, we replace it with the
284  * currently running process.
285  *
286  * @param  action The action to perform.
287  * @return TRUE if the service was installed/upgraded
288  */
289 BOOL
SvcInstall(SvcInstallAction action)290 SvcInstall(SvcInstallAction action)
291 {
292     // Get a handle to the local computer SCM database with full access rights.
293     AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
294                                    SC_MANAGER_ALL_ACCESS));
295     if (!schSCManager)
296     {
297         LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
298         return FALSE;
299     }
300 
301     WCHAR newServiceBinaryPath[MAX_PATH + 1];
302     if (!GetModuleFileNameW(nullptr, newServiceBinaryPath,
303                             sizeof(newServiceBinaryPath) /
304                             sizeof(newServiceBinaryPath[0])))
305     {
306         LOG_WARN(("Could not obtain module filename when attempting to "
307                   "install service.  (%d)",
308                   GetLastError()));
309         return FALSE;
310     }
311 
312     // Check if we already have the service installed.
313     AutoServiceHandle schService(OpenServiceW(schSCManager.get(),
314                                  SVC_NAME,
315                                  SERVICE_ALL_ACCESS));
316     DWORD lastError = GetLastError();
317     if (!schService && ERROR_SERVICE_DOES_NOT_EXIST != lastError)
318     {
319         // The service exists but we couldn't open it
320         LOG_WARN(("Could not open service.  (%d)", GetLastError()));
321         return FALSE;
322     }
323 
324     if (schService)
325     {
326         // The service exists but it may not have the correct permissions.
327         // This could happen if the permissions were not set correctly originally
328         // or have been changed after the installation.  This will reset the
329         // permissions back to allow limited user accounts.
330         if (!SetUserAccessServiceDACL(schService.get()))
331         {
332             LOG_WARN(("Could not reset security ACE on service handle. It might not be "
333                       "possible to start the service. This error should never "
334                       "happen.  (%d)", GetLastError()));
335         }
336 
337         // The service exists and we opened it
338         DWORD bytesNeeded;
339         if (!QueryServiceConfigW(schService.get(), nullptr, 0, &bytesNeeded) &&
340                 GetLastError() != ERROR_INSUFFICIENT_BUFFER)
341         {
342             LOG_WARN(("Could not determine buffer size for query service config.  (%d)",
343                       GetLastError()));
344             return FALSE;
345         }
346 
347         // Get the service config information, in particular we want the binary
348         // path of the service.
349         std::unique_ptr<char[]> serviceConfigBuffer(new char[bytesNeeded]);
350         if (!QueryServiceConfigW(schService.get(),
351                                  reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get()),
352                                  bytesNeeded, &bytesNeeded))
353         {
354             LOG_WARN(("Could open service but could not query service config.  (%d)",
355                       GetLastError()));
356             return FALSE;
357         }
358         QUERY_SERVICE_CONFIGW &serviceConfig =
359             *reinterpret_cast<QUERY_SERVICE_CONFIGW*>(serviceConfigBuffer.get());
360 
361         // Check if we need to fix the service path
362         BOOL servicePathWasWrong;
363         static BOOL alreadyCheckedFixServicePath = FALSE;
364         if (!alreadyCheckedFixServicePath)
365         {
366             if (!FixServicePath(schService.get(), serviceConfig.lpBinaryPathName,
367                                 servicePathWasWrong))
368             {
369                 LOG_WARN(("Could not fix service path. This should never happen.  (%d)",
370                           GetLastError()));
371                 // True is returned because the service is pointing to
372                 // maintenanceservice_tmp.exe so it actually was upgraded to the
373                 // newest installed service.
374                 return TRUE;
375             }
376             else if (servicePathWasWrong)
377             {
378                 // Now that the path is fixed we should re-attempt the install.
379                 // This current process' image path is maintenanceservice_tmp.exe.
380                 // The service used to point to maintenanceservice_tmp.exe.
381                 // The service was just fixed to point to maintenanceservice.exe.
382                 // Re-attempting an install from scratch will work as normal.
383                 alreadyCheckedFixServicePath = TRUE;
384                 LOG(("Restarting install action: %d", action));
385                 return SvcInstall(action);
386             }
387         }
388 
389         // Ensure the service path is not quoted. We own this memory and know it to
390         // be large enough for the quoted path, so it is large enough for the
391         // unquoted path.  This function cannot fail.
392         PathUnquoteSpacesW(serviceConfig.lpBinaryPathName);
393 
394         // Obtain the existing maintenanceservice file's version number and
395         // the new file's version number.  Versions are in the format of
396         // A.B.C.D.
397         DWORD existingA, existingB, existingC, existingD;
398         DWORD newA, newB, newC, newD;
399         BOOL obtainedExistingVersionInfo =
400             GetVersionNumberFromPath(serviceConfig.lpBinaryPathName,
401                                      existingA, existingB,
402                                      existingC, existingD);
403         if (!GetVersionNumberFromPath(newServiceBinaryPath, newA,
404                                       newB, newC, newD))
405         {
406             LOG_WARN(("Could not obtain version number from new path"));
407             return FALSE;
408         }
409 
410         // Check if we need to replace the old binary with the new one
411         // If we couldn't get the old version info then we assume we should
412         // replace it.
413         if (ForceInstallSvc == action ||
414                 !obtainedExistingVersionInfo ||
415                 (existingA < newA) ||
416                 (existingA == newA && existingB < newB) ||
417                 (existingA == newA && existingB == newB &&
418                  existingC < newC) ||
419                 (existingA == newA && existingB == newB &&
420                  existingC == newC && existingD < newD))
421         {
422 
423             // We have a newer updater, so update the description from the INI file.
424             UpdateServiceDescription(schService.get());
425 
426             schService.reset();
427             if (!StopService())
428             {
429                 return FALSE;
430             }
431 
432             if (!wcscmp(newServiceBinaryPath, serviceConfig.lpBinaryPathName))
433             {
434                 LOG(("File is already in the correct location, no action needed for "
435                      "upgrade.  The path is: \"%ls\"", newServiceBinaryPath));
436                 return TRUE;
437             }
438 
439             BOOL result = TRUE;
440 
441             // Attempt to copy the new binary over top the existing binary.
442             // If there is an error we try to move it out of the way and then
443             // copy it in.  First try the safest / easiest way to overwrite the file.
444             if (!CopyFileW(newServiceBinaryPath,
445                            serviceConfig.lpBinaryPathName, FALSE))
446             {
447                 LOG_WARN(("Could not overwrite old service binary file. "
448                           "This should never happen, but if it does the next "
449                           "upgrade will fix it, the service is not a critical "
450                           "component that needs to be installed for upgrades "
451                           "to work.  (%d)", GetLastError()));
452 
453                 // We rename the last 3 filename chars in an unsafe way.  Manually
454                 // verify there are more than 3 chars for safe failure in MoveFileExW.
455                 const size_t len = wcslen(serviceConfig.lpBinaryPathName);
456                 if (len > 3)
457                 {
458                     // Calculate the temp file path that we're moving the file to. This
459                     // is the same as the proper service path but with a .old extension.
460                     LPWSTR oldServiceBinaryTempPath =
461                         new WCHAR[len + 1];
462                     memset(oldServiceBinaryTempPath, 0, (len + 1) * sizeof (WCHAR));
463                     wcsncpy(oldServiceBinaryTempPath, serviceConfig.lpBinaryPathName, len);
464                     // Rename the last 3 chars to 'old'
465                     wcsncpy(oldServiceBinaryTempPath + len - 3, L"old", 3);
466 
467                     // Move the current (old) service file to the temp path.
468                     if (MoveFileExW(serviceConfig.lpBinaryPathName,
469                                     oldServiceBinaryTempPath,
470                                     MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
471                     {
472                         // The old binary is moved out of the way, copy in the new one.
473                         if (!CopyFileW(newServiceBinaryPath,
474                                        serviceConfig.lpBinaryPathName, FALSE))
475                         {
476                             // It is best to leave the old service binary in this condition.
477                             LOG_WARN(("The new service binary could not be copied in."
478                                       " The service will not be upgraded."));
479                             result = FALSE;
480                         }
481                         else
482                         {
483                             LOG(("The new service binary was copied in by first moving the"
484                                  " old one out of the way."));
485                         }
486 
487                         // Attempt to get rid of the old service temp path.
488                         if (DeleteFileW(oldServiceBinaryTempPath))
489                         {
490                             LOG(("The old temp service path was deleted: %ls.",
491                                  oldServiceBinaryTempPath));
492                         }
493                         else
494                         {
495                             // The old temp path could not be removed.  It will be removed
496                             // the next time the user can't copy the binary in or on uninstall.
497                             LOG_WARN(("The old temp service path was not deleted."));
498                         }
499                     }
500                     else
501                     {
502                         // It is best to leave the old service binary in this condition.
503                         LOG_WARN(("Could not move old service file out of the way from:"
504                                   " \"%ls\" to \"%ls\". Service will not be upgraded.  (%d)",
505                                   serviceConfig.lpBinaryPathName,
506                                   oldServiceBinaryTempPath, GetLastError()));
507                         result = FALSE;
508                     }
509                     delete[] oldServiceBinaryTempPath;
510                 }
511                 else
512                 {
513                     // It is best to leave the old service binary in this condition.
514                     LOG_WARN(("Service binary path was less than 3, service will"
515                               " not be updated.  This should never happen."));
516                     result = FALSE;
517                 }
518             }
519             else
520             {
521                 LOG(("The new service binary was copied in."));
522             }
523 
524             // We made a copy of ourselves to the existing location.
525             // The tmp file (the process of which we are executing right now) will be
526             // left over.  Attempt to delete the file on the next reboot.
527             if (MoveFileExW(newServiceBinaryPath, nullptr,
528                             MOVEFILE_DELAY_UNTIL_REBOOT))
529             {
530                 LOG(("Deleting the old file path on the next reboot: %ls.",
531                      newServiceBinaryPath));
532             }
533             else
534             {
535                 LOG_WARN(("Call to delete the old file path failed: %ls.",
536                           newServiceBinaryPath));
537             }
538 
539             return result;
540         }
541 
542         // We don't need to copy ourselves to the existing location.
543         // The tmp file (the process of which we are executing right now) will be
544         // left over.  Attempt to delete the file on the next reboot.
545         MoveFileExW(newServiceBinaryPath, nullptr, MOVEFILE_DELAY_UNTIL_REBOOT);
546 
547         // nothing to do, we already have a newer service installed
548         return TRUE;
549     }
550 
551     // If the service does not exist and we are upgrading, don't install it.
552     if (UpgradeSvc == action)
553     {
554         // The service does not exist and we are upgrading, so don't install it
555         return TRUE;
556     }
557 
558     // Quote the path only if it contains spaces.
559     PathQuoteSpacesW(newServiceBinaryPath);
560     // The service does not already exist so create the service as on demand
561     schService.reset(CreateServiceW(schSCManager.get(), SVC_NAME, SVC_DISPLAY_NAME,
562                                     SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
563                                     SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
564                                     newServiceBinaryPath, nullptr, nullptr,
565                                     nullptr, nullptr, nullptr));
566     if (!schService)
567     {
568         LOG_WARN(("Could not create Windows service. "
569                   "This error should never happen since a service install "
570                   "should only be called when elevated.  (%d)", GetLastError()));
571         return FALSE;
572     }
573 
574     if (!SetUserAccessServiceDACL(schService.get()))
575     {
576         LOG_WARN(("Could not set security ACE on service handle, the service will not "
577                   "be able to be started from unelevated processes. "
578                   "This error should never happen.  (%d)",
579                   GetLastError()));
580     }
581 
582     UpdateServiceDescription(schService.get());
583 
584     return TRUE;
585 }
586 
587 /**
588  * Stops the Maintenance service.
589  *
590  * @return TRUE if successful.
591  */
592 BOOL
StopService()593 StopService()
594 {
595     // Get a handle to the local computer SCM database with full access rights.
596     AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
597                                    SC_MANAGER_ALL_ACCESS));
598     if (!schSCManager)
599     {
600         LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
601         return FALSE;
602     }
603 
604     // Open the service
605     AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME,
606                                  SERVICE_ALL_ACCESS));
607     if (!schService)
608     {
609         LOG_WARN(("Could not open service.  (%d)", GetLastError()));
610         return FALSE;
611     }
612 
613     LOG(("Sending stop request..."));
614     SERVICE_STATUS status;
615     SetLastError(ERROR_SUCCESS);
616     if (!ControlService(schService.get(), SERVICE_CONTROL_STOP, &status) &&
617             GetLastError() != ERROR_SERVICE_NOT_ACTIVE)
618     {
619         LOG_WARN(("Error sending stop request.  (%d)", GetLastError()));
620     }
621 
622     schSCManager.reset();
623     schService.reset();
624 
625     LOG(("Waiting for service stop..."));
626     DWORD lastState = WaitForServiceStop(SVC_NAME, 30);
627 
628     // The service can be in a stopped state but the exe still in use
629     // so make sure the process is really gone before proceeding
630     WaitForProcessExit(L"maintenanceservice.exe", 30);
631     LOG(("Done waiting for service stop, last service state: %d", lastState));
632 
633     return lastState == SERVICE_STOPPED;
634 }
635 
636 /**
637  * Uninstalls the Maintenance service.
638  *
639  * @return TRUE if successful.
640  */
641 BOOL
SvcUninstall()642 SvcUninstall()
643 {
644     // Get a handle to the local computer SCM database with full access rights.
645     AutoServiceHandle schSCManager(OpenSCManager(nullptr, nullptr,
646                                    SC_MANAGER_ALL_ACCESS));
647     if (!schSCManager)
648     {
649         LOG_WARN(("Could not open service manager.  (%d)", GetLastError()));
650         return FALSE;
651     }
652 
653     // Open the service
654     AutoServiceHandle schService(OpenServiceW(schSCManager.get(), SVC_NAME,
655                                  SERVICE_ALL_ACCESS));
656     if (!schService)
657     {
658         LOG_WARN(("Could not open service.  (%d)", GetLastError()));
659         return FALSE;
660     }
661 
662     //Stop the service so it deletes faster and so the uninstaller
663     // can actually delete its EXE.
664     DWORD totalWaitTime = 0;
665     SERVICE_STATUS status;
666     static const int maxWaitTime = 1000 * 60; // Never wait more than a minute
667     if (ControlService(schService.get(), SERVICE_CONTROL_STOP, &status))
668     {
669         do
670         {
671             Sleep(status.dwWaitHint);
672             totalWaitTime += (status.dwWaitHint + 10);
673             if (status.dwCurrentState == SERVICE_STOPPED)
674             {
675                 break;
676             }
677             else if (totalWaitTime > maxWaitTime)
678             {
679                 break;
680             }
681         }
682         while (QueryServiceStatus(schService.get(), &status));
683     }
684 
685     // Delete the service or mark it for deletion
686     BOOL deleted = DeleteService(schService.get());
687     if (!deleted)
688     {
689         deleted = (GetLastError() == ERROR_SERVICE_MARKED_FOR_DELETE);
690     }
691 
692     return deleted;
693 }
694 
695 /**
696  * Sets the access control list for user access for the specified service.
697  *
698  * @param  hService The service to set the access control list on
699  * @return TRUE if successful
700  */
701 BOOL
SetUserAccessServiceDACL(SC_HANDLE hService)702 SetUserAccessServiceDACL(SC_HANDLE hService)
703 {
704     PACL pNewAcl = nullptr;
705     PSECURITY_DESCRIPTOR psd = nullptr;
706     DWORD lastError = SetUserAccessServiceDACL(hService, pNewAcl, psd);
707     if (pNewAcl)
708     {
709         LocalFree((HLOCAL)pNewAcl);
710     }
711     if (psd)
712     {
713         LocalFree((LPVOID)psd);
714     }
715     return ERROR_SUCCESS == lastError;
716 }
717 
718 /**
719  * Sets the access control list for user access for the specified service.
720  *
721  * @param  hService  The service to set the access control list on
722  * @param  pNewAcl   The out param ACL which should be freed by caller
723  * @param  psd       out param security descriptor, should be freed by caller
724  * @return ERROR_SUCCESS if successful
725  */
726 DWORD
SetUserAccessServiceDACL(SC_HANDLE hService,PACL & pNewAcl,PSECURITY_DESCRIPTOR psd)727 SetUserAccessServiceDACL(SC_HANDLE hService, PACL &pNewAcl,
728                          PSECURITY_DESCRIPTOR psd)
729 {
730     // Get the current security descriptor needed size
731     DWORD needed = 0;
732     if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
733                                     &psd, 0, &needed))
734     {
735         if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
736         {
737             LOG_WARN(("Could not query service object security size.  (%d)",
738                       GetLastError()));
739             return GetLastError();
740         }
741 
742         DWORD size = needed;
743         psd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, size);
744         if (!psd)
745         {
746             LOG_WARN(("Could not allocate security descriptor.  (%d)",
747                       GetLastError()));
748             return ERROR_INSUFFICIENT_BUFFER;
749         }
750 
751         // Get the actual security descriptor now
752         if (!QueryServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION,
753                                         psd, size, &needed))
754         {
755             LOG_WARN(("Could not allocate security descriptor.  (%d)",
756                       GetLastError()));
757             return GetLastError();
758         }
759     }
760 
761     // Get the current DACL from the security descriptor.
762     PACL pacl = nullptr;
763     BOOL bDaclPresent = FALSE;
764     BOOL bDaclDefaulted = FALSE;
765     if ( !GetSecurityDescriptorDacl(psd, &bDaclPresent, &pacl,
766                                     &bDaclDefaulted))
767     {
768         LOG_WARN(("Could not obtain DACL.  (%d)", GetLastError()));
769         return GetLastError();
770     }
771 
772     PSID sid;
773     DWORD SIDSize = SECURITY_MAX_SID_SIZE;
774     sid = LocalAlloc(LMEM_FIXED, SIDSize);
775     if (!sid)
776     {
777         LOG_WARN(("Could not allocate SID memory.  (%d)", GetLastError()));
778         return GetLastError();
779     }
780 
781     if (!CreateWellKnownSid(WinBuiltinUsersSid, nullptr, sid, &SIDSize))
782     {
783         DWORD lastError = GetLastError();
784         LOG_WARN(("Could not create well known SID.  (%d)", lastError));
785         LocalFree(sid);
786         return lastError;
787     }
788 
789     // Lookup the account name, the function fails if you don't pass in
790     // a buffer for the domain name but it's not used since we're using
791     // the built in account Sid.
792     SID_NAME_USE accountType;
793     WCHAR accountName[UNLEN + 1] = { L'\0' };
794     WCHAR domainName[DNLEN + 1] = { L'\0' };
795     DWORD accountNameSize = UNLEN + 1;
796     DWORD domainNameSize = DNLEN + 1;
797     if (!LookupAccountSidW(nullptr, sid, accountName,
798                            &accountNameSize,
799                            domainName, &domainNameSize, &accountType))
800     {
801         LOG_WARN(("Could not lookup account Sid, will try Users.  (%d)",
802                   GetLastError()));
803         wcsncpy(accountName, L"Users", UNLEN);
804     }
805 
806     // We already have the group name so we can get rid of the SID
807     FreeSid(sid);
808     sid = nullptr;
809 
810     // Build the ACE, BuildExplicitAccessWithName cannot fail so it is not logged.
811     EXPLICIT_ACCESS ea;
812     BuildExplicitAccessWithNameW(&ea, accountName,
813                                  SERVICE_START | SERVICE_STOP | GENERIC_READ,
814                                  SET_ACCESS, NO_INHERITANCE);
815     DWORD lastError = SetEntriesInAclW(1, (PEXPLICIT_ACCESS)&ea, pacl, &pNewAcl);
816     if (ERROR_SUCCESS != lastError)
817     {
818         LOG_WARN(("Could not set entries in ACL.  (%d)", lastError));
819         return lastError;
820     }
821 
822     // Initialize a new security descriptor.
823     SECURITY_DESCRIPTOR sd;
824     if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
825     {
826         LOG_WARN(("Could not initialize security descriptor.  (%d)",
827                   GetLastError()));
828         return GetLastError();
829     }
830 
831     // Set the new DACL in the security descriptor.
832     if (!SetSecurityDescriptorDacl(&sd, TRUE, pNewAcl, FALSE))
833     {
834         LOG_WARN(("Could not set security descriptor DACL.  (%d)",
835                   GetLastError()));
836         return GetLastError();
837     }
838 
839     // Set the new security descriptor for the service object.
840     if (!SetServiceObjectSecurity(hService, DACL_SECURITY_INFORMATION, &sd))
841     {
842         LOG_WARN(("Could not set object security.  (%d)",
843                   GetLastError()));
844         return GetLastError();
845     }
846 
847     // Woohoo, raise the roof
848     LOG(("User access was set successfully on the service."));
849     return ERROR_SUCCESS;
850 }
851