xref: /reactos/sdk/lib/smlib/smclient.c (revision 144e984b)
1 /*
2  * PROJECT:     ReactOS NT-Compatible Session Manager
3  * LICENSE:     BSD 2-Clause License (https://spdx.org/licenses/BSD-2-Clause)
4  * PURPOSE:     SMSS Client Library (SMLIB) Client Stubs
5  * COPYRIGHT:   Copyright 2012-2013 Alex Ionescu <alex.ionescu@reactos.org>
6  *              Copyright 2021 Hervé Poussineau <hpoussin@reactos.org>
7  *              Copyright 2022 Hermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include "precomp.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 /* FUNCTIONS ******************************************************************/
18 
19 /**
20  * @brief
21  * Connects to the SM API port for registering a session callback port (Sb)
22  * associated to a subsystem, or for issuing API requests to the SM API port.
23  *
24  * There are only two ways to call this API:
25  * a) subsystems willing to register with SM will use it
26  *    with full parameters (the function checks them);
27  * b) regular SM clients, will set to 0 the 1st, the 2nd,
28  *    and the 3rd parameters.
29  *
30  * @param[in]   SbApiPortName
31  * Name of the Sb port the calling subsystem server already
32  * created in the system namespace.
33  *
34  * @param[in]   SbApiPort
35  * LPC port handle (checked, but not used: the subsystem is
36  * required to have already created the callback port before
37  * it connects to the SM).
38  *
39  * @param[in]   ImageType
40  * A valid IMAGE_SUBSYSTEM_xxx value. PE images having this
41  * subsystem value will be handled by the current subsystem.
42  *
43  * @param[out]  SmApiPort
44  * Pointer to a HANDLE, which will be filled with a valid
45  * client-side LPC communication port.
46  *
47  * @return
48  * If all three optional values are omitted, an LPC status.
49  * STATUS_INVALID_PARAMETER_MIX if PortName is defined and
50  * both SbApiPort and ImageType are 0.
51  *
52  * @remark
53  * Exported on Vista+ by NTDLL and called RtlConnectToSm().
54  **/
55 NTSTATUS
56 NTAPI
57 SmConnectToSm(
58     _In_opt_ PUNICODE_STRING SbApiPortName,
59     _In_opt_ HANDLE SbApiPort,
60     _In_opt_ ULONG ImageType,
61     _Out_ PHANDLE SmApiPort)
62 {
63     NTSTATUS Status;
64     SECURITY_QUALITY_OF_SERVICE SecurityQos;
65     UNICODE_STRING PortName;
66     SB_CONNECTION_INFO ConnectInfo = {0};
67     ULONG ConnectInfoLength = sizeof(ConnectInfo);
68 
69     /* Setup the QoS structure */
70     SecurityQos.ImpersonationLevel = SecurityIdentification;
71     SecurityQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
72     SecurityQos.EffectiveOnly = TRUE;
73 
74     /* Set the SM API port name */
75     RtlInitUnicodeString(&PortName, L"\\SmApiPort"); // SM_API_PORT_NAME
76 
77     /* Check if this is a client connecting to SMSS, or SMSS to itself */
78     if (SbApiPortName)
79     {
80         /* A client SB port as well as an image type must be present */
81         if (!SbApiPort || (ImageType == IMAGE_SUBSYSTEM_UNKNOWN))
82             return STATUS_INVALID_PARAMETER_MIX;
83 
84         /* Validate SbApiPortName's length */
85         if (SbApiPortName->Length >= sizeof(ConnectInfo.SbApiPortName))
86             return STATUS_INVALID_PARAMETER;
87 
88         /* Copy the client port name, and NULL-terminate it */
89         RtlCopyMemory(ConnectInfo.SbApiPortName,
90                       SbApiPortName->Buffer,
91                       SbApiPortName->Length);
92         ConnectInfo.SbApiPortName[SbApiPortName->Length / sizeof(WCHAR)] = UNICODE_NULL;
93 
94         /* Save the subsystem type */
95         ConnectInfo.SubsystemType = ImageType;
96     }
97     else
98     {
99         /* No client port, and the subsystem type is not set */
100         ConnectInfo.SbApiPortName[0] = UNICODE_NULL;
101         ConnectInfo.SubsystemType = IMAGE_SUBSYSTEM_UNKNOWN;
102     }
103 
104     /* Connect to SMSS and exchange connection information */
105     Status = NtConnectPort(SmApiPort,
106                            &PortName,
107                            &SecurityQos,
108                            NULL,
109                            NULL,
110                            NULL,
111                            &ConnectInfo,
112                            &ConnectInfoLength);
113     if (!NT_SUCCESS(Status))
114     {
115         DPRINT1("SmConnectToSm: Connect to Sm failed %lx\n", Status);
116     }
117 #if (NTDDI_VERSION < NTDDI_VISTA)
118     else
119     {
120         /* Treat a warning or informational status as success */
121         Status = STATUS_SUCCESS;
122     }
123 #endif
124 
125     /* Return if the connection was successful or not */
126     return Status;
127 }
128 
129 /**
130  * @brief
131  * Sends a message to the SM via the SM API port.
132  *
133  * @param[in]   SmApiPort
134  * Port handle returned by SmConnectToSm().
135  *
136  * @param[in,out]   SmApiMsg
137  * Message to send to the SM. The API-specific data must be initialized,
138  * and the SmApiMsg->ApiNumber must be specified accordingly.
139  *
140  * @return
141  * Success status as handed by the SM reply; otherwise a failure
142  * status code.
143  *
144  * @remark
145  * Exported on Vista+ by NTDLL and called RtlSendMsgToSm().
146  **/
147 NTSTATUS
148 NTAPI
149 SmSendMsgToSm(
150     _In_ HANDLE SmApiPort,
151     _Inout_ PSM_API_MSG SmApiMsg)
152 {
153     static ULONG RtlpSmMessageInfo[SmpMaxApiNumber] =
154     {
155         0 /*sizeof(SM_CREATE_FOREIGN_SESSION_MSG)*/,
156         sizeof(SM_SESSION_COMPLETE_MSG),
157         0 /*sizeof(SM_TERMINATE_FOREIGN_SESSION_MSG)*/,
158         sizeof(SM_EXEC_PGM_MSG),
159         sizeof(SM_LOAD_DEFERED_SUBSYSTEM_MSG),
160         sizeof(SM_START_CSR_MSG),
161         sizeof(SM_STOP_CSR_MSG),
162     };
163 
164     NTSTATUS Status;
165     ULONG DataLength;
166 
167     if (SmApiMsg->ApiNumber >= SmpMaxApiNumber)
168         return STATUS_NOT_IMPLEMENTED;
169 
170     /* Obtain the necessary data length for this API */
171     DataLength = RtlpSmMessageInfo[SmApiMsg->ApiNumber];
172 
173     /* Fill out the Port Message Header */
174     // RtlZeroMemory(&SmApiMsg->h, sizeof(SmApiMsg->h));
175     SmApiMsg->h.u2.ZeroInit = 0;
176     /* DataLength = user_data_size + anything between
177      * header and data, including intermediate padding */
178     SmApiMsg->h.u1.s1.DataLength = (CSHORT)DataLength +
179         FIELD_OFFSET(SM_API_MSG, u) - sizeof(SmApiMsg->h);
180     /* TotalLength = sizeof(*SmApiMsg) on <= NT5.2, otherwise:
181      * DataLength + header_size == user_data_size + FIELD_OFFSET(SM_API_MSG, u)
182      * without structure trailing padding */
183     SmApiMsg->h.u1.s1.TotalLength = SmApiMsg->h.u1.s1.DataLength + sizeof(SmApiMsg->h);
184 
185     /* Send the LPC message and wait for a reply */
186     Status = NtRequestWaitReplyPort(SmApiPort, &SmApiMsg->h, &SmApiMsg->h);
187     if (!NT_SUCCESS(Status))
188     {
189         DPRINT1("SmSendMsgToSm: NtRequestWaitReplyPort failed, Status: 0x%08lx\n", Status);
190     }
191     else
192     {
193         /* Return the real status */
194         Status = SmApiMsg->ReturnValue;
195     }
196 
197     return Status;
198 }
199 
200 /**
201  * @brief
202  * This function is called by an environment subsystem server
203  * to tell the SM it has terminated the session it managed.
204  *
205  * @param[in]   SmApiPort
206  * Port handle returned by SmConnectToSm().
207  *
208  * @param[in]   SessionId
209  * The session ID of the session being terminated.
210  *
211  * @param[in]   SessionStatus
212  * An NT status code for the termination.
213  *
214  * @return
215  * Success status as handed by the SM reply; otherwise a failure
216  * status code.
217  **/
218 NTSTATUS
219 NTAPI
220 SmSessionComplete(
221     _In_ HANDLE SmApiPort,
222     _In_ ULONG SessionId,
223     _In_ NTSTATUS SessionStatus)
224 {
225     SM_API_MSG SmApiMsg = {0};
226     PSM_SESSION_COMPLETE_MSG SessionComplete = &SmApiMsg.u.SessionComplete;
227 
228 #if 0 //def _WIN64
229     /* 64-bit SMSS needs to talk to 32-bit processes so do the LPC conversion */
230     if (SmpIsWow64Process())
231     {
232         return SmpWow64SessionComplete(SmApiPort, SessionId, SessionStatus);
233     }
234 #endif
235 
236     /* Set the message data */
237     SessionComplete->SessionId = SessionId;
238     SessionComplete->SessionStatus = SessionStatus;
239 
240     /* Send the message and wait for a reply */
241     SmApiMsg.ApiNumber = SmpSessionCompleteApi;
242     return SmSendMsgToSm(SmApiPort, &SmApiMsg);
243 }
244 
245 /**
246  * @brief
247  * Requests the SM to start a process under a new environment session.
248  *
249  * @param[in]   SmApiPort
250  * Port handle returned by SmConnectToSm().
251  *
252  * @param[in]   ProcessInformation
253  * A process description as returned by RtlCreateUserProcess().
254  *
255  * @param[in]   DebugFlag
256  * If set, indicates that the caller wants to debug this process
257  * and act as its debug user interface.
258  *
259  * @return
260  * Success status as handed by the SM reply; otherwise a failure
261  * status code.
262  **/
263 NTSTATUS
264 NTAPI
265 SmExecPgm(
266     _In_ HANDLE SmApiPort,
267     _In_ PRTL_USER_PROCESS_INFORMATION ProcessInformation,
268     _In_ BOOLEAN DebugFlag)
269 {
270     NTSTATUS Status;
271     SM_API_MSG SmApiMsg = {0};
272     PSM_EXEC_PGM_MSG ExecPgm = &SmApiMsg.u.ExecPgm;
273 
274 #if 0 //def _WIN64
275     /* 64-bit SMSS needs to talk to 32-bit processes so do the LPC conversion */
276     if (SmpIsWow64Process())
277     {
278         return SmpWow64ExecPgm(SmApiPort, ProcessInformation, DebugFlag);
279     }
280 #endif
281 
282     /* Set the message data */
283     ExecPgm->ProcessInformation = *ProcessInformation;
284     ExecPgm->DebugFlag = DebugFlag;
285 
286     /* Send the message and wait for a reply */
287     SmApiMsg.ApiNumber = SmpExecPgmApi;
288     Status = SmSendMsgToSm(SmApiPort, &SmApiMsg);
289 
290     /* Close the handles that the parent passed in and return status */
291     NtClose(ProcessInformation->ProcessHandle);
292     NtClose(ProcessInformation->ThreadHandle);
293     return Status;
294 }
295 
296 /**
297  * @brief
298  * This function is used to make the SM start an environment
299  * subsystem server process.
300  *
301  * @param[in]   SmApiPort
302  * Port handle returned by SmConnectToSm().
303  *
304  * @param[in]   DeferedSubsystem
305  * Name of the subsystem to start. This must be one of the subsystems
306  * listed by value's name in the SM registry key
307  * \Registry\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems
308  * (used by the SM to lookup the corresponding image name).
309  * Default valid names are: "Debug", "Windows", "Posix", "Os2".
310  *
311  * @return
312  * Success status as handed by the SM reply; otherwise a failure
313  * status code.
314  **/
315 NTSTATUS
316 NTAPI
317 SmLoadDeferedSubsystem(
318     _In_ HANDLE SmApiPort,
319     _In_ PUNICODE_STRING DeferedSubsystem)
320 {
321     SM_API_MSG SmApiMsg = {0};
322     PSM_LOAD_DEFERED_SUBSYSTEM_MSG LoadDefered = &SmApiMsg.u.LoadDefered;
323 
324 #if 0 //def _WIN64
325     /* 64-bit SMSS needs to talk to 32-bit processes so do the LPC conversion */
326     if (SmpIsWow64Process())
327     {
328         return SmpWow64LoadDeferedSubsystem(SmApiPort, DeferedSubsystem);
329     }
330 #endif
331 
332     /* Validate DeferedSubsystem's length */
333     if (DeferedSubsystem->Length > sizeof(LoadDefered->Buffer))
334         return STATUS_INVALID_PARAMETER;
335 
336     /* Set the message data */
337     /* Buffer stores a counted non-NULL-terminated UNICODE string */
338     LoadDefered->Length = DeferedSubsystem->Length;
339     RtlCopyMemory(LoadDefered->Buffer,
340                   DeferedSubsystem->Buffer,
341                   DeferedSubsystem->Length);
342 
343     /* Send the message and wait for a reply */
344     SmApiMsg.ApiNumber = SmpLoadDeferedSubsystemApi;
345     return SmSendMsgToSm(SmApiPort, &SmApiMsg);
346 }
347 
348 /**
349  * @brief
350  * Requests the SM to create a new Terminal Services session
351  * and start an initial command.
352  *
353  * @param[in]   SmApiPort
354  * Port handle returned by SmConnectToSm().
355  *
356  * @param[out]  pMuSessionId
357  * Pointer to a variable that receives the session ID of the new
358  * Terminal Services session that has been created.
359  *
360  * @param[in]   CommandLine
361  * Full path to the image to be used as the initial command.
362  *
363  * @param[out]  pWindowsSubSysProcessId
364  * Pointer to a variable that receives the process ID of the environment
365  * subsystem that has been started in the new session.
366  *
367  * @param[out]  pInitialCommandProcessId
368  * Pointer to a variable that receives the process ID of the initial command.
369  *
370  * @return
371  * Success status as handed by the SM reply; otherwise a failure
372  * status code.
373  **/
374 NTSTATUS
375 NTAPI
376 SmStartCsr(
377     _In_ HANDLE SmApiPort,
378     _Out_ PULONG pMuSessionId,
379     _In_opt_ PUNICODE_STRING CommandLine,
380     _Out_ PHANDLE pWindowsSubSysProcessId,
381     _Out_ PHANDLE pInitialCommandProcessId)
382 {
383     NTSTATUS Status;
384     SM_API_MSG SmApiMsg = {0};
385     PSM_START_CSR_MSG StartCsr = &SmApiMsg.u.StartCsr;
386 
387 #if 0 //def _WIN64
388     /* 64-bit SMSS needs to talk to 32-bit processes so do the LPC conversion */
389     if (SmpIsWow64Process())
390     {
391         return SmpWow64StartCsr(SmApiPort,
392                                 pMuSessionId,
393                                 CommandLine,
394                                 pWindowsSubSysProcessId,
395                                 pInitialCommandProcessId);
396     }
397 #endif
398 
399     /* Set the message data */
400     if (CommandLine)
401     {
402         /* Validate CommandLine's length */
403         if (CommandLine->Length > sizeof(StartCsr->Buffer))
404             return STATUS_INVALID_PARAMETER;
405 
406         /* Buffer stores a counted non-NULL-terminated UNICODE string */
407         StartCsr->Length = CommandLine->Length;
408         RtlCopyMemory(StartCsr->Buffer,
409                       CommandLine->Buffer,
410                       CommandLine->Length);
411     }
412     else
413     {
414         StartCsr->Length = 0;
415     }
416 
417     /* Send the message and wait for a reply */
418     SmApiMsg.ApiNumber = SmpStartCsrApi;
419     Status = SmSendMsgToSm(SmApiPort, &SmApiMsg);
420 
421     /* Give back information to caller */
422     *pMuSessionId = StartCsr->MuSessionId;
423     *pWindowsSubSysProcessId = StartCsr->WindowsSubSysProcessId;
424     *pInitialCommandProcessId = StartCsr->SmpInitialCommandProcessId;
425 
426     return Status;
427 }
428 
429 /**
430  * @brief
431  * Requests the SM to terminate a Terminal Services session.
432  *
433  * @param[in]   SmApiPort
434  * Port handle returned by SmConnectToSm().
435  *
436  * @param[in]   MuSessionId
437  * The Terminal Services session ID, returned by SmStartCsr().
438  *
439  * @return
440  * Success status as handed by the SM reply; otherwise a failure
441  * status code.
442  **/
443 NTSTATUS
444 NTAPI
445 SmStopCsr(
446     _In_ HANDLE SmApiPort,
447     _In_ ULONG MuSessionId)
448 {
449     SM_API_MSG SmApiMsg = {0};
450 
451 #if 0 //def _WIN64
452     /* 64-bit SMSS needs to talk to 32-bit processes so do the LPC conversion */
453     if (SmpIsWow64Process())
454     {
455         return SmpWow64StopCsr(SmApiPort, MuSessionId);
456     }
457 #endif
458 
459     /* Set the message data */
460     SmApiMsg.u.StopCsr.MuSessionId = MuSessionId;
461 
462     /* Send the message and wait for a reply */
463     SmApiMsg.ApiNumber = SmpStopCsrApi;
464     return SmSendMsgToSm(SmApiPort, &SmApiMsg);
465 }
466