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