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