1 /* 2 * PROJECT: ReactOS Service Host 3 * LICENSE: BSD - See COPYING.ARM in the top level directory 4 * FILE: base/services/svchost/svchost.c 5 * PURPOSE: Main Service Host Implementation Routines 6 * PROGRAMMERS: ReactOS Portable Systems Group 7 */ 8 9 /* INCLUDES ******************************************************************/ 10 11 #include "svchost.h" 12 13 #include <objidl.h> 14 15 /* GLOBALS *******************************************************************/ 16 17 LIST_ENTRY DllList; 18 CRITICAL_SECTION ListLock; 19 DWORD ServiceCount; 20 LPCWSTR ServiceNames; 21 PSVCHOST_SERVICE ServiceArray; 22 23 /* FUNCTIONS *****************************************************************/ 24 25 __callback 26 LONG 27 WINAPI 28 SvchostUnhandledExceptionFilter ( 29 _In_ struct _EXCEPTION_POINTERS * ExceptionInfo 30 ) 31 { 32 /* Call the default OS handler */ 33 return RtlUnhandledExceptionFilter(ExceptionInfo); 34 } 35 36 VOID 37 WINAPI 38 DummySvchostCtrlHandler ( 39 _In_ DWORD dwControl 40 ) 41 { 42 /* This is just a stub while we abort loading */ 43 return; 44 } 45 46 PSVCHOST_OPTIONS 47 WINAPI 48 BuildCommandOptions ( 49 _In_ LPWSTR pszCmdLine 50 ) 51 { 52 ULONG cbCmdLine; 53 PSVCHOST_OPTIONS pOptions; 54 LPWSTR pch, pGroupNameStart, lpServiceGroup; 55 LPWSTR *pGroupName = NULL; 56 57 /* Nothing to do without a command-line */ 58 if (pszCmdLine == NULL) return ERROR_SUCCESS; 59 60 /* Get the length if the command line*/ 61 cbCmdLine = sizeof(WCHAR) * lstrlenW(pszCmdLine) + sizeof(UNICODE_NULL); 62 63 /* Allocate a buffer for the parsed options */ 64 pOptions = MemAlloc(HEAP_ZERO_MEMORY, cbCmdLine + sizeof(*pOptions)); 65 if (pOptions == NULL) return ERROR_SUCCESS; 66 67 /* Copy the buffer into the parsed options block */ 68 pOptions->CmdLineBuffer = (PWCHAR)(pOptions + 1); 69 memcpy(pOptions->CmdLineBuffer, pszCmdLine, cbCmdLine); 70 71 /* Set the command-line as being inside the block itself */ 72 pch = pOptions->CmdLineBuffer; 73 ASSERT(pch); 74 pOptions->CmdLine = pch; 75 76 /* Now scan it */ 77 while (*pch != UNICODE_NULL) 78 { 79 /* And look for the first space or TAB */ 80 if ((*pch == ' ') || (*pch == '\t')) 81 { 82 /* Terminate the command-line there */ 83 *pch++ = UNICODE_NULL; 84 break; 85 } 86 87 /* Keep searching */ 88 pch++; 89 } 90 91 /* Lowercase the entire command line, but not the options */ 92 SvchostCharLowerW(pOptions->CmdLine); 93 94 /* Loop over the command-line */ 95 while (*pch != UNICODE_NULL) 96 { 97 /* Do an inner loop within it */ 98 while (*pch != UNICODE_NULL) 99 { 100 /* And keep going until we find a space or TAB */ 101 if ((*pch != ' ') && (*pch != '\t')) break; 102 pch++; 103 } 104 105 /* Did we reach the end? If so, bail out */ 106 if (*pch == UNICODE_NULL) break; 107 108 /* We found the space -- is it followed by a dash or slash? */ 109 if ((*pch == '-' || *pch == '/') && (*(pch + 1) != UNICODE_NULL)) 110 { 111 /* Yep, and there's at least one character. Is it k or K? */ 112 pch++; 113 if (*pch == 'k' || *pch == 'K') 114 { 115 /* Yep, we have a proper command line with a group! */ 116 pOptions->HasServiceGroup = TRUE; 117 pGroupName = &pOptions->ServiceGroupName; 118 } 119 120 /* Keep going -- the name follows between spaces or quotes */ 121 pch++; 122 } 123 else 124 { 125 /* Nope, so this might be the group being entered */ 126 pGroupNameStart = pch; 127 128 /* Check if there's a quote */ 129 if ((*pch == '"') && (*(pch + 1) != UNICODE_NULL)) 130 { 131 /* There is, keep going until the quote is over with */ 132 pGroupNameStart = ++pch; 133 while (*pch) if (*pch++ == '"') break; 134 } 135 else 136 { 137 /* No quote, so keep going until the next space or TAB */ 138 while ((*pch) && ((*pch != ' ') && (*pch != '\t'))) pch++; 139 } 140 141 /* Now we have a space or quote delimited name, terminate it */ 142 if (*pch) *pch++ = UNICODE_NULL; 143 144 /* Ok, so we have a string -- was it preceded by a -K or /K? */ 145 if (pGroupName) 146 { 147 /* Yes it was, remember this, and don't overwrite it later */ 148 *pGroupName = pGroupNameStart; 149 pGroupName = NULL; 150 } 151 } 152 } 153 154 /* Print out the service group for the debugger, and the command line */ 155 DBG_TRACE("Command line : %ws\n", pszCmdLine); 156 lpServiceGroup = (pOptions->HasServiceGroup) ? 157 pOptions->ServiceGroupName : L"No"; 158 DBG_TRACE("Service Group : %ws\n", lpServiceGroup); 159 160 /* Was a group specified? */ 161 if (pOptions->HasServiceGroup == FALSE) 162 { 163 /* This isn't valid. Print out help on the debugger and fail */ 164 DBG_TRACE("Generic Service Host\n\n" 165 "%ws [-k <key>] | [-r] | <service>\n\n" 166 " -k <key> Host all services whose ImagePath matches\n" 167 " %ws -k <key>.\n\n", 168 lpServiceGroup); 169 MemFree(pOptions); 170 pOptions = NULL; 171 } 172 173 /* Return the allocated option block, if valid*/ 174 return pOptions; 175 } 176 177 DWORD 178 WINAPI 179 ReadPerInstanceRegistryParameters ( 180 _In_ HKEY hKey, 181 _In_ PSVCHOST_OPTIONS pOptions 182 ) 183 { 184 DWORD dwError, dwData; 185 HKEY hSubKey; 186 187 /* We should have a name for this service group... */ 188 ASSERT(pOptions->ServiceGroupName); 189 190 /* Query the group to see what services are part of it */ 191 dwError = RegQueryString(hKey, 192 pOptions->ServiceGroupName, 193 REG_MULTI_SZ, 194 (PBYTE*)&ServiceNames); 195 if ((dwError == ERROR_SUCCESS) && 196 ((ServiceNames == NULL) || (*ServiceNames == UNICODE_NULL))) 197 { 198 /* If the key exists but there's no data, fail */ 199 return ERROR_INVALID_DATA; 200 } 201 202 /* Open the key for this group */ 203 if (RegOpenKeyExW(hKey, 204 pOptions->ServiceGroupName, 205 0, 206 KEY_READ, 207 &hSubKey) != ERROR_SUCCESS) 208 { 209 /* If we couldn't, bail out */ 210 return dwError; 211 } 212 213 /* Check if we should initialize COM */ 214 if (RegQueryDword(hSubKey, 215 L"CoInitializeSecurityParam", 216 &dwData) == ERROR_SUCCESS) 217 { 218 /* Yes, remember the parameter to be sent when we do so */ 219 pOptions->CoInitializeSecurityParam = dwData; 220 } 221 222 /* Also, if COM is requested, we must read a bunch more data... */ 223 if (pOptions->CoInitializeSecurityParam) 224 { 225 /* First, read the authentication level, use a default if none is set */ 226 if (RegQueryDword(hSubKey, L"AuthenticationLevel", &dwData)) 227 { 228 pOptions->AuthenticationLevel = RPC_C_AUTHN_LEVEL_PKT; 229 } 230 else 231 { 232 pOptions->AuthenticationLevel = dwData; 233 } 234 235 /* Do the same for the impersonation level */ 236 if (RegQueryDword(hSubKey, L"ImpersonationLevel", &dwData)) 237 { 238 pOptions->ImpersonationLevel = RPC_C_IMP_LEVEL_IDENTIFY; 239 } 240 else 241 { 242 pOptions->ImpersonationLevel = dwData; 243 } 244 245 /* Do the same for the authentication capabilities */ 246 if (RegQueryDword(hSubKey, L"AuthenticationCapabilities", &dwData)) 247 { 248 pOptions->AuthenticationCapabilities = EOAC_NO_CUSTOM_MARSHAL | 249 EOAC_DISABLE_AAA; 250 } 251 else 252 { 253 pOptions->AuthenticationCapabilities = dwData; 254 } 255 } 256 257 /* Check if we need a specific RPC stack size, if not, we'll set it later */ 258 if (!RegQueryDword(hSubKey, L"DefaultRpcStackSize", &dwData)) 259 { 260 pOptions->DefaultRpcStackSize = dwData; 261 } 262 263 /* Finally, check if the services should be marked critical later on */ 264 if (!RegQueryDword(hSubKey, L"SystemCritical", &dwData)) 265 { 266 pOptions->SystemCritical = dwData; 267 } 268 269 /* All done reading the settings */ 270 RegCloseKey(hSubKey); 271 return dwError; 272 } 273 274 VOID 275 WINAPI 276 BuildServiceArray ( 277 _In_ PSVCHOST_OPTIONS lpOptions 278 ) 279 { 280 DWORD dwError; 281 PSVCHOST_SERVICE pService; 282 LPCWSTR pszServiceName; 283 HKEY hKey; 284 285 /* Open the service host key */ 286 dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE, 287 L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Svchost", 288 0, 289 KEY_READ, 290 &hKey); 291 if (dwError != ERROR_SUCCESS) return; 292 293 /* Read all the parameters for this service group */ 294 dwError = ReadPerInstanceRegistryParameters(hKey, lpOptions); 295 RegCloseKey(hKey); 296 if (dwError != ERROR_SUCCESS) return; 297 298 /* Acquire the database lock while we create the service group */ 299 EnterCriticalSection(&ListLock); 300 301 /* Loop the array of services for this group */ 302 pszServiceName = ServiceNames; 303 while (*pszServiceName != UNICODE_NULL) 304 { 305 /* For each one, increment the amount of services we'llhost */ 306 ServiceCount++; 307 pszServiceName += lstrlenW(pszServiceName) + 1; 308 } 309 310 /* We should host at least one... */ 311 ASSERT(ServiceCount); 312 313 /* Now allocate an array to hold all their pointers */ 314 ServiceArray = MemAlloc(HEAP_ZERO_MEMORY, sizeof(*pService) * ServiceCount); 315 if (ServiceArray) 316 { 317 /* Start at the beginning of the array */ 318 pService = ServiceArray; 319 320 /* Loop through all the services */ 321 pszServiceName = ServiceNames; 322 while (*pszServiceName != UNICODE_NULL) 323 { 324 /* Save the service's name and move to the next entry */ 325 pService++->pszServiceName = pszServiceName; 326 pszServiceName += lstrlenW(pszServiceName) + 1; 327 } 328 329 /* We should have gotten the same count as earlier! */ 330 ASSERT(pService == ServiceArray + ServiceCount); 331 } 332 333 /* We're done, drop the lock */ 334 LeaveCriticalSection(&ListLock); 335 } 336 337 BOOL 338 WINAPI 339 FDebugBreakForService ( 340 _In_ LPCWSTR ServiceName 341 ) 342 { 343 DWORD Data; 344 BOOL DebugBreakOn = FALSE; 345 HKEY hKey, hSubKey; 346 347 /* Open the key for Svchost itself */ 348 if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, 349 L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Svchost", 350 0, 351 KEY_READ, 352 &hKey) == ERROR_SUCCESS) 353 { 354 /* That worked, now open the key for this service, if it exists */ 355 if (RegOpenKeyExW(hKey, ServiceName, 0, KEY_READ, &hSubKey) == ERROR_SUCCESS) 356 { 357 /* It does -- is there a DebugBreak value set? */ 358 if (RegQueryDword(hSubKey, L"DebugBreak", &Data) == ERROR_SUCCESS) 359 { 360 /* There is! Check if it's 0 or 1 */ 361 DebugBreakOn = Data != 0; 362 } 363 364 /* Done, close the service key */ 365 RegCloseKey(hSubKey); 366 } 367 368 /* Done, close the svchost key */ 369 RegCloseKey(hKey); 370 } 371 372 /* Return if the value was set or not */ 373 return DebugBreakOn; 374 } 375 376 DWORD 377 WINAPI 378 OpenServiceParametersKey ( 379 _In_ LPCWSTR lpSubKey, 380 _Out_ PHKEY phKey 381 ) 382 { 383 DWORD dwError; 384 HKEY hSubKey = NULL, hKey = NULL; 385 ASSERT(phKey); 386 387 /* Open the services key */ 388 dwError = RegOpenKeyExW(HKEY_LOCAL_MACHINE, 389 L"System\\CurrentControlSet\\Services", 390 0, 391 KEY_READ, 392 &hKey); 393 if (dwError == ERROR_SUCCESS) 394 { 395 /* Now open the service specific key, returning its handle */ 396 dwError = RegOpenKeyExW(hKey, lpSubKey, 0, KEY_READ, &hSubKey); 397 if (dwError == ERROR_SUCCESS) 398 { 399 /* And if that worked, return a handle to the parameters key */ 400 dwError = RegOpenKeyExW(hSubKey, 401 L"Parameters", 402 0, 403 KEY_READ, 404 phKey); 405 } 406 } 407 408 /* Clean up any leftovers*/ 409 if (hKey != NULL) RegCloseKey(hKey); 410 if (hSubKey != NULL) RegCloseKey(hSubKey); 411 return dwError; 412 } 413 414 VOID 415 WINAPI 416 UnloadServiceDll ( 417 _In_ PSVCHOST_SERVICE pService 418 ) 419 { 420 DWORD cbData; 421 DWORD Data; 422 DWORD dwType; 423 HKEY hKey = NULL; 424 425 cbData = 4; 426 Data = 0; 427 428 /* Grab the lock while we touch the thread count and potentially unload */ 429 EnterCriticalSection(&ListLock); 430 431 /* There is one less active callout into the DLL, it may be unloadabl now */ 432 ASSERT(pService->cServiceActiveThreadCount > 0); 433 pService->cServiceActiveThreadCount--; 434 435 /* Try to open the service registry key */ 436 if (OpenServiceParametersKey(pService->pszServiceName, &hKey) == ERROR_SUCCESS) 437 { 438 /* It worked -- check if we should unload the service when stopped */ 439 if (RegQueryValueExW(hKey, 440 L"ServiceDllUnloadOnStop", 441 0, 442 &dwType, 443 (LPBYTE)&Data, 444 &cbData) == ERROR_SUCCESS) 445 { 446 /* Make sure the data is correctly formatted */ 447 if ((dwType == REG_DWORD) && (Data == 1)) 448 { 449 /* Does the service still have active threads? */ 450 if (pService->cServiceActiveThreadCount != 0) 451 { 452 /* Yep, so we can't kill it just yet */ 453 DBG_TRACE("Skip Unload dll %ws for service %ws, " 454 "active threads %d\n", 455 pService->pDll->pszDllPath, 456 pService->pszServiceName, 457 pService->cServiceActiveThreadCount); 458 } 459 else 460 { 461 /* Nope, we're good to go */ 462 DBG_TRACE("Unload dll %ws for service %ws\n", 463 pService->pDll->pszDllPath, 464 pService->pszServiceName); 465 466 /* Free the library and clear the module handle */ 467 FreeLibrary(pService->pDll->hModule); 468 pService->pDll->hModule = NULL; 469 } 470 } 471 } 472 } 473 474 /* Drop the lock, unload is complete */ 475 LeaveCriticalSection(&ListLock); 476 477 /* Close the parameters key if we had it */ 478 if (hKey) RegCloseKey(hKey); 479 } 480 481 VOID 482 WINAPI 483 SvchostStopCallback ( 484 _In_ PVOID Context, 485 _In_ BOOLEAN TimerOrWaitFired 486 ) 487 { 488 PSVCHOST_CALLBACK_CONTEXT pSvcsStopCbContext = Context; 489 PSVCHOST_STOP_CALLBACK pfnStopCallback; 490 491 /* Hold the lock while we grab the callback */ 492 EnterCriticalSection(&ListLock); 493 494 /* Grab the callback, then clear it */ 495 ASSERT(pSvcsStopCbContext->pService != NULL); 496 pfnStopCallback = pSvcsStopCbContext->pService->pfnStopCallback; 497 ASSERT(pfnStopCallback != NULL); 498 pSvcsStopCbContext->pService->pfnStopCallback = NULL; 499 500 /* Now release the lock, making sure the above was atomic */ 501 LeaveCriticalSection(&ListLock); 502 503 /* Now make the callout */ 504 DBG_TRACE("Call stop callback for service %ws, active threads %d\n", 505 pSvcsStopCbContext->pService->pszServiceName, 506 pSvcsStopCbContext->pService->cServiceActiveThreadCount); 507 pfnStopCallback(pSvcsStopCbContext->pContext, TimerOrWaitFired); 508 509 /* Decrement the active threads -- maybe the DLL can unload now */ 510 UnloadServiceDll(pSvcsStopCbContext->pService); 511 512 /* We no longer need the context */ 513 MemFree(pSvcsStopCbContext); 514 } 515 516 DWORD 517 WINAPI 518 SvcRegisterStopCallback ( 519 _Out_ PHANDLE phNewWaitObject, 520 _In_ PCWSTR ServiceName, 521 _In_ HANDLE hObject, 522 _In_ PSVCHOST_STOP_CALLBACK pfnStopCallback, 523 _In_ PVOID pContext, 524 _In_ ULONG dwFlags 525 ) 526 { 527 ULONG i; 528 PSVCHOST_CALLBACK_CONTEXT pSvcsStopCbContext = NULL; 529 PSVCHOST_SERVICE pService = NULL; 530 DWORD dwError = ERROR_SUCCESS; 531 532 /* All parameters must be present */ 533 if ((phNewWaitObject == NULL) || 534 (ServiceName == NULL) || 535 (hObject == NULL) || 536 (pfnStopCallback == NULL)) 537 { 538 /* Fail otherwise */ 539 return ERROR_INVALID_PARAMETER; 540 } 541 542 /* Don't allow new DLLs while we send notifications */ 543 EnterCriticalSection(&ListLock); 544 545 /* Scan all registered services */ 546 for (i = 0; i < ServiceCount; i++) 547 { 548 /* Search for the service entry matching this service name */ 549 if (lstrcmpiW(ServiceName, ServiceArray[i].pszServiceName) == 0) 550 { 551 /* Found it */ 552 pService = &ServiceArray[i]; 553 break; 554 } 555 } 556 557 /* Do we have a service? Does it already have a callback? */ 558 if ((pService == NULL) || (pService->pfnStopCallback != NULL)) 559 { 560 /* Affirmative, nothing for us to do */ 561 dwError = ERROR_INVALID_DATA; 562 DBG_ERR("Service %ws missing or already registered for callback, " 563 "status %d\n", 564 ServiceName, 565 dwError); 566 goto Quickie; 567 } 568 569 /* Allocate our internal context */ 570 pSvcsStopCbContext = MemAlloc(HEAP_ZERO_MEMORY, sizeof(*pSvcsStopCbContext)); 571 if (pSvcsStopCbContext == NULL) 572 { 573 /* We failed, bail out */ 574 dwError = ERROR_NOT_ENOUGH_MEMORY; 575 DBG_ERR("MemAlloc for context block for service %ws failed, status " 576 "%d\n", 577 ServiceName, 578 dwError); 579 goto Quickie; 580 } 581 582 /* Setup the context and register for the wait */ 583 pSvcsStopCbContext->pContext = pContext; 584 pSvcsStopCbContext->pService = pService; 585 if (RegisterWaitForSingleObject(phNewWaitObject, 586 hObject, 587 SvchostStopCallback, 588 pSvcsStopCbContext, 589 INFINITE, 590 dwFlags)) 591 { 592 /* We now have an active thread associated with this wait */ 593 pService->cServiceActiveThreadCount++; 594 pService->pfnStopCallback = pfnStopCallback; 595 DBG_TRACE("Registered stop callback for service %ws, active threads %d\n", 596 ServiceName, 597 pService->cServiceActiveThreadCount); 598 } 599 else 600 { 601 /* Registration failed, bail out */ 602 dwError = GetLastError(); 603 DBG_ERR("Registration for stop callback for service %ws failed, " 604 "status %d\n", 605 ServiceName, 606 dwError); 607 } 608 609 Quickie: 610 /* Drop the lock, and free the context if we failed */ 611 LeaveCriticalSection(&ListLock); 612 if (dwError != ERROR_SUCCESS) MemFree(pSvcsStopCbContext); 613 return dwError; 614 } 615 616 VOID 617 WINAPI 618 AbortSvchostService ( 619 _In_ LPCWSTR lpServiceName, 620 _In_ DWORD dwExitCode 621 ) 622 { 623 SERVICE_STATUS_HANDLE scHandle; 624 SERVICE_STATUS ServiceStatus; 625 626 /* Make us stopped and accept only query commands */ 627 ServiceStatus.dwCheckPoint = 0; 628 ServiceStatus.dwWaitHint = 0; 629 ServiceStatus.dwServiceSpecificExitCode = 0; 630 ServiceStatus.dwCurrentState = SERVICE_STOPPED; 631 ServiceStatus.dwControlsAccepted = SERVICE_QUERY_CONFIG; 632 ServiceStatus.dwServiceType = SERVICE_WIN32; 633 ServiceStatus.dwWin32ExitCode = dwExitCode; 634 635 /* Register a handler that will do nothing while we are being stopped */ 636 scHandle = RegisterServiceCtrlHandlerW(lpServiceName, 637 DummySvchostCtrlHandler); 638 if (scHandle) 639 { 640 /* Stop us */ 641 if (!SetServiceStatus(scHandle, &ServiceStatus)) 642 { 643 /* Tell the debugger if this didn't work */ 644 DBG_ERR("AbortSvchostService: SetServiceStatus error %ld\n", 645 GetLastError()); 646 } 647 } 648 else 649 { 650 /* Tell the debugger if we couldn't register the handler */ 651 DBG_ERR("AbortSvchostService: RegisterServiceCtrlHandler failed %d\n", 652 GetLastError()); 653 } 654 } 655 656 PVOID 657 WINAPI 658 GetServiceDllFunction ( 659 _In_ PSVCHOST_DLL pDll, 660 _In_ LPCSTR lpProcName, 661 _Out_ PDWORD lpdwError 662 ) 663 { 664 HMODULE hModule; 665 PVOID lpAddress = NULL; 666 ULONG_PTR ulCookie = 0; 667 668 /* Activate the context */ 669 if (ActivateActCtx(pDll->hActCtx, &ulCookie) == FALSE) 670 { 671 /* We couldn't, bail out */ 672 if (lpdwError) *lpdwError = GetLastError(); 673 DBG_ERR("ActivateActCtx for %ws failed. Error %d.\n", 674 pDll->pszDllPath, 675 GetLastError()); 676 return lpAddress; 677 } 678 679 /* Check if we already have a loaded module for this service */ 680 hModule = pDll->hModule; 681 if (!hModule) 682 { 683 /* We don't -- load it */ 684 hModule = LoadLibraryExW(pDll->pszDllPath, 685 NULL, 686 LOAD_WITH_ALTERED_SEARCH_PATH); 687 if (!hModule) 688 { 689 /* We failed to load it, bail out */ 690 if (lpdwError) *lpdwError = GetLastError(); 691 DBG_ERR("LoadLibrary (%ws) failed. Error %d.\n", 692 pDll->pszDllPath, 693 GetLastError()); 694 DeactivateActCtx(0, ulCookie); 695 return lpAddress; 696 } 697 698 /* Loaded! */ 699 pDll->hModule = hModule; 700 } 701 702 /* Next, get the address being looked up*/ 703 lpAddress = GetProcAddress(hModule, lpProcName); 704 if (!lpAddress && lpdwError) 705 { 706 /* We couldn't find it, write the error code and a debug statement */ 707 *lpdwError = GetLastError(); 708 DBG_ERR("GetProcAddress (%s) failed on DLL %ws. Error %d.\n", 709 lpProcName, 710 pDll->pszDllPath, 711 GetLastError()); 712 } 713 714 /* All done, get rid of the activation context */ 715 DeactivateActCtx(0, ulCookie); 716 return lpAddress; 717 } 718 719 PSVCHOST_DLL 720 WINAPI 721 FindDll ( 722 _In_ LPCWSTR pszManifestPath, 723 _In_ LPCWSTR pszDllPath, 724 _In_ PSVCHOST_SERVICE pService 725 ) 726 { 727 PSVCHOST_DLL pDll, pFoundDll = NULL; 728 PLIST_ENTRY pNextEntry; 729 ASSERT(pszDllPath); 730 731 /* Lock the DLL database */ 732 EnterCriticalSection(&ListLock); 733 734 /* Scan through the database */ 735 pNextEntry = DllList.Flink; 736 while (pNextEntry != &DllList) 737 { 738 /* Search for an existing DLL with the same parameters */ 739 pDll = CONTAINING_RECORD(pNextEntry, SVCHOST_DLL, DllList); 740 if ((lstrcmpW(pDll->pszDllPath, pszDllPath) == 0) && 741 (lstrcmpW(pDll->pszManifestPath, pszManifestPath) == 0) && 742 (lstrcmpW(pDll->pService->pszServiceName, pService->pszServiceName) == 0)) 743 { 744 /* Found it! */ 745 pFoundDll = pDll; 746 break; 747 } 748 749 /* Keep searching */ 750 pNextEntry = pNextEntry->Flink; 751 } 752 753 /* All done, release the lock and return what we found, if anything */ 754 LeaveCriticalSection(&ListLock); 755 return pFoundDll; 756 } 757 758 PSVCHOST_DLL 759 WINAPI 760 AddDll ( 761 _In_ LPCWSTR pszManifestPath, 762 _In_ LPCWSTR pszDllPath, 763 _In_ PSVCHOST_SERVICE pService, 764 _Out_ PDWORD lpdwError 765 ) 766 { 767 PSVCHOST_DLL pDll; 768 ULONG nDllPathLength, nManifestPathLength; 769 ASSERT(pszDllPath); 770 ASSERT(*pszDllPath); 771 772 /* Compute the length of the two paths */ 773 nDllPathLength = lstrlenW(pszDllPath); 774 nManifestPathLength = lstrlenW(pszManifestPath); 775 776 /* Allocate the DLL entry for this service */ 777 pDll = MemAlloc(HEAP_ZERO_MEMORY, 778 sizeof(*pDll) + 779 (nDllPathLength + nManifestPathLength) * sizeof(WCHAR) + 780 2 * sizeof(UNICODE_NULL)); 781 if (pDll == NULL) 782 { 783 /* Bail out with failure */ 784 *lpdwError = ERROR_NOT_ENOUGH_MEMORY; 785 return pDll; 786 } 787 788 /* Put the DLL path right after the DLL entry */ 789 pDll->pszDllPath = (LPCWSTR)(pDll + 1); 790 791 /* Put the manifest path right past the DLL path (note the pointer math) */ 792 pDll->pszManifestPath = pDll->pszDllPath + nDllPathLength + 1; 793 794 /* Now copy both paths */ 795 CopyMemory((PVOID)pDll->pszDllPath, 796 pszDllPath, 797 sizeof(WCHAR) * nDllPathLength); 798 CopyMemory((PVOID)pDll->pszManifestPath, 799 pszManifestPath, 800 sizeof(WCHAR) * nManifestPathLength); 801 802 /* Now set the service, and make sure both paths are NULL-terminated */ 803 pDll->pService = pService; 804 ASSERT(pDll->hActCtx == NULL); 805 ASSERT(pDll->pszDllPath[nDllPathLength] == UNICODE_NULL); 806 ASSERT(pDll->pszManifestPath[nManifestPathLength] == UNICODE_NULL); 807 808 /* Finally, add the entry to the DLL database, while holding the lock */ 809 EnterCriticalSection(&ListLock); 810 InsertTailList(&DllList, &pDll->DllList); 811 LeaveCriticalSection(&ListLock); 812 813 /* And return it */ 814 return pDll; 815 } 816 817 VOID 818 WINAPI 819 GetServiceMainFunctions ( 820 _In_ PSVCHOST_SERVICE pService, 821 _Out_ PVOID *pServiceMain, 822 _Out_ PVOID *pPushServiceGlobals, 823 _Out_ PDWORD lpdwError 824 ) 825 { 826 DWORD dwError, cbDllLength, cbData, dwType; 827 PSVCHOST_DLL pDll; 828 ACTCTXW actCtx; 829 LPCWSTR pszDllPath; 830 HKEY hKey; 831 HANDLE hActCtx; 832 LPWSTR lpData; 833 WCHAR szDllBuffer[MAX_PATH + 2], szManifestBuffer[MAX_PATH + 2]; 834 835 /* Initialize the activation context we might need later */ 836 RtlZeroMemory(&actCtx, sizeof(actCtx)); 837 actCtx.cbSize = sizeof(actCtx); 838 839 /* We clean these up in our failure path so initialize them to NULL here */ 840 hActCtx = NULL; 841 hKey = NULL; 842 lpData = NULL; 843 *lpdwError = ERROR_SUCCESS; 844 845 /* Null terminate the string buffers */ 846 szDllBuffer[0] = UNICODE_NULL; 847 szManifestBuffer[0] = UNICODE_NULL; 848 849 /* Do we already have a DLL ready to go for this service? */ 850 pDll = pService->pDll; 851 if (pDll != NULL) goto HaveDll; 852 853 /* Nope, we're starting from scratch. Open a handle to parameters key */ 854 dwError = OpenServiceParametersKey(pService->pszServiceName, &hKey); 855 if (dwError) 856 { 857 *lpdwError = dwError; 858 ASSERT(*lpdwError != NO_ERROR); 859 goto Quickie; 860 } 861 862 /* Allocate enough space to hold a unicode path (NULL-terminated) */ 863 cbData = MAX_PATH * sizeof(WCHAR) + sizeof(UNICODE_NULL); 864 lpData = MemAlloc(0, cbData); 865 if (lpData == NULL) 866 { 867 /* No memory, bail out */ 868 *lpdwError = ERROR_NOT_ENOUGH_MEMORY; 869 goto Quickie; 870 } 871 872 /* Query the DLL path */ 873 lpData[0] = UNICODE_NULL; 874 dwError = RegQueryValueExW(hKey, 875 L"ServiceDll", 876 0, 877 &dwType, 878 (LPBYTE)lpData, 879 &cbData); 880 if (dwError != ERROR_SUCCESS) 881 { 882 *lpdwError = dwError; 883 DBG_ERR("RegQueryValueEx for the ServiceDll parameter of the %ws " 884 "service returned %u\n", 885 pService->pszServiceName, 886 dwError); 887 goto Quickie; 888 } 889 890 /* Is the registry data valid and present? */ 891 if ((dwType != REG_EXPAND_SZ) || (lpData[0] == UNICODE_NULL)) 892 { 893 /* Nope, bail out */ 894 *lpdwError = ERROR_FILE_NOT_FOUND; 895 DBG_ERR("The ServiceDll parameter for the %ws service is not of type " 896 "REG_EXPAND_SZ\n", 897 pService->pszServiceName); 898 goto Quickie; 899 } 900 901 /* Convert the expandable path into an absolute path */ 902 ExpandEnvironmentStringsW(lpData, szDllBuffer, MAX_PATH); 903 SvchostCharLowerW(szDllBuffer); 904 905 /* Check if the service has a manifest file associated with it */ 906 cbData = MAX_PATH * sizeof(WCHAR) + sizeof(UNICODE_NULL); 907 dwError = RegQueryValueExW(hKey, 908 L"ServiceManifest", 909 NULL, 910 &dwType, 911 (LPBYTE)lpData, 912 &cbData); 913 if (dwError != ERROR_SUCCESS) 914 { 915 /* Did we fail because one wasn't set? */ 916 if ((dwError != ERROR_PATH_NOT_FOUND) && 917 (dwError != ERROR_FILE_NOT_FOUND)) 918 { 919 /* We failed for some other reason, bail out */ 920 *lpdwError = dwError; 921 DBG_ERR("RegQueryValueEx for the ServiceManifest parameter of the " 922 "%ws service returned %u\n", 923 pService->pszServiceName, 924 dwError); 925 goto Quickie; 926 } 927 928 /* We have no manifest, make sure the buffer is empty */ 929 szManifestBuffer[0] = UNICODE_NULL; 930 931 /* We're done with this buffer */ 932 MemFree(lpData); 933 lpData = NULL; 934 935 /* Use the whole DLL path, since we don't have a manifest */ 936 pszDllPath = szDllBuffer; 937 } 938 else 939 { 940 /* Do we have invalid registry data? */ 941 if ((dwType != REG_EXPAND_SZ) || (*lpData == UNICODE_NULL)) 942 { 943 /* Yes, pretend there's no manifest and bail out */ 944 *lpdwError = ERROR_FILE_NOT_FOUND; 945 DBG_ERR("The ServiceManifest parameter for the %ws service is not " 946 "of type REG_EXPAND_SZ\n", 947 pService->pszServiceName); 948 goto Quickie; 949 } 950 951 /* Expand the string into our stack buffer */ 952 ExpandEnvironmentStringsW(lpData, szManifestBuffer, MAX_PATH); 953 954 /* We no longer need the heap buffer*/ 955 MemFree(lpData); 956 lpData = NULL; 957 958 /* Lowercase the manifest path */ 959 SvchostCharLowerW(szManifestBuffer); 960 961 /* Now loop over the DLL path */ 962 cbDllLength = lstrlenW(szDllBuffer); 963 while (cbDllLength) 964 { 965 /* From the end, until we find the first path separator */ 966 if (szDllBuffer[cbDllLength] == '\\' || szDllBuffer[cbDllLength] == '/') 967 { 968 /* Use just a short name (cut out the rest of the path) */ 969 pszDllPath = &szDllBuffer[cbDllLength + 1]; 970 break; 971 } 972 973 /* Try the next character */ 974 cbDllLength--; 975 } 976 } 977 978 /* See if we already have a matching DLL and manifest for this service */ 979 pDll = FindDll(szManifestBuffer, pszDllPath, pService); 980 if (pDll == NULL) 981 { 982 /* We don't have it yet -- does this DLL have a manifest? */ 983 if (szManifestBuffer[0] != UNICODE_NULL) 984 { 985 /* Create an activation context for it */ 986 actCtx.lpSource = szManifestBuffer; 987 hActCtx = CreateActCtxW(&actCtx); 988 if (hActCtx == INVALID_HANDLE_VALUE) 989 { 990 /* We've failed to create one, bail out */ 991 *lpdwError = GetLastError(); 992 DBG_ERR("CreateActCtxW(%ws) for the %ws service returned %u\n", 993 szManifestBuffer, 994 pService->pszServiceName, 995 *lpdwError); 996 goto Quickie; 997 } 998 } 999 1000 /* Add this new DLL into the database */ 1001 pDll = AddDll(szManifestBuffer, pszDllPath, pService, lpdwError); 1002 if (pDll) 1003 { 1004 /* Save the activation context and zero it so we don't release later */ 1005 pDll->hActCtx = hActCtx; 1006 hActCtx = NULL; 1007 } 1008 } 1009 1010 /* We either found, added, or failed to add, the DLL for this service */ 1011 ASSERT(!pService->pDll); 1012 pService->pDll = pDll; 1013 1014 /* In all cases, we will query the ServiceMain function, however*/ 1015 RegQueryStringA(hKey, 1016 L"ServiceMain", 1017 REG_SZ, 1018 &pService->pszServiceMain); 1019 1020 /* And now we'll check if we were able to create it earlier (or find it) */ 1021 if (!pService->pDll) 1022 { 1023 /* We were not, so there's no point in going on */ 1024 ASSERT(*lpdwError != NO_ERROR); 1025 goto Quickie; 1026 } 1027 1028 /* We do have a valid DLL, so now get the service main routine out of it */ 1029 HaveDll: 1030 *pServiceMain = GetServiceDllFunction(pDll, 1031 pService->pszServiceMain ? 1032 pService->pszServiceMain : 1033 "ServiceMain", 1034 lpdwError); 1035 1036 /* And now get the globals routine out of it (this one is optional) */ 1037 *pPushServiceGlobals = GetServiceDllFunction(pDll, 1038 "SvchostPushServiceGlobals", 1039 NULL); 1040 1041 Quickie: 1042 /* We're done, cleanup any resources we left behind */ 1043 if (hKey != NULL) RegCloseKey(hKey); 1044 if (lpData != NULL) MemFree(lpData); 1045 if ((hActCtx) && (hActCtx != INVALID_HANDLE_VALUE)) ReleaseActCtx(hActCtx); 1046 } 1047 1048 VOID 1049 WINAPI 1050 ServiceStarter ( 1051 _In_ DWORD dwNumServicesArgs, 1052 _In_ LPWSTR *lpServiceArgVectors 1053 ) 1054 { 1055 DWORD i, dwError = ERROR_FILE_NOT_FOUND; 1056 PSVCHOST_SERVICE ServiceEntry; 1057 LPCWSTR lpServiceName; 1058 LPSERVICE_MAIN_FUNCTIONW ServiceMain = NULL; 1059 PSVCHOST_INIT_GLOBALS ServiceInitGlobals = NULL; 1060 1061 /* Hold the lock while we start a service */ 1062 EnterCriticalSection(&ListLock); 1063 1064 /* Get this service's name, and loop the database */ 1065 lpServiceName = *lpServiceArgVectors; 1066 for (i = 0; i < ServiceCount; i++) 1067 { 1068 /* Try to find a match by name */ 1069 if (!lstrcmpiW(lpServiceName, ServiceArray[i].pszServiceName)) break; 1070 } 1071 1072 /* Check if we didn't find it */ 1073 if (i == ServiceCount) 1074 { 1075 /* This looks like a bogus attempt, bail out */ 1076 LeaveCriticalSection(&ListLock); 1077 return; 1078 } 1079 1080 /* We have it */ 1081 ServiceEntry = &ServiceArray[i]; 1082 1083 /* Should we breakpoint before loading this service? */ 1084 if (FDebugBreakForService(lpServiceName) != FALSE) 1085 { 1086 /* Yep -- do it */ 1087 DBG_TRACE("Attaching debugger before getting ServiceMain for %ws...\n", 1088 lpServiceName); 1089 DebugBreak(); 1090 } 1091 1092 /* Get the callbacks for this service */ 1093 GetServiceMainFunctions(&ServiceArray[i], 1094 (PVOID*)&ServiceMain, 1095 (PVOID*)&ServiceInitGlobals, 1096 &dwError); 1097 1098 /* If this service is valid and needs globals, but we don't have them... */ 1099 if ((ServiceMain != NULL) && 1100 (ServiceInitGlobals != NULL) && 1101 (g_pSvchostSharedGlobals == NULL)) 1102 { 1103 /* Go and build them for the first time! */ 1104 SvchostBuildSharedGlobals(); 1105 } 1106 1107 /* We call a service if it has a main, or if wants globals and we have them */ 1108 if (((ServiceInitGlobals != NULL) && (g_pSvchostSharedGlobals != NULL)) || 1109 ((ServiceMain != NULL) && (ServiceInitGlobals == NULL))) 1110 { 1111 /* Therefore, make sure it won't be unloaded as we drop the lock */ 1112 ServiceEntry->cServiceActiveThreadCount++; 1113 } 1114 1115 /* Drop the lock while we call into the service DLL */ 1116 LeaveCriticalSection(&ListLock); 1117 1118 /* First: does this service want globals? */ 1119 if (ServiceInitGlobals != NULL) 1120 { 1121 /* Do we have any to give? */ 1122 if (g_pSvchostSharedGlobals != NULL) 1123 { 1124 /* Yes, push them to the service */ 1125 ServiceInitGlobals(g_pSvchostSharedGlobals); 1126 1127 /* Does the service NOT have an entrypoint? */ 1128 if (ServiceMain == NULL) 1129 { 1130 /* We're going to abort loading, it serves no use */ 1131 if (lpServiceName != NULL) 1132 { 1133 AbortSvchostService(lpServiceName, dwError); 1134 } 1135 1136 /* And drop a reference count since it's not active anymore */ 1137 UnloadServiceDll(ServiceEntry); 1138 } 1139 } 1140 else if (lpServiceName != NULL) 1141 { 1142 /* We have no globals to give, so don't even bother calling it */ 1143 AbortSvchostService(lpServiceName, dwError); 1144 } 1145 } 1146 1147 /* Next up, does it have an entrypoint? */ 1148 if (ServiceMain != NULL) 1149 { 1150 /* It does, so call it */ 1151 DBG_TRACE("Calling ServiceMain for %ws...\n", lpServiceName); 1152 ServiceMain(dwNumServicesArgs, lpServiceArgVectors); 1153 1154 /* We're out of the service now, so we can drop a reference */ 1155 UnloadServiceDll(ServiceEntry); 1156 } 1157 else if (lpServiceName != NULL) 1158 { 1159 /* It has no entrypoint, so abort its launch */ 1160 AbortSvchostService(lpServiceName, dwError); 1161 } 1162 } 1163 1164 SERVICE_TABLE_ENTRYW* 1165 WINAPI 1166 BuildServiceTable ( 1167 VOID 1168 ) 1169 { 1170 SERVICE_TABLE_ENTRYW *pServiceTable; 1171 ULONG i; 1172 1173 /* Acquire the database lock while we go over the services */ 1174 EnterCriticalSection(&ListLock); 1175 1176 /* Allocate space for a NULL entry at the end as well, Windows needs this */ 1177 pServiceTable = MemAlloc(HEAP_ZERO_MEMORY, 1178 (ServiceCount + 1) * sizeof(*pServiceTable)); 1179 if (pServiceTable) 1180 { 1181 /* Go over all our registered services */ 1182 for (i = 0; i < ServiceCount; i++) 1183 { 1184 /* And set their parameters in the service table */ 1185 pServiceTable[i].lpServiceName = (LPWSTR)ServiceArray[i].pszServiceName; 1186 pServiceTable[i].lpServiceProc = ServiceStarter; 1187 DBG_TRACE("Added service table entry for %ws\n", 1188 pServiceTable[i].lpServiceName); 1189 } 1190 } 1191 1192 /* All done, can release the lock now */ 1193 LeaveCriticalSection(&ListLock); 1194 return pServiceTable; 1195 } 1196 1197 BOOLEAN 1198 WINAPI 1199 CallPerInstanceInitFunctions ( 1200 _In_ PSVCHOST_OPTIONS pOptions 1201 ) 1202 { 1203 PIMAGE_NT_HEADERS pNtHeaders; 1204 BOOLEAN bResult; 1205 1206 /* Is COM required for this host? */ 1207 if (pOptions->CoInitializeSecurityParam != 0) 1208 { 1209 /* Yes, initialize COM security and parameters */ 1210 bResult = InitializeSecurity(pOptions->CoInitializeSecurityParam, 1211 pOptions->AuthenticationLevel, 1212 pOptions->ImpersonationLevel, 1213 pOptions->AuthenticationCapabilities); 1214 if (bResult == FALSE) return FALSE; 1215 } 1216 1217 /* Do we have a custom RPC stack size? */ 1218 if (pOptions->DefaultRpcStackSize != 0) 1219 { 1220 /* Yes, go set it */ 1221 RpcMgmtSetServerStackSize(pOptions->DefaultRpcStackSize << 10); 1222 } 1223 else 1224 { 1225 /* Nope, get the NT headers from the image */ 1226 pNtHeaders = RtlImageNtHeader(NtCurrentPeb()->ImageBaseAddress); 1227 if (pNtHeaders) 1228 { 1229 /* And just use whatever the default thread stack is */ 1230 RpcMgmtSetServerStackSize(pNtHeaders->OptionalHeader.SizeOfStackCommit); 1231 } 1232 } 1233 1234 /* Is this host holding critical services? */ 1235 if (pOptions->SystemCritical != FALSE) 1236 { 1237 /* Mark the process as critical if so */ 1238 RtlSetProcessIsCritical(TRUE, NULL, TRUE); 1239 } 1240 1241 /* All done */ 1242 return TRUE; 1243 } 1244 1245 VOID 1246 __cdecl 1247 wmainCRTStartup ( 1248 VOID 1249 ) 1250 { 1251 PSVCHOST_OPTIONS lpOptions; 1252 SERVICE_TABLE_ENTRYW *pServiceTable; 1253 LPWSTR pszCmdLine; 1254 1255 /* Set a generic SEH filter and hard fail all critical errors */ 1256 SetUnhandledExceptionFilter(SvchostUnhandledExceptionFilter); 1257 SetErrorMode(SEM_FAILCRITICALERRORS); 1258 1259 /* Initialize the heap allocator */ 1260 MemInit(GetProcessHeap()); 1261 1262 /* Initialize the DLL database and lock */ 1263 InitializeListHead(&DllList); 1264 InitializeCriticalSection(&ListLock); 1265 1266 /* Get the command-line and parse it to get the service group */ 1267 pszCmdLine = GetCommandLineW(); 1268 lpOptions = BuildCommandOptions(pszCmdLine); 1269 if (lpOptions == NULL) 1270 { 1271 /* Without a valid command-line, there's nothing for us to do */ 1272 DBG_TRACE("Calling ExitProcess for %ws\n", pszCmdLine); 1273 ExitProcess(0); 1274 } 1275 1276 /* Now use the service group information to lookup all the services */ 1277 BuildServiceArray(lpOptions); 1278 1279 /* Convert the list of services in this group to the SCM format */ 1280 pServiceTable = BuildServiceTable(); 1281 if (pServiceTable == NULL) 1282 { 1283 /* This is critical, bail out without it */ 1284 MemFree(lpOptions); 1285 return; 1286 } 1287 1288 /* Initialize COM and RPC as needed for this service group */ 1289 if (CallPerInstanceInitFunctions(lpOptions) == FALSE) 1290 { 1291 /* Exit with a special code indicating COM/RPC setup has failed */ 1292 DBG_ERR("%s", "CallPerInstanceInitFunctions failed -- exiting!\n"); 1293 ExitProcess(1); 1294 } 1295 1296 /* We're ready to go -- free the options buffer */ 1297 MemFree(lpOptions); 1298 1299 /* And call into ADVAPI32 to get our services going */ 1300 StartServiceCtrlDispatcherW(pServiceTable); 1301 } 1302