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
SvchostUnhandledExceptionFilter(_In_ struct _EXCEPTION_POINTERS * ExceptionInfo)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
DummySvchostCtrlHandler(_In_ DWORD dwControl)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
BuildCommandOptions(_In_ LPWSTR pszCmdLine)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
ReadPerInstanceRegistryParameters(_In_ HKEY hKey,_In_ PSVCHOST_OPTIONS pOptions)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
BuildServiceArray(_In_ PSVCHOST_OPTIONS lpOptions)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
FDebugBreakForService(_In_ LPCWSTR ServiceName)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
OpenServiceParametersKey(_In_ LPCWSTR lpSubKey,_Out_ PHKEY phKey)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
UnloadServiceDll(_In_ PSVCHOST_SERVICE pService)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
SvchostStopCallback(_In_ PVOID Context,_In_ BOOLEAN TimerOrWaitFired)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
SvcRegisterStopCallback(_Out_ PHANDLE phNewWaitObject,_In_ PCWSTR ServiceName,_In_ HANDLE hObject,_In_ PSVCHOST_STOP_CALLBACK pfnStopCallback,_In_ PVOID pContext,_In_ ULONG dwFlags)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
AbortSvchostService(_In_ LPCWSTR lpServiceName,_In_ DWORD dwExitCode)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
GetServiceDllFunction(_In_ PSVCHOST_DLL pDll,_In_ LPCSTR lpProcName,_Out_ PDWORD lpdwError)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
FindDll(_In_ LPCWSTR pszManifestPath,_In_ LPCWSTR pszDllPath,_In_ PSVCHOST_SERVICE pService)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
AddDll(_In_ LPCWSTR pszManifestPath,_In_ LPCWSTR pszDllPath,_In_ PSVCHOST_SERVICE pService,_Out_ PDWORD lpdwError)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
GetServiceMainFunctions(_In_ PSVCHOST_SERVICE pService,_Out_ PVOID * pServiceMain,_Out_ PVOID * pPushServiceGlobals,_Out_ PDWORD lpdwError)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
ServiceStarter(_In_ DWORD dwNumServicesArgs,_In_ LPWSTR * lpServiceArgVectors)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
BuildServiceTable(VOID)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
CallPerInstanceInitFunctions(_In_ PSVCHOST_OPTIONS pOptions)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
wmainCRTStartup(VOID)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