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