xref: /reactos/base/services/umpnpmgr/install.c (revision 3c5a56ed)
1 /*
2  *  ReactOS kernel
3  *  Copyright (C) 2005 ReactOS Team
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License along
16  *  with this program; if not, write to the Free Software Foundation, Inc.,
17  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 /*
20  * COPYRIGHT:        See COPYING in the top level directory
21  * PROJECT:          ReactOS kernel
22  * FILE:             base/services/umpnpmgr/install.c
23  * PURPOSE:          Device installer
24  * PROGRAMMER:       Eric Kohl (eric.kohl@reactos.org)
25  *                   Hervé Poussineau (hpoussin@reactos.org)
26  *                   Colin Finck (colin@reactos.org)
27  */
28 
29 /* INCLUDES *****************************************************************/
30 
31 #include "precomp.h"
32 
33 #define NDEBUG
34 #include <debug.h>
35 
36 
37 /* GLOBALS ******************************************************************/
38 
39 HANDLE hUserToken = NULL;
40 HANDLE hInstallEvent = NULL;
41 HANDLE hNoPendingInstalls = NULL;
42 
43 /* Device-install event list */
44 HANDLE hDeviceInstallListMutex;
45 LIST_ENTRY DeviceInstallListHead;
46 HANDLE hDeviceInstallListNotEmpty;
47 
48 DWORD
49 CreatePnpInstallEventSecurity(
50     _Out_ PSECURITY_DESCRIPTOR *EventSd);
51 
52 /* FUNCTIONS *****************************************************************/
53 
54 static BOOL
55 InstallDevice(PCWSTR DeviceInstance, BOOL ShowWizard)
56 {
57     BOOL DeviceInstalled = FALSE;
58     DWORD BytesWritten;
59     DWORD Value;
60     DWORD ErrCode;
61     HANDLE hInstallEvent;
62     HANDLE hPipe = INVALID_HANDLE_VALUE;
63     LPVOID Environment = NULL;
64     PROCESS_INFORMATION ProcessInfo;
65     STARTUPINFOW StartupInfo;
66     UUID RandomUuid;
67     HKEY DeviceKey;
68     SECURITY_ATTRIBUTES EventAttrs;
69     PSECURITY_DESCRIPTOR EventSd;
70 
71     /* The following lengths are constant (see below), they cannot overflow */
72     WCHAR CommandLine[116];
73     WCHAR InstallEventName[73];
74     WCHAR PipeName[74];
75     WCHAR UuidString[39];
76 
77     DPRINT("InstallDevice(%S, %d)\n", DeviceInstance, ShowWizard);
78 
79     ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
80 
81     if (RegOpenKeyExW(hEnumKey,
82                       DeviceInstance,
83                       0,
84                       KEY_QUERY_VALUE,
85                       &DeviceKey) == ERROR_SUCCESS)
86     {
87         if (RegQueryValueExW(DeviceKey,
88                              L"Class",
89                              NULL,
90                              NULL,
91                              NULL,
92                              NULL) == ERROR_SUCCESS)
93         {
94             DPRINT("No need to install: %S\n", DeviceInstance);
95             RegCloseKey(DeviceKey);
96             return TRUE;
97         }
98 
99         BytesWritten = sizeof(DWORD);
100         if (RegQueryValueExW(DeviceKey,
101                              L"ConfigFlags",
102                              NULL,
103                              NULL,
104                              (PBYTE)&Value,
105                              &BytesWritten) == ERROR_SUCCESS)
106         {
107             if (Value & CONFIGFLAG_FAILEDINSTALL)
108             {
109                 DPRINT("No need to install: %S\n", DeviceInstance);
110                 RegCloseKey(DeviceKey);
111                 return TRUE;
112             }
113         }
114 
115         RegCloseKey(DeviceKey);
116     }
117 
118     DPRINT1("Installing: %S\n", DeviceInstance);
119 
120     /* Create a random UUID for the named pipe & event*/
121     UuidCreate(&RandomUuid);
122     swprintf(UuidString, L"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
123         RandomUuid.Data1, RandomUuid.Data2, RandomUuid.Data3,
124         RandomUuid.Data4[0], RandomUuid.Data4[1], RandomUuid.Data4[2],
125         RandomUuid.Data4[3], RandomUuid.Data4[4], RandomUuid.Data4[5],
126         RandomUuid.Data4[6], RandomUuid.Data4[7]);
127 
128     ErrCode = CreatePnpInstallEventSecurity(&EventSd);
129     if (ErrCode != ERROR_SUCCESS)
130     {
131         DPRINT1("CreatePnpInstallEventSecurity failed with error %u\n", GetLastError());
132         return FALSE;
133     }
134 
135     /* Set up the security attributes for the event */
136     EventAttrs.nLength = sizeof(SECURITY_ATTRIBUTES);
137     EventAttrs.lpSecurityDescriptor = EventSd;
138     EventAttrs.bInheritHandle = FALSE;
139 
140     /* Create the event */
141     wcscpy(InstallEventName, L"Global\\PNP_Device_Install_Event_0.");
142     wcscat(InstallEventName, UuidString);
143     hInstallEvent = CreateEventW(&EventAttrs, TRUE, FALSE, InstallEventName);
144     HeapFree(GetProcessHeap(), 0, EventSd);
145     if (!hInstallEvent)
146     {
147         DPRINT1("CreateEventW('%ls') failed with error %lu\n", InstallEventName, GetLastError());
148         goto cleanup;
149     }
150 
151     /* Create the named pipe */
152     wcscpy(PipeName, L"\\\\.\\pipe\\PNP_Device_Install_Pipe_0.");
153     wcscat(PipeName, UuidString);
154     hPipe = CreateNamedPipeW(PipeName, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 1, 512, 512, 0, NULL);
155     if (hPipe == INVALID_HANDLE_VALUE)
156     {
157         DPRINT1("CreateNamedPipeW failed with error %u\n", GetLastError());
158         goto cleanup;
159     }
160 
161     /* Launch rundll32 to call ClientSideInstallW */
162     wcscpy(CommandLine, L"rundll32.exe newdev.dll,ClientSideInstall ");
163     wcscat(CommandLine, PipeName);
164 
165     ZeroMemory(&StartupInfo, sizeof(StartupInfo));
166     StartupInfo.cb = sizeof(StartupInfo);
167 
168     if (hUserToken)
169     {
170         /* newdev has to run under the environment of the current user */
171         if (!CreateEnvironmentBlock(&Environment, hUserToken, FALSE))
172         {
173             DPRINT1("CreateEnvironmentBlock failed with error %d\n", GetLastError());
174             goto cleanup;
175         }
176 
177         if (!CreateProcessAsUserW(hUserToken, NULL, CommandLine, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, Environment, NULL, &StartupInfo, &ProcessInfo))
178         {
179             DPRINT1("CreateProcessAsUserW failed with error %u\n", GetLastError());
180             goto cleanup;
181         }
182     }
183     else
184     {
185         /* FIXME: This is probably not correct, I guess newdev should never be run with SYSTEM privileges.
186 
187            Still, we currently do that in 2nd stage setup and probably Console mode as well, so allow it here.
188            (ShowWizard is only set to FALSE for these two modes) */
189         ASSERT(!ShowWizard);
190 
191         if (!CreateProcessW(NULL, CommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &StartupInfo, &ProcessInfo))
192         {
193             DPRINT1("CreateProcessW failed with error %u\n", GetLastError());
194             goto cleanup;
195         }
196     }
197 
198     /* Wait for the function to connect to our pipe */
199     if (!ConnectNamedPipe(hPipe, NULL))
200     {
201         if (GetLastError() != ERROR_PIPE_CONNECTED)
202         {
203             DPRINT1("ConnectNamedPipe failed with error %u\n", GetLastError());
204             goto cleanup;
205         }
206     }
207 
208     /* Pass the data. The following output is partly compatible to Windows XP SP2 (researched using a modified newdev.dll to log this stuff) */
209     Value = sizeof(InstallEventName);
210     WriteFile(hPipe, &Value, sizeof(Value), &BytesWritten, NULL);
211     WriteFile(hPipe, InstallEventName, Value, &BytesWritten, NULL);
212 
213     /* I couldn't figure out what the following value means under WinXP. It's usually 0 in my tests, but was also 5 once.
214        Therefore the following line is entirely ReactOS-specific. We use the value here to pass the ShowWizard variable. */
215     WriteFile(hPipe, &ShowWizard, sizeof(ShowWizard), &BytesWritten, NULL);
216 
217     Value = (wcslen(DeviceInstance) + 1) * sizeof(WCHAR);
218     WriteFile(hPipe, &Value, sizeof(Value), &BytesWritten, NULL);
219     WriteFile(hPipe, DeviceInstance, Value, &BytesWritten, NULL);
220 
221     /* Wait for newdev.dll to finish processing */
222     WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
223 
224     /* If the event got signalled, this is success */
225     DeviceInstalled = WaitForSingleObject(hInstallEvent, 0) == WAIT_OBJECT_0;
226 
227 cleanup:
228     if (hInstallEvent)
229         CloseHandle(hInstallEvent);
230 
231     if (hPipe != INVALID_HANDLE_VALUE)
232         CloseHandle(hPipe);
233 
234     if (Environment)
235         DestroyEnvironmentBlock(Environment);
236 
237     if (ProcessInfo.hProcess)
238         CloseHandle(ProcessInfo.hProcess);
239 
240     if (ProcessInfo.hThread)
241         CloseHandle(ProcessInfo.hThread);
242 
243     if (!DeviceInstalled)
244     {
245         DPRINT1("InstallDevice failed for DeviceInstance '%ws'\n", DeviceInstance);
246     }
247 
248     return DeviceInstalled;
249 }
250 
251 
252 static LONG
253 ReadRegSzKey(
254     IN HKEY hKey,
255     IN LPCWSTR pszKey,
256     OUT LPWSTR* pValue)
257 {
258     LONG rc;
259     DWORD dwType;
260     DWORD cbData = 0;
261     LPWSTR Value;
262 
263     if (!pValue)
264         return ERROR_INVALID_PARAMETER;
265 
266     *pValue = NULL;
267     rc = RegQueryValueExW(hKey, pszKey, NULL, &dwType, NULL, &cbData);
268     if (rc != ERROR_SUCCESS)
269         return rc;
270     if (dwType != REG_SZ)
271         return ERROR_FILE_NOT_FOUND;
272     Value = HeapAlloc(GetProcessHeap(), 0, cbData + sizeof(WCHAR));
273     if (!Value)
274         return ERROR_NOT_ENOUGH_MEMORY;
275     rc = RegQueryValueExW(hKey, pszKey, NULL, NULL, (LPBYTE)Value, &cbData);
276     if (rc != ERROR_SUCCESS)
277     {
278         HeapFree(GetProcessHeap(), 0, Value);
279         return rc;
280     }
281     /* NULL-terminate the string */
282     Value[cbData / sizeof(WCHAR)] = '\0';
283 
284     *pValue = Value;
285     return ERROR_SUCCESS;
286 }
287 
288 
289 BOOL
290 SetupIsActive(VOID)
291 {
292     HKEY hKey = NULL;
293     DWORD regType, active, size;
294     LONG rc;
295     BOOL ret = FALSE;
296 
297     rc = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\Setup", 0, KEY_QUERY_VALUE, &hKey);
298     if (rc != ERROR_SUCCESS)
299         goto cleanup;
300 
301     size = sizeof(DWORD);
302     rc = RegQueryValueExW(hKey, L"SystemSetupInProgress", NULL, &regType, (LPBYTE)&active, &size);
303     if (rc != ERROR_SUCCESS)
304         goto cleanup;
305     if (regType != REG_DWORD || size != sizeof(DWORD))
306         goto cleanup;
307 
308     ret = (active != 0);
309 
310 cleanup:
311     if (hKey != NULL)
312         RegCloseKey(hKey);
313 
314     DPRINT("System setup in progress? %S\n", ret ? L"YES" : L"NO");
315 
316     return ret;
317 }
318 
319 
320 /**
321  * @brief
322  * Creates a security descriptor for the PnP event
323  * installation.
324  *
325  * @param[out] EventSd
326  * A pointer to an allocated security descriptor
327  * for the event.
328  *
329  * @return
330  * ERROR_SUCCESS is returned if the function has
331  * successfully created the descriptor, otherwise
332  * a Win32 error code is returned.
333  *
334  * @remarks
335  * Only admins and local system have full power
336  * over this event as privileged users can install
337  * devices on a system.
338  */
339 DWORD
340 CreatePnpInstallEventSecurity(
341     _Out_ PSECURITY_DESCRIPTOR *EventSd)
342 {
343     DWORD ErrCode;
344     PACL Dacl;
345     ULONG DaclSize;
346     SECURITY_DESCRIPTOR AbsoluteSd;
347     ULONG Size = 0;
348     PSECURITY_DESCRIPTOR RelativeSd = NULL;
349     PSID SystemSid = NULL, AdminsSid = NULL;
350     static SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
351 
352     if (!AllocateAndInitializeSid(&NtAuthority,
353                                   1,
354                                   SECURITY_LOCAL_SYSTEM_RID,
355                                   0, 0, 0, 0, 0, 0, 0,
356                                   &SystemSid))
357     {
358         return GetLastError();
359     }
360 
361     if (!AllocateAndInitializeSid(&NtAuthority,
362                                   2,
363                                   SECURITY_BUILTIN_DOMAIN_RID,
364                                   DOMAIN_ALIAS_RID_ADMINS,
365                                   0, 0, 0, 0, 0, 0,
366                                   &AdminsSid))
367     {
368         ErrCode = GetLastError();
369         goto Quit;
370     }
371 
372     DaclSize = sizeof(ACL) +
373                sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(SystemSid) +
374                sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(AdminsSid);
375 
376     Dacl = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, DaclSize);
377     if (!Dacl)
378     {
379         ErrCode = ERROR_OUTOFMEMORY;
380         goto Quit;
381     }
382 
383     if (!InitializeAcl(Dacl, DaclSize, ACL_REVISION))
384     {
385         ErrCode = GetLastError();
386         goto Quit;
387     }
388 
389     if (!AddAccessAllowedAce(Dacl,
390                              ACL_REVISION,
391                              EVENT_ALL_ACCESS,
392                              SystemSid))
393     {
394         ErrCode = GetLastError();
395         goto Quit;
396     }
397 
398     if (!AddAccessAllowedAce(Dacl,
399                              ACL_REVISION,
400                              EVENT_ALL_ACCESS,
401                              AdminsSid))
402     {
403         ErrCode = GetLastError();
404         goto Quit;
405     }
406 
407     if (!InitializeSecurityDescriptor(&AbsoluteSd, SECURITY_DESCRIPTOR_REVISION))
408     {
409         ErrCode = GetLastError();
410         goto Quit;
411     }
412 
413     if (!SetSecurityDescriptorDacl(&AbsoluteSd, TRUE, Dacl, FALSE))
414     {
415         ErrCode = GetLastError();
416         goto Quit;
417     }
418 
419     if (!SetSecurityDescriptorOwner(&AbsoluteSd, SystemSid, FALSE))
420     {
421         ErrCode = GetLastError();
422         goto Quit;
423     }
424 
425     if (!SetSecurityDescriptorGroup(&AbsoluteSd, AdminsSid, FALSE))
426     {
427         ErrCode = GetLastError();
428         goto Quit;
429     }
430 
431     if (!MakeSelfRelativeSD(&AbsoluteSd, NULL, &Size) && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
432     {
433         RelativeSd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Size);
434         if (RelativeSd == NULL)
435         {
436             ErrCode = ERROR_OUTOFMEMORY;
437             goto Quit;
438         }
439 
440         if (!MakeSelfRelativeSD(&AbsoluteSd, RelativeSd, &Size))
441         {
442             ErrCode = GetLastError();
443             goto Quit;
444         }
445     }
446 
447     *EventSd = RelativeSd;
448     ErrCode = ERROR_SUCCESS;
449 
450 Quit:
451     if (SystemSid)
452     {
453         FreeSid(SystemSid);
454     }
455 
456     if (AdminsSid)
457     {
458         FreeSid(AdminsSid);
459     }
460 
461     if (Dacl)
462     {
463         HeapFree(GetProcessHeap(), 0, Dacl);
464     }
465 
466     if (ErrCode != ERROR_SUCCESS)
467     {
468         if (RelativeSd)
469         {
470             HeapFree(GetProcessHeap(), 0, RelativeSd);
471         }
472     }
473 
474     return ErrCode;
475 }
476 
477 
478 static BOOL
479 IsConsoleBoot(VOID)
480 {
481     HKEY ControlKey = NULL;
482     LPWSTR SystemStartOptions = NULL;
483     LPWSTR CurrentOption, NextOption; /* Pointers into SystemStartOptions */
484     BOOL ConsoleBoot = FALSE;
485     LONG rc;
486 
487     rc = RegOpenKeyExW(
488         HKEY_LOCAL_MACHINE,
489         L"SYSTEM\\CurrentControlSet\\Control",
490         0,
491         KEY_QUERY_VALUE,
492         &ControlKey);
493 
494     rc = ReadRegSzKey(ControlKey, L"SystemStartOptions", &SystemStartOptions);
495     if (rc != ERROR_SUCCESS)
496         goto cleanup;
497 
498     /* Check for CONSOLE switch in SystemStartOptions */
499     CurrentOption = SystemStartOptions;
500     while (CurrentOption)
501     {
502         NextOption = wcschr(CurrentOption, L' ');
503         if (NextOption)
504             *NextOption = L'\0';
505         if (_wcsicmp(CurrentOption, L"CONSOLE") == 0)
506         {
507             DPRINT("Found %S. Switching to console boot\n", CurrentOption);
508             ConsoleBoot = TRUE;
509             goto cleanup;
510         }
511         CurrentOption = NextOption ? NextOption + 1 : NULL;
512     }
513 
514 cleanup:
515     if (ControlKey != NULL)
516         RegCloseKey(ControlKey);
517     HeapFree(GetProcessHeap(), 0, SystemStartOptions);
518     return ConsoleBoot;
519 }
520 
521 
522 FORCEINLINE
523 BOOL
524 IsUISuppressionAllowed(VOID)
525 {
526     /* Display the newdev.dll wizard UI only if it's allowed */
527     return (g_IsUISuppressed || GetSuppressNewUIValue());
528 }
529 
530 
531 /* Loop to install all queued devices installations */
532 DWORD
533 WINAPI
534 DeviceInstallThread(LPVOID lpParameter)
535 {
536     PLIST_ENTRY ListEntry;
537     DeviceInstallParams* Params;
538 
539     UNREFERENCED_PARAMETER(lpParameter);
540 
541     // Step 1: install all drivers which were configured during the boot
542 
543     DPRINT("Step 1: Installing devices configured during the boot\n");
544 
545     PWSTR deviceList;
546 
547     while (TRUE)
548     {
549         ULONG devListSize;
550         DWORD status = PNP_GetDeviceListSize(NULL, NULL, &devListSize, 0);
551         if (status != CR_SUCCESS)
552         {
553             goto Step2;
554         }
555 
556         deviceList = HeapAlloc(GetProcessHeap(), 0, devListSize * sizeof(WCHAR));
557         if (!deviceList)
558         {
559             goto Step2;
560         }
561 
562         status = PNP_GetDeviceList(NULL, NULL, deviceList, &devListSize, 0);
563         if (status == CR_BUFFER_SMALL)
564         {
565             HeapFree(GetProcessHeap(), 0, deviceList);
566         }
567         else if (status != CR_SUCCESS)
568         {
569             DPRINT1("PNP_GetDeviceList failed with error %u\n", status);
570             goto Cleanup;
571         }
572         else // status == CR_SUCCESS
573         {
574             break;
575         }
576     }
577 
578     for (PWSTR currentDev = deviceList;
579          currentDev[0] != UNICODE_NULL;
580          currentDev += lstrlenW(currentDev) + 1)
581     {
582         InstallDevice(currentDev, FALSE);
583     }
584 
585 Cleanup:
586     HeapFree(GetProcessHeap(), 0, deviceList);
587 
588     // Step 2: start the wait-loop for newly added devices
589 Step2:
590 
591     DPRINT("Step 2: Starting the wait-loop\n");
592 
593     WaitForSingleObject(hInstallEvent, INFINITE);
594 
595     BOOL showWizard = !SetupIsActive() && !IsConsoleBoot();
596 
597     while (TRUE)
598     {
599         /* Dequeue the next oldest device-install event */
600         WaitForSingleObject(hDeviceInstallListMutex, INFINITE);
601         ListEntry = (IsListEmpty(&DeviceInstallListHead)
602                         ? NULL : RemoveHeadList(&DeviceInstallListHead));
603         ReleaseMutex(hDeviceInstallListMutex);
604 
605         if (ListEntry == NULL)
606         {
607             SetEvent(hNoPendingInstalls);
608             WaitForSingleObject(hDeviceInstallListNotEmpty, INFINITE);
609         }
610         else
611         {
612             ResetEvent(hNoPendingInstalls);
613             Params = CONTAINING_RECORD(ListEntry, DeviceInstallParams, ListEntry);
614             InstallDevice(Params->DeviceIds, showWizard && !IsUISuppressionAllowed());
615             HeapFree(GetProcessHeap(), 0, Params);
616         }
617     }
618 
619     return 0;
620 }
621