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