FreeRDP
Loading...
Searching...
No Matches
childsession.c
1
20#include "tcp.h"
21
22#include <winpr/library.h>
23#include <winpr/assert.h>
24#include <winpr/print.h>
25#include <winpr/sysinfo.h>
26
27#include <freerdp/utils/ringbuffer.h>
28
29#include "childsession.h"
30
31#define TAG FREERDP_TAG("childsession")
32
33typedef struct
34{
35 OVERLAPPED readOverlapped;
36 HANDLE hFile;
37 BOOL opInProgress;
38 BOOL lastOpClosed;
39 RingBuffer readBuffer;
40 BOOL blocking;
41 BYTE tmpReadBuffer[4096];
42
43 HANDLE readEvent;
44} WINPR_BIO_NAMED;
45
46static int transport_bio_named_uninit(BIO* bio);
47
48static int transport_bio_named_write(BIO* bio, const char* buf, int size)
49{
50 WINPR_ASSERT(bio);
51 WINPR_ASSERT(buf);
52
53 WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
54
55 if (!buf)
56 return 0;
57
58 BIO_clear_flags(bio, BIO_FLAGS_WRITE);
59 DWORD written = 0;
60
61 UINT64 start = GetTickCount64();
62 BOOL ret = WriteFile(ptr->hFile, buf, WINPR_ASSERTING_INT_CAST(uint32_t, size), &written, NULL);
63 // winpr_HexDump(TAG, WLOG_DEBUG, buf, size);
64
65 if (!ret)
66 {
67 WLog_VRB(TAG, "error or deferred");
68 return 0;
69 }
70
71 WLog_VRB(TAG, "(%d)=%d written=%d duration=%d", size, ret, written, GetTickCount64() - start);
72
73 if (written == 0)
74 {
75 WLog_VRB(TAG, "closed on write");
76 return 0;
77 }
78
79 WINPR_ASSERT(written <= INT32_MAX);
80 return (int)written;
81}
82
83static BOOL treatReadResult(WINPR_BIO_NAMED* ptr, DWORD readBytes)
84{
85 WLog_VRB(TAG, "treatReadResult(readBytes=%" PRIu32 ")", readBytes);
86 ptr->opInProgress = FALSE;
87 if (readBytes == 0)
88 {
89 WLog_VRB(TAG, "readBytes == 0");
90 return TRUE;
91 }
92
93 if (!ringbuffer_write(&ptr->readBuffer, ptr->tmpReadBuffer, readBytes))
94 {
95 WLog_VRB(TAG, "ringbuffer_write()");
96 return FALSE;
97 }
98
99 return SetEvent(ptr->readEvent);
100}
101
102static BOOL doReadOp(WINPR_BIO_NAMED* ptr)
103{
104 DWORD readBytes = 0;
105
106 if (!ResetEvent(ptr->readEvent))
107 return FALSE;
108
109 ptr->opInProgress = TRUE;
110 if (!ReadFile(ptr->hFile, ptr->tmpReadBuffer, sizeof(ptr->tmpReadBuffer), &readBytes,
111 &ptr->readOverlapped))
112 {
113 DWORD error = GetLastError();
114 switch (error)
115 {
116 case ERROR_NO_DATA:
117 WLog_VRB(TAG, "No Data, unexpected");
118 return TRUE;
119 case ERROR_IO_PENDING:
120 WLog_VRB(TAG, "ERROR_IO_PENDING");
121 return TRUE;
122 case ERROR_BROKEN_PIPE:
123 WLog_VRB(TAG, "broken pipe");
124 ptr->lastOpClosed = TRUE;
125 return TRUE;
126 default:
127 return FALSE;
128 }
129 }
130
131 return treatReadResult(ptr, readBytes);
132}
133
134static int transport_bio_named_read(BIO* bio, char* buf, int size)
135{
136 WINPR_ASSERT(bio);
137 WINPR_ASSERT(buf);
138
139 WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
140 if (!buf)
141 return 0;
142
143 BIO_clear_flags(bio, BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ);
144
145 if (ptr->blocking)
146 {
147 while (!ringbuffer_used(&ptr->readBuffer))
148 {
149 if (ptr->lastOpClosed)
150 return 0;
151
152 if (ptr->opInProgress)
153 {
154 DWORD status = WaitForSingleObjectEx(ptr->readEvent, 500, TRUE);
155 switch (status)
156 {
157 case WAIT_TIMEOUT:
158 case WAIT_IO_COMPLETION:
159 continue;
160 case WAIT_OBJECT_0:
161 break;
162 default:
163 return -1;
164 }
165
166 DWORD readBytes = 0;
167 if (!GetOverlappedResult(ptr->hFile, &ptr->readOverlapped, &readBytes, FALSE))
168 {
169 WLog_ERR(TAG, "GetOverlappedResult blocking(lastError=%" PRIu32 ")",
170 GetLastError());
171 return -1;
172 }
173
174 if (!treatReadResult(ptr, readBytes))
175 {
176 WLog_ERR(TAG, "treatReadResult blocking");
177 return -1;
178 }
179 }
180 }
181 }
182 else
183 {
184 if (ptr->opInProgress)
185 {
186 DWORD status = WaitForSingleObject(ptr->readEvent, 0);
187 switch (status)
188 {
189 case WAIT_OBJECT_0:
190 break;
191 case WAIT_TIMEOUT:
192 BIO_set_flags(bio, (BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ));
193 return -1;
194 default:
195 WLog_ERR(TAG, "error WaitForSingleObject(readEvent)=0x%" PRIx32 "", status);
196 return -1;
197 }
198
199 DWORD readBytes = 0;
200 if (!GetOverlappedResult(ptr->hFile, &ptr->readOverlapped, &readBytes, FALSE))
201 {
202 WLog_ERR(TAG, "GetOverlappedResult non blocking(lastError=%" PRIu32 ")",
203 GetLastError());
204 return -1;
205 }
206
207 if (!treatReadResult(ptr, readBytes))
208 {
209 WLog_ERR(TAG, "error treatReadResult non blocking");
210 return -1;
211 }
212 }
213 }
214
215 SSIZE_T ret = -1;
216 if (size >= 0)
217 {
218 size_t rsize = ringbuffer_used(&ptr->readBuffer);
219 if (rsize <= SSIZE_MAX)
220 ret = MIN(size, (SSIZE_T)rsize);
221 }
222 if ((size >= 0) && ret)
223 {
224 DataChunk chunks[2] = { 0 };
225 const int nchunks =
226 ringbuffer_peek(&ptr->readBuffer, chunks, WINPR_ASSERTING_INT_CAST(size_t, ret));
227 for (int i = 0; i < nchunks; i++)
228 {
229 memcpy(buf, chunks[i].data, chunks[i].size);
230 buf += chunks[i].size;
231 }
232
233 ringbuffer_commit_read_bytes(&ptr->readBuffer, WINPR_ASSERTING_INT_CAST(size_t, ret));
234
235 WLog_VRB(TAG, "(%d)=%" PRIdz " nchunks=%d", size, ret, nchunks);
236 }
237
238 if (!ringbuffer_used(&ptr->readBuffer))
239 {
240 if (!ptr->opInProgress && !doReadOp(ptr))
241 {
242 WLog_ERR(TAG, "error rearming read");
243 return -1;
244 }
245 }
246
247 if (ret <= 0)
248 BIO_set_flags(bio, (BIO_FLAGS_SHOULD_RETRY | BIO_FLAGS_READ));
249
250 WINPR_ASSERT(ret <= INT32_MAX);
251 return (int)ret;
252}
253
254static int transport_bio_named_puts(BIO* bio, const char* str)
255{
256 WINPR_ASSERT(bio);
257 WINPR_ASSERT(str);
258
259 const int max = (INT_MAX > SIZE_MAX) ? SIZE_MAX : INT_MAX;
260 const size_t len = strnlen(str, max);
261 if (len >= max)
262 return -1;
263 return transport_bio_named_write(bio, str, WINPR_ASSERTING_INT_CAST(int, len));
264}
265
266static int transport_bio_named_gets(BIO* bio, char* str, int size)
267{
268 WINPR_ASSERT(bio);
269 WINPR_ASSERT(str);
270
271 return transport_bio_named_read(bio, str, size);
272}
273
274static long transport_bio_named_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
275{
276 WINPR_ASSERT(bio);
277
278 int status = -1;
279 WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
280
281 switch (cmd)
282 {
283 case BIO_C_SET_SOCKET:
284 case BIO_C_GET_SOCKET:
285 return -1;
286 case BIO_C_GET_EVENT:
287 if (!BIO_get_init(bio) || !arg2)
288 return 0;
289
290 *((HANDLE*)arg2) = ptr->readEvent;
291 return 1;
292 case BIO_C_SET_HANDLE:
293 BIO_set_init(bio, 1);
294 if (!BIO_get_init(bio) || !arg2)
295 return 0;
296
297 ptr->hFile = (HANDLE)arg2;
298 ptr->blocking = TRUE;
299 if (!doReadOp(ptr))
300 return -1;
301 return 1;
302 case BIO_C_SET_NONBLOCK:
303 {
304 WLog_DBG(TAG, "BIO_C_SET_NONBLOCK");
305 ptr->blocking = FALSE;
306 return 1;
307 }
308 case BIO_C_WAIT_READ:
309 {
310 WLog_DBG(TAG, "BIO_C_WAIT_READ");
311 return 1;
312 }
313
314 case BIO_C_WAIT_WRITE:
315 {
316 WLog_DBG(TAG, "BIO_C_WAIT_WRITE");
317 return 1;
318 }
319
320 default:
321 break;
322 }
323
324 switch (cmd)
325 {
326 case BIO_CTRL_GET_CLOSE:
327 status = BIO_get_shutdown(bio);
328 break;
329
330 case BIO_CTRL_SET_CLOSE:
331 BIO_set_shutdown(bio, (int)arg1);
332 status = 1;
333 break;
334
335 case BIO_CTRL_DUP:
336 status = 1;
337 break;
338
339 case BIO_CTRL_FLUSH:
340 status = 1;
341 break;
342
343 default:
344 status = 0;
345 break;
346 }
347
348 return status;
349}
350
351static void BIO_NAMED_free(WINPR_BIO_NAMED* ptr)
352{
353 if (!ptr)
354 return;
355
356 if (ptr->hFile)
357 {
358 (void)CloseHandle(ptr->hFile);
359 ptr->hFile = NULL;
360 }
361
362 if (ptr->readEvent)
363 {
364 (void)CloseHandle(ptr->readEvent);
365 ptr->readEvent = NULL;
366 }
367
368 ringbuffer_destroy(&ptr->readBuffer);
369 free(ptr);
370}
371
372static int transport_bio_named_uninit(BIO* bio)
373{
374 WINPR_ASSERT(bio);
375 WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
376
377 BIO_NAMED_free(ptr);
378
379 BIO_set_init(bio, 0);
380 BIO_set_flags(bio, 0);
381 return 1;
382}
383
384static int transport_bio_named_new(BIO* bio)
385{
386 WINPR_ASSERT(bio);
387
388 WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)calloc(1, sizeof(WINPR_BIO_NAMED));
389 if (!ptr)
390 return 0;
391
392 if (!ringbuffer_init(&ptr->readBuffer, 0xfffff))
393 goto error;
394
395 ptr->readEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
396 if (!ptr->readEvent || ptr->readEvent == INVALID_HANDLE_VALUE)
397 goto error;
398
399 ptr->readOverlapped.hEvent = ptr->readEvent;
400
401 BIO_set_data(bio, ptr);
402 BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
403 return 1;
404
405error:
406 BIO_NAMED_free(ptr);
407 return 0;
408}
409
410static int transport_bio_named_free(BIO* bio)
411{
412 WINPR_BIO_NAMED* ptr = NULL;
413
414 if (!bio)
415 return 0;
416
417 transport_bio_named_uninit(bio);
418
419 ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
420 if (ptr)
421 BIO_set_data(bio, NULL);
422
423 return 1;
424}
425
426static BIO_METHOD* BIO_s_namedpipe(void)
427{
428 static BIO_METHOD* bio_methods = NULL;
429
430 if (bio_methods == NULL)
431 {
432 if (!(bio_methods = BIO_meth_new(BIO_TYPE_NAMEDPIPE, "NamedPipe")))
433 return NULL;
434
435 BIO_meth_set_write(bio_methods, transport_bio_named_write);
436 BIO_meth_set_read(bio_methods, transport_bio_named_read);
437 BIO_meth_set_puts(bio_methods, transport_bio_named_puts);
438 BIO_meth_set_gets(bio_methods, transport_bio_named_gets);
439 BIO_meth_set_ctrl(bio_methods, transport_bio_named_ctrl);
440 BIO_meth_set_create(bio_methods, transport_bio_named_new);
441 BIO_meth_set_destroy(bio_methods, transport_bio_named_free);
442 }
443
444 return bio_methods;
445}
446
447typedef NTSTATUS (*WinStationCreateChildSessionTransportFn)(WCHAR* path, DWORD len);
448static BOOL createChildSessionTransport(HANDLE* pFile)
449{
450 WINPR_ASSERT(pFile);
451
452 HANDLE hModule = NULL;
453 BOOL ret = FALSE;
454 *pFile = INVALID_HANDLE_VALUE;
455
456 BOOL childEnabled = 0;
457 if (!WTSIsChildSessionsEnabled(&childEnabled))
458 {
459 WLog_ERR(TAG, "error when calling WTSIsChildSessionsEnabled");
460 goto out;
461 }
462
463 if (!childEnabled)
464 {
465 WLog_INFO(TAG, "child sessions aren't enabled");
466 if (!WTSEnableChildSessions(TRUE))
467 {
468 WLog_ERR(TAG, "error when calling WTSEnableChildSessions");
469 goto out;
470 }
471 WLog_INFO(TAG, "successfully enabled child sessions");
472 }
473
474 hModule = LoadLibraryA("winsta.dll");
475 if (!hModule)
476 return FALSE;
477 WCHAR pipePath[0x80] = { 0 };
478 char pipePathA[0x80] = { 0 };
479
480 WinStationCreateChildSessionTransportFn createChildSessionFn = GetProcAddressAs(
481 hModule, "WinStationCreateChildSessionTransport", WinStationCreateChildSessionTransportFn);
482 if (!createChildSessionFn)
483 {
484 WLog_ERR(TAG, "unable to retrieve WinStationCreateChildSessionTransport function");
485 goto out;
486 }
487
488 HRESULT hStatus = createChildSessionFn(pipePath, 0x80);
489 if (!SUCCEEDED(hStatus))
490 {
491 WLog_ERR(TAG, "error 0x%x when creating childSessionTransport", hStatus);
492 goto out;
493 }
494
495 const BYTE startOfPath[] = { '\\', 0, '\\', 0, '.', 0, '\\', 0 };
496 if (_wcsncmp(pipePath, (const WCHAR*)startOfPath, 4))
497 {
498 /* when compiled under 32 bits, the path may miss "\\.\" at the beginning of the string
499 * so add it if it's not there
500 */
501 size_t len = _wcslen(pipePath);
502 if (len > 0x80 - (4 + 1))
503 {
504 WLog_ERR(TAG, "pipePath is too long to be adjusted");
505 goto out;
506 }
507
508 memmove(pipePath + 4, pipePath, (len + 1) * sizeof(WCHAR));
509 memcpy(pipePath, startOfPath, 8);
510 }
511
512 (void)ConvertWCharNToUtf8(pipePath, 0x80, pipePathA, sizeof(pipePathA));
513 WLog_DBG(TAG, "child session is at '%s'", pipePathA);
514
515 HANDLE f = CreateFileW(pipePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
516 FILE_FLAG_OVERLAPPED, NULL);
517 if (f == INVALID_HANDLE_VALUE)
518 {
519 WLog_ERR(TAG, "error when connecting to local named pipe");
520 goto out;
521 }
522
523 *pFile = f;
524 ret = TRUE;
525
526out:
527 FreeLibrary(hModule);
528 return ret;
529}
530
531BIO* createChildSessionBio(void)
532{
533 HANDLE f = INVALID_HANDLE_VALUE;
534 if (!createChildSessionTransport(&f))
535 return NULL;
536
537 BIO* lowLevelBio = BIO_new(BIO_s_namedpipe());
538 if (!lowLevelBio)
539 {
540 (void)CloseHandle(f);
541 return NULL;
542 }
543
544 BIO_set_handle(lowLevelBio, f);
545 BIO* bufferedBio = BIO_new(BIO_s_buffered_socket());
546
547 if (!bufferedBio)
548 {
549 BIO_free_all(lowLevelBio);
550 return NULL;
551 }
552
553 bufferedBio = BIO_push(bufferedBio, lowLevelBio);
554
555 return bufferedBio;
556}
a piece of data in the ring buffer, exactly like a glibc iovec
Definition ringbuffer.h:44
ring buffer meta data
Definition ringbuffer.h:33