FreeRDP
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 
33 typedef 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 
46 static int transport_bio_named_uninit(BIO* bio);
47 
48 static 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 
83 static 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 
102 static 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 
134 static 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 
254 static int transport_bio_named_puts(BIO* bio, const char* str)
255 {
256  WINPR_ASSERT(bio);
257  WINPR_ASSERT(str);
258 
259  return transport_bio_named_write(bio, str, (int)strnlen(str, INT32_MAX));
260 }
261 
262 static int transport_bio_named_gets(BIO* bio, char* str, int size)
263 {
264  WINPR_ASSERT(bio);
265  WINPR_ASSERT(str);
266 
267  return transport_bio_named_read(bio, str, size);
268 }
269 
270 static long transport_bio_named_ctrl(BIO* bio, int cmd, long arg1, void* arg2)
271 {
272  WINPR_ASSERT(bio);
273 
274  int status = -1;
275  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
276 
277  switch (cmd)
278  {
279  case BIO_C_SET_SOCKET:
280  case BIO_C_GET_SOCKET:
281  return -1;
282  case BIO_C_GET_EVENT:
283  if (!BIO_get_init(bio) || !arg2)
284  return 0;
285 
286  *((HANDLE*)arg2) = ptr->readEvent;
287  return 1;
288  case BIO_C_SET_HANDLE:
289  BIO_set_init(bio, 1);
290  if (!BIO_get_init(bio) || !arg2)
291  return 0;
292 
293  ptr->hFile = (HANDLE)arg2;
294  ptr->blocking = TRUE;
295  if (!doReadOp(ptr))
296  return -1;
297  return 1;
298  case BIO_C_SET_NONBLOCK:
299  {
300  WLog_DBG(TAG, "BIO_C_SET_NONBLOCK");
301  ptr->blocking = FALSE;
302  return 1;
303  }
304  case BIO_C_WAIT_READ:
305  {
306  WLog_DBG(TAG, "BIO_C_WAIT_READ");
307  return 1;
308  }
309 
310  case BIO_C_WAIT_WRITE:
311  {
312  WLog_DBG(TAG, "BIO_C_WAIT_WRITE");
313  return 1;
314  }
315 
316  default:
317  break;
318  }
319 
320  switch (cmd)
321  {
322  case BIO_CTRL_GET_CLOSE:
323  status = BIO_get_shutdown(bio);
324  break;
325 
326  case BIO_CTRL_SET_CLOSE:
327  BIO_set_shutdown(bio, (int)arg1);
328  status = 1;
329  break;
330 
331  case BIO_CTRL_DUP:
332  status = 1;
333  break;
334 
335  case BIO_CTRL_FLUSH:
336  status = 1;
337  break;
338 
339  default:
340  status = 0;
341  break;
342  }
343 
344  return status;
345 }
346 
347 static void BIO_NAMED_free(WINPR_BIO_NAMED* ptr)
348 {
349  if (!ptr)
350  return;
351 
352  if (ptr->hFile)
353  {
354  (void)CloseHandle(ptr->hFile);
355  ptr->hFile = NULL;
356  }
357 
358  if (ptr->readEvent)
359  {
360  (void)CloseHandle(ptr->readEvent);
361  ptr->readEvent = NULL;
362  }
363 
364  ringbuffer_destroy(&ptr->readBuffer);
365  free(ptr);
366 }
367 
368 static int transport_bio_named_uninit(BIO* bio)
369 {
370  WINPR_ASSERT(bio);
371  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
372 
373  BIO_NAMED_free(ptr);
374 
375  BIO_set_init(bio, 0);
376  BIO_set_flags(bio, 0);
377  return 1;
378 }
379 
380 static int transport_bio_named_new(BIO* bio)
381 {
382  WINPR_ASSERT(bio);
383 
384  WINPR_BIO_NAMED* ptr = (WINPR_BIO_NAMED*)calloc(1, sizeof(WINPR_BIO_NAMED));
385  if (!ptr)
386  return 0;
387 
388  if (!ringbuffer_init(&ptr->readBuffer, 0xfffff))
389  goto error;
390 
391  ptr->readEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
392  if (!ptr->readEvent || ptr->readEvent == INVALID_HANDLE_VALUE)
393  goto error;
394 
395  ptr->readOverlapped.hEvent = ptr->readEvent;
396 
397  BIO_set_data(bio, ptr);
398  BIO_set_flags(bio, BIO_FLAGS_SHOULD_RETRY);
399  return 1;
400 
401 error:
402  BIO_NAMED_free(ptr);
403  return 0;
404 }
405 
406 static int transport_bio_named_free(BIO* bio)
407 {
408  WINPR_BIO_NAMED* ptr = NULL;
409 
410  if (!bio)
411  return 0;
412 
413  transport_bio_named_uninit(bio);
414 
415  ptr = (WINPR_BIO_NAMED*)BIO_get_data(bio);
416  if (ptr)
417  BIO_set_data(bio, NULL);
418 
419  return 1;
420 }
421 
422 static BIO_METHOD* BIO_s_namedpipe(void)
423 {
424  static BIO_METHOD* bio_methods = NULL;
425 
426  if (bio_methods == NULL)
427  {
428  if (!(bio_methods = BIO_meth_new(BIO_TYPE_NAMEDPIPE, "NamedPipe")))
429  return NULL;
430 
431  BIO_meth_set_write(bio_methods, transport_bio_named_write);
432  BIO_meth_set_read(bio_methods, transport_bio_named_read);
433  BIO_meth_set_puts(bio_methods, transport_bio_named_puts);
434  BIO_meth_set_gets(bio_methods, transport_bio_named_gets);
435  BIO_meth_set_ctrl(bio_methods, transport_bio_named_ctrl);
436  BIO_meth_set_create(bio_methods, transport_bio_named_new);
437  BIO_meth_set_destroy(bio_methods, transport_bio_named_free);
438  }
439 
440  return bio_methods;
441 }
442 
443 typedef NTSTATUS (*WinStationCreateChildSessionTransportFn)(WCHAR* path, DWORD len);
444 static BOOL createChildSessionTransport(HANDLE* pFile)
445 {
446  WINPR_ASSERT(pFile);
447 
448  HANDLE hModule = NULL;
449  BOOL ret = FALSE;
450  *pFile = INVALID_HANDLE_VALUE;
451 
452  BOOL childEnabled = 0;
453  if (!WTSIsChildSessionsEnabled(&childEnabled))
454  {
455  WLog_ERR(TAG, "error when calling WTSIsChildSessionsEnabled");
456  goto out;
457  }
458 
459  if (!childEnabled)
460  {
461  WLog_INFO(TAG, "child sessions aren't enabled");
462  if (!WTSEnableChildSessions(TRUE))
463  {
464  WLog_ERR(TAG, "error when calling WTSEnableChildSessions");
465  goto out;
466  }
467  WLog_INFO(TAG, "successfully enabled child sessions");
468  }
469 
470  hModule = LoadLibraryA("winsta.dll");
471  if (!hModule)
472  return FALSE;
473  WCHAR pipePath[0x80] = { 0 };
474  char pipePathA[0x80] = { 0 };
475 
476  WinStationCreateChildSessionTransportFn createChildSessionFn = GetProcAddressAs(
477  hModule, "WinStationCreateChildSessionTransport", WinStationCreateChildSessionTransportFn);
478  if (!createChildSessionFn)
479  {
480  WLog_ERR(TAG, "unable to retrieve WinStationCreateChildSessionTransport function");
481  goto out;
482  }
483 
484  HRESULT hStatus = createChildSessionFn(pipePath, 0x80);
485  if (!SUCCEEDED(hStatus))
486  {
487  WLog_ERR(TAG, "error 0x%x when creating childSessionTransport", hStatus);
488  goto out;
489  }
490 
491  const BYTE startOfPath[] = { '\\', 0, '\\', 0, '.', 0, '\\', 0 };
492  if (_wcsncmp(pipePath, (const WCHAR*)startOfPath, 4))
493  {
494  /* when compiled under 32 bits, the path may miss "\\.\" at the beginning of the string
495  * so add it if it's not there
496  */
497  size_t len = _wcslen(pipePath);
498  if (len > 0x80 - (4 + 1))
499  {
500  WLog_ERR(TAG, "pipePath is too long to be adjusted");
501  goto out;
502  }
503 
504  memmove(pipePath + 4, pipePath, (len + 1) * sizeof(WCHAR));
505  memcpy(pipePath, startOfPath, 8);
506  }
507 
508  (void)ConvertWCharNToUtf8(pipePath, 0x80, pipePathA, sizeof(pipePathA));
509  WLog_DBG(TAG, "child session is at '%s'", pipePathA);
510 
511  HANDLE f = CreateFileW(pipePath, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
512  FILE_FLAG_OVERLAPPED, NULL);
513  if (f == INVALID_HANDLE_VALUE)
514  {
515  WLog_ERR(TAG, "error when connecting to local named pipe");
516  goto out;
517  }
518 
519  *pFile = f;
520  ret = TRUE;
521 
522 out:
523  FreeLibrary(hModule);
524  return ret;
525 }
526 
527 BIO* createChildSessionBio(void)
528 {
529  HANDLE f = INVALID_HANDLE_VALUE;
530  if (!createChildSessionTransport(&f))
531  return NULL;
532 
533  BIO* lowLevelBio = BIO_new(BIO_s_namedpipe());
534  if (!lowLevelBio)
535  {
536  (void)CloseHandle(f);
537  return NULL;
538  }
539 
540  BIO_set_handle(lowLevelBio, &f);
541  BIO* bufferedBio = BIO_new(BIO_s_buffered_socket());
542 
543  if (!bufferedBio)
544  {
545  BIO_free_all(lowLevelBio);
546  return FALSE;
547  }
548 
549  bufferedBio = BIO_push(bufferedBio, lowLevelBio);
550 
551  return bufferedBio;
552 }
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