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