xref: /reactos/sdk/lib/smlib/smutils.c (revision 09dde2cf)
1 /*
2  * PROJECT:     ReactOS SM Helper Library
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Utility functions built around the client SM API
5  * COPYRIGHT:   Copyright 2005 Emanuele Aliberti <ea@reactos.com>
6  *              Copyright 2022 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
7  */
8 
9 #include "precomp.h"
10 
11 #define WIN32_NO_STATUS
12 #define _INC_WINDOWS
13 #define COM_NO_WINDOWS_H
14 #include <windef.h>
15 #include <winreg.h>
16 
17 #define NTOS_MODE_USER
18 #include <ndk/cmfuncs.h>
19 
20 #include <sm/helper.h>
21 
22 #define NDEBUG
23 #include <debug.h>
24 
25 #if DBG
26 BOOLEAN SmpDebug = TRUE;
27 #else
28 BOOLEAN SmpDebug = FALSE;
29 #endif
30 
31 /**
32  * @brief
33  * This function is used to make the SM start an external process
34  * under an already-loaded environment subsystem server.
35  *
36  * @param[in]   SmApiPort
37  * Port handle returned by SmConnectToSm().
38  *
39  * @param[in]   Program
40  * Fully qualified NT name of the executable to load.
41  *
42  * @return
43  * Success status as handed by the SM reply; otherwise a failure
44  * status code.
45  *
46  * @remark
47  * Adapted from SMSS' SmpExecuteInitialCommand() and SmpExecuteImage().
48  **/
49 NTSTATUS
50 NTAPI
51 SmExecuteProgram(
52     _In_ HANDLE SmApiPort,
53     _In_ PUNICODE_STRING Program /*,
54     _Out_opt_ PRTL_USER_PROCESS_INFORMATION ProcessInformation*/)
55 {
56     // ULONG MuSessionId;
57 
58     NTSTATUS Status;
59     RTL_USER_PROCESS_INFORMATION ProcessInfo;
60     PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
61 
62     PUNICODE_STRING FileName = Program; // FIXME!
63     PUNICODE_STRING Directory = NULL; // FIXME!
64     PUNICODE_STRING CommandLine = NULL; // FIXME!
65 
66     PVOID SmpDefaultEnvironment = NtCurrentPeb()->ProcessParameters->Environment;
67     // UNICODE_STRING SmpDefaultLibPath;
68 
69     DPRINT("SMLIB: %s(%p, '%wZ') called\n",
70            __FUNCTION__, SmApiPort, Program);
71 
72     /* Parameters validation */
73     if (!SmApiPort)
74         return STATUS_INVALID_PARAMETER_1;
75     if (!Program)
76         return STATUS_INVALID_PARAMETER_2;
77 
78     /* Create parameters for the target process, using the current environment */
79     Status = RtlCreateProcessParameters(&ProcessParameters,
80                                         FileName,
81                                         /* SmpDefaultLibPath.Length ?
82                                         &SmpDefaultLibPath : */ NULL,
83                                         Directory,
84                                         CommandLine,
85                                         SmpDefaultEnvironment,
86                                         NULL,
87                                         NULL,
88                                         NULL,
89                                         0);
90     if (!NT_SUCCESS(Status))
91     {
92         DPRINT1("SMLIB: RtlCreateProcessParameters failed for %wZ - Status == %lx\n",
93                 FileName, Status);
94         return Status;
95     }
96 
97     /* Set the size field as required */
98     ProcessInfo.Size = sizeof(ProcessInfo);
99 
100     /* Otherwise inherit the flag that was passed to SMSS itself */
101     ProcessParameters->DebugFlags = SmpDebug;
102 
103     /* And always force NX for anything that SMSS launches */
104     ProcessParameters->Flags |= RTL_USER_PROCESS_PARAMETERS_NX;
105 
106     /* Now create the process in suspended state */
107     Status = RtlCreateUserProcess(FileName,
108                                   OBJ_CASE_INSENSITIVE,
109                                   ProcessParameters,
110                                   NULL,
111                                   NULL,
112                                   NULL,
113                                   FALSE,
114                                   NULL,
115                                   NULL,
116                                   &ProcessInfo);
117     RtlDestroyProcessParameters(ProcessParameters);
118     if (!NT_SUCCESS(Status))
119     {
120         /* If we couldn't create it, fail back to the caller */
121         DPRINT1("SMLIB: Failed load of %wZ - Status  == %lx\n",
122                 FileName, Status);
123         return Status;
124     }
125 
126     // /* Now duplicate the handle to this process */
127     // Status = NtDuplicateObject(NtCurrentProcess(),
128                                // ProcessInfo.ProcessHandle,
129                                // NtCurrentProcess(),
130                                // InitialCommandProcess,
131                                // PROCESS_ALL_ACCESS,
132                                // 0,
133                                // 0);
134     // if (!NT_SUCCESS(Status))
135     // {
136         // /* Kill it utterly if duplication failed */
137         // DPRINT1("SMLIB: DupObject Failed. Status == %lx\n", Status);
138         // NtTerminateProcess(ProcessInfo.ProcessHandle, Status);
139         // NtResumeThread(ProcessInfo.ThreadHandle, NULL);
140         // NtClose(ProcessInfo.ThreadHandle);
141         // NtClose(ProcessInfo.ProcessHandle);
142         // return Status;
143     // }
144 
145     /* Call SM and wait for a reply */
146     Status = SmExecPgm(SmApiPort, &ProcessInfo, TRUE);
147 
148     NtClose(ProcessInfo.ThreadHandle);
149     NtClose(ProcessInfo.ProcessHandle);
150 
151     // if (ProcessInformation)
152         // *ProcessInformation = ProcessInfo;
153 
154     DPRINT("SMLIB: %s returned (Status=0x%08lx)\n", __FUNCTION__, Status);
155     return Status;
156 }
157 
158 /**
159  * @brief
160  * Reads from the registry key
161  * \Registry\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems
162  * the value specified by Name.
163  *
164  * @param[in]   Name
165  * Name of the program to run, that is a value's name in
166  * the SM registry key "SubSystems".
167  *
168  * @param[out]   Data
169  * What the registry gave back for Name.
170  *
171  * @param[in,out]   DataLength
172  * How much Data the registry returns.
173  *
174  * @param[out]   DataType
175  * Optional pointer to a variable that receives the type of data
176  * stored in the specified value.
177  *
178  * @param[in]   Environment
179  * Optional environment to be used to possibly expand Data before
180  * returning it back; if set to NULL, no expansion will be performed.
181  *
182  * @return
183  * Success status if the specified subsystem existed and its information
184  * has been retrieved successfully; otherwise a failure status code.
185  **/
186 NTSTATUS
187 NTAPI
188 SmLookupSubsystem(
189     _In_ PWSTR Name,
190     _Out_ PWSTR Data,
191     _Inout_ PULONG DataLength,
192     _Out_opt_ PULONG DataType,
193     _In_opt_ PVOID Environment)
194 {
195     NTSTATUS Status;
196     UNICODE_STRING usKeyName;
197     OBJECT_ATTRIBUTES Oa;
198     HANDLE hKey = NULL;
199 
200     UNICODE_STRING usValueName;
201     PWCHAR KeyValueInformation = NULL;
202     ULONG  KeyValueInformationLength = 1024;
203     ULONG  ResultLength = 0;
204     PKEY_VALUE_PARTIAL_INFORMATION kvpi;
205 
206     DPRINT("SMLIB: %s(Name='%S') called\n", __FUNCTION__, Name);
207 
208     /*
209      * Prepare the key name to scan and
210      * related object attributes.
211      */
212     RtlInitUnicodeString(&usKeyName,
213         L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\SubSystems");
214 
215     InitializeObjectAttributes(&Oa,
216                                &usKeyName,
217                                OBJ_CASE_INSENSITIVE,
218                                NULL,
219                                NULL);
220     /*
221      * Open the key. This MUST NOT fail, if the
222      * request is for a legitimate subsystem.
223      */
224     Status = NtOpenKey(&hKey,
225                        MAXIMUM_ALLOWED,
226                        &Oa);
227     if (!NT_SUCCESS(Status))
228     {
229         DPRINT1("%s: NtOpenKey failed (Status=0x%08lx)\n", __FUNCTION__, Status);
230         return Status;
231     }
232 
233     KeyValueInformation = RtlAllocateHeap(RtlGetProcessHeap(),
234                                           0,
235                                           KeyValueInformationLength);
236     if (!KeyValueInformation)
237     {
238         NtClose(hKey);
239         return STATUS_NO_MEMORY;
240     }
241 
242     kvpi = (PKEY_VALUE_PARTIAL_INFORMATION)KeyValueInformation;
243     RtlInitUnicodeString(&usValueName, Name);
244     Status = NtQueryValueKey(hKey,
245                              &usValueName,
246                              KeyValuePartialInformation,
247                              KeyValueInformation,
248                              KeyValueInformationLength,
249                              &ResultLength);
250     if (!NT_SUCCESS(Status))
251     {
252         DPRINT1("%s: NtQueryValueKey failed (Status=0x%08lx)\n", __FUNCTION__, Status);
253 
254         RtlFreeHeap(RtlGetProcessHeap(), 0, KeyValueInformation);
255         NtClose(hKey);
256 
257         return Status;
258     }
259 
260     DPRINT("kvpi.TitleIndex = %lu\n", kvpi->TitleIndex);
261     DPRINT("kvpi.Type       = %lu\n", kvpi->Type);
262     DPRINT("kvpi.DataLength = %lu\n", kvpi->DataLength);
263 
264     if (Data && DataLength)
265     {
266         if (DataType)
267             *DataType = kvpi->Type;
268 
269         if (Environment && (kvpi->Type == REG_EXPAND_SZ))
270         {
271             UNICODE_STRING Source;
272             UNICODE_STRING Destination;
273             PWCHAR DestinationBuffer = NULL;
274             ULONG  Length;
275 
276             DPRINT("SMLIB: %s: value will be expanded\n", __FUNCTION__);
277 
278             Length = 2 * KeyValueInformationLength;
279             DestinationBuffer = RtlAllocateHeap(RtlGetProcessHeap(),
280                                                 0,
281                                                 Length);
282             if (!DestinationBuffer)
283             {
284                 Status = STATUS_NO_MEMORY;
285             }
286             else
287             {
288                 Source.Length = (USHORT)kvpi->DataLength;
289                 Source.MaximumLength = Source.Length;
290                 Source.Buffer = (PWCHAR)&kvpi->Data;
291 
292                 RtlInitEmptyUnicodeString(&Destination,
293                                           DestinationBuffer,
294                                           (USHORT)Length);
295 
296                 Length = 0;
297                 Status = RtlExpandEnvironmentStrings_U(Environment,
298                                                        &Source,
299                                                        &Destination,
300                                                        &Length);
301                 if (NT_SUCCESS(Status))
302                 {
303                     *DataLength = min(*DataLength, Destination.Length);
304                     RtlCopyMemory(Data, Destination.Buffer, *DataLength);
305                 }
306                 RtlFreeHeap(RtlGetProcessHeap(), 0, DestinationBuffer);
307             }
308         }
309         else
310         {
311             DPRINT("SMLIB: %s: value won't be expanded\n", __FUNCTION__);
312             *DataLength = min(*DataLength, kvpi->DataLength);
313             RtlCopyMemory(Data, &kvpi->Data, *DataLength);
314         }
315     }
316     else
317     {
318         DPRINT1("SMLIB: %s: Data or DataLength is NULL!\n", __FUNCTION__);
319         Status = STATUS_INVALID_PARAMETER;
320     }
321 
322     RtlFreeHeap(RtlGetProcessHeap(), 0, KeyValueInformation);
323     NtClose(hKey);
324 
325     return Status;
326 }
327 
328 /**
329  * @brief
330  * Retrieves information about subsystems registered with the SM.
331  *
332  * @param[in]   SmApiPort
333  * Port handle returned by SmConnectToSm().
334  *
335  * @param[in]   SmInformationClass
336  * An SM information class ID:
337  * - SmBasicInformation:
338  *   The number and list of registered subsystems. Data is returned
339  *   in a SM_BASIC_INFORMATION structure.
340  * - SmSubSystemInformation:
341  *   Information about a particular registered subsystem. Data is
342  *   returned in a SM_SUBSYSTEM_INFORMATION structure.
343  *
344  * @param[in,out]   Data
345  * Pointer to storage for the information to request. Either a
346  * SM_BASIC_INFORMATION or a SM_SUBSYSTEM_INFORMATION, depending
347  * on the information class.
348  *
349  * @param[in]   DataLength
350  * Length in bytes of the Data buffer; it must be set and must
351  * match the SmInformationClass information size.
352  *
353  * @param[in,out]   ReturnedDataLength
354  * Optional pointer to storage to receive the size of the returned data.
355  *
356  * @return
357  * STATUS_SUCCESS when the information asked for has been retrieved.
358  * STATUS_INVALID_PARAMETER_2 if an invalid information class has been provided.
359  * STATUS_INFO_LENGTH_MISMATCH in case either DataLength was set to 0
360  *   or to a value that does not match what the SmInformationClass requires.
361  * Otherwise, a success status as handed by the SM reply, or a failure
362  * status code.
363  *
364  * @remark
365  * This API is ReactOS-specific and not in NT.
366  */
367 NTSTATUS
368 NTAPI
369 SmQueryInformation(
370     _In_ HANDLE SmApiPort,
371     _In_ SM_INFORMATION_CLASS SmInformationClass,
372     _Inout_ PVOID Data,
373     _In_ ULONG DataLength,
374     _Inout_opt_ PULONG ReturnedDataLength)
375 {
376 #if defined(__REACTOS__) && DBG
377     NTSTATUS Status;
378     SM_API_MSG SmApiMsg = {0};
379     PSM_QUERYINFO_MSG QueryInfo = &SmApiMsg.u.QueryInfo;
380 
381     /* Marshal data in the port message */
382     switch (SmInformationClass)
383     {
384         case SmBasicInformation:
385             if (DataLength != sizeof(SM_BASIC_INFORMATION))
386             {
387                 return STATUS_INFO_LENGTH_MISMATCH;
388             }
389             QueryInfo->SmInformationClass = SmBasicInformation;
390             QueryInfo->DataLength = DataLength;
391             QueryInfo->BasicInformation.SubSystemCount = 0;
392             break;
393 
394         case SmSubSystemInformation:
395             if (DataLength != sizeof(SM_SUBSYSTEM_INFORMATION))
396             {
397                 return STATUS_INFO_LENGTH_MISMATCH;
398             }
399             QueryInfo->SmInformationClass = SmSubSystemInformation;
400             QueryInfo->DataLength = DataLength;
401             QueryInfo->SubSystemInformation.SubSystemId =
402                 ((PSM_SUBSYSTEM_INFORMATION)Data)->SubSystemId;
403             break;
404 
405         default:
406             return STATUS_INVALID_PARAMETER_2;
407     }
408 
409     /* SM API to invoke */
410     SmApiMsg.ApiNumber = SM_API_QUERY_INFORMATION;
411 
412     /* NOTE: Repurpose the DataLength variable */
413     DataLength = sizeof(SM_QUERYINFO_MSG);
414 
415     /* Fill out the Port Message Header */
416     // SmApiMsg.h.u2.s2.Type = LPC_NEW_MESSAGE;
417     SmApiMsg.h.u2.ZeroInit = 0;
418     /* DataLength = user_data_size + anything between
419      * header and data, including intermediate padding */
420     SmApiMsg.h.u1.s1.DataLength = (CSHORT)DataLength +
421         FIELD_OFFSET(SM_API_MSG, u) - sizeof(SmApiMsg.h);
422     /* TotalLength = sizeof(SmApiMsg) on <= NT5.2, otherwise:
423      * DataLength + header_size == user_data_size + FIELD_OFFSET(SM_API_MSG, u)
424      * without structure trailing padding */
425     SmApiMsg.h.u1.s1.TotalLength = SmApiMsg.h.u1.s1.DataLength + sizeof(SmApiMsg.h);
426 
427     /* Send the LPC message and wait for a reply */
428     Status = NtRequestWaitReplyPort(SmApiPort, &SmApiMsg.h, &SmApiMsg.h);
429     if (!NT_SUCCESS(Status))
430     {
431         DPRINT1("SMLIB: %s: NtRequestWaitReplyPort failed, Status: 0x%08lx\n",
432                 __FUNCTION__, Status);
433         return Status;
434     }
435 
436     /* Unmarshal data */
437     RtlCopyMemory(Data,
438                   &QueryInfo->BasicInformation,
439                   QueryInfo->DataLength);
440 
441     /* Use caller provided storage to store data size */
442     if (ReturnedDataLength)
443         *ReturnedDataLength = QueryInfo->DataLength;
444 
445     /* Return the real status */
446     return SmApiMsg.ReturnValue;
447 #else
448     return STATUS_NOT_IMPLEMENTED;
449 #endif /* defined(__REACTOS__) && DBG */
450 }
451 
452 /* EOF */
453