toolkit/components/maintenanceservice/serviceinstall.cpp

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:93835bc86966
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 }

mercurial