FreeRDP
Loading...
Searching...
No Matches
rpc_client.c
1
20#include <freerdp/config.h>
21
22#include <freerdp/log.h>
23
24#include <winpr/crt.h>
25#include <winpr/wtypes.h>
26#include <winpr/assert.h>
27#include <winpr/cast.h>
28#include <winpr/print.h>
29#include <winpr/synch.h>
30#include <winpr/thread.h>
31#include <winpr/stream.h>
32
33#include "http.h"
34#include "ncacn_http.h"
35
36#include "rpc_bind.h"
37#include "rpc_fault.h"
38#include "rpc_client.h"
39#include "rts_signature.h"
40
41#include "../utils.h"
42#include "../rdp.h"
43#include "../proxy.h"
44
45#define TAG FREERDP_TAG("core.gateway.rpc")
46
47WINPR_ATTR_NODISCARD
48static const char* rpc_client_state_str(RPC_CLIENT_STATE state)
49{
50 // NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
51 const char* str = "RPC_CLIENT_STATE_UNKNOWN";
52
53 switch (state)
54 {
55 case RPC_CLIENT_STATE_INITIAL:
56 str = "RPC_CLIENT_STATE_INITIAL";
57 break;
58
59 case RPC_CLIENT_STATE_ESTABLISHED:
60 str = "RPC_CLIENT_STATE_ESTABLISHED";
61 break;
62
63 case RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK:
64 str = "RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK";
65 break;
66
67 case RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK:
68 str = "RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK";
69 break;
70
71 case RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE:
72 str = "RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE";
73 break;
74
75 case RPC_CLIENT_STATE_CONTEXT_NEGOTIATED:
76 str = "RPC_CLIENT_STATE_CONTEXT_NEGOTIATED";
77 break;
78
79 case RPC_CLIENT_STATE_WAIT_RESPONSE:
80 str = "RPC_CLIENT_STATE_WAIT_RESPONSE";
81 break;
82
83 case RPC_CLIENT_STATE_FINAL:
84 str = "RPC_CLIENT_STATE_FINAL";
85 break;
86 default:
87 break;
88 }
89 return str;
90}
91
92WINPR_ATTR_NODISCARD
93static BOOL rpc_pdu_reset(RPC_PDU* pdu)
94{
95 WINPR_ASSERT(pdu);
96 pdu->Type = 0;
97 pdu->Flags = 0;
98 pdu->CallId = 0;
99 Stream_ResetPosition(pdu->s);
100 return Stream_SetLength(pdu->s, 0);
101}
102
103static void rpc_pdu_free(RPC_PDU* pdu)
104{
105 if (!pdu)
106 return;
107
108 Stream_Free(pdu->s, TRUE);
109 free(pdu);
110}
111
112WINPR_ATTR_MALLOC(rpc_pdu_free, 1)
113static RPC_PDU* rpc_pdu_new(void)
114{
115 RPC_PDU* pdu = (RPC_PDU*)calloc(1, sizeof(RPC_PDU));
116
117 if (!pdu)
118 return nullptr;
119
120 pdu->s = Stream_New(nullptr, 4096);
121
122 if (!pdu->s)
123 goto fail;
124
125 if (!rpc_pdu_reset(pdu))
126 goto fail;
127
128 return pdu;
129
130fail:
131 rpc_pdu_free(pdu);
132 return nullptr;
133}
134
135WINPR_ATTR_NODISCARD
136static int rpc_client_receive_pipe_write(RpcClient* client, const BYTE* buffer, size_t length)
137{
138 int status = 0;
139
140 if (!client || !buffer)
141 return -1;
142
143 EnterCriticalSection(&(client->PipeLock));
144
145 if (ringbuffer_write(&(client->ReceivePipe), buffer, length))
146 status += (int)length;
147
148 if (ringbuffer_used(&(client->ReceivePipe)) > 0)
149 (void)SetEvent(client->PipeEvent);
150
151 LeaveCriticalSection(&(client->PipeLock));
152 return status;
153}
154
155int rpc_client_receive_pipe_read(RpcClient* client, BYTE* buffer, size_t length)
156{
157 size_t status = 0;
158 int nchunks = 0;
159 DataChunk chunks[2];
160
161 if (!client || !buffer)
162 return -1;
163
164 EnterCriticalSection(&(client->PipeLock));
165 nchunks = ringbuffer_peek(&(client->ReceivePipe), chunks, length);
166
167 for (int index = 0; index < nchunks; index++)
168 {
169 CopyMemory(&buffer[status], chunks[index].data, chunks[index].size);
170 status += chunks[index].size;
171 }
172
173 if (status > 0)
174 ringbuffer_commit_read_bytes(&(client->ReceivePipe), status);
175
176 if (ringbuffer_used(&(client->ReceivePipe)) < 1)
177 (void)ResetEvent(client->PipeEvent);
178
179 LeaveCriticalSection(&(client->PipeLock));
180
181 if (status > INT_MAX)
182 return -1;
183 return (int)status;
184}
185
186static int rpc_client_transition_to_state(rdpRpc* rpc, RPC_CLIENT_STATE state)
187{
188 int status = 1;
189
190 WINPR_ASSERT(rpc);
191 rpc->State = state;
192 WLog_Print(rpc->log, WLOG_DEBUG, "%s", rpc_client_state_str(state));
193 return status;
194}
195
196WINPR_ATTR_NODISCARD
197static int rpc_client_recv_pdu_int(rdpRpc* rpc, RPC_PDU* pdu)
198{
199 int status = -1;
200 RtsPduSignature found = WINPR_C_ARRAY_INIT;
201
202 WINPR_ASSERT(rpc);
203 WINPR_ASSERT(pdu);
204
205 rdpTsg* tsg = transport_get_tsg(rpc->transport);
206
207 WLog_Print(rpc->log, WLOG_TRACE, "client state %s, vc state %s",
208 rpc_client_state_str(rpc->State), rpc_vc_state_str(rpc->VirtualConnection->State));
209
210 const BOOL rc =
211 rts_match_pdu_signature_ex(&RTS_PDU_PING_SIGNATURE, pdu->s, nullptr, &found, TRUE);
212 rts_print_pdu_signature(rpc->log, WLOG_TRACE, &found);
213 if (rc)
214 return rts_recv_ping_pdu(rpc, pdu->s);
215
216 if (rpc->VirtualConnection->State < VIRTUAL_CONNECTION_STATE_OPENED)
217 {
218 switch (rpc->VirtualConnection->State)
219 {
220 case VIRTUAL_CONNECTION_STATE_INITIAL:
221 break;
222
223 case VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT:
224 break;
225
226 case VIRTUAL_CONNECTION_STATE_WAIT_A3W:
227 if (memcmp(&found, &RTS_PDU_CONN_A3_SIGNATURE, sizeof(found)) != 0)
228 {
229 WLog_Print(rpc->log, WLOG_ERROR, "unexpected RTS PDU: Expected CONN/A3");
230 rts_print_pdu_signature(rpc->log, WLOG_ERROR, &found);
231 return -1;
232 }
233
234 if (!rts_recv_CONN_A3_pdu(rpc, pdu->s))
235 {
236 WLog_Print(rpc->log, WLOG_ERROR, "rts_recv_CONN_A3_pdu failure");
237 return -1;
238 }
239
240 rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
241 VIRTUAL_CONNECTION_STATE_WAIT_C2);
242 status = 1;
243 break;
244
245 case VIRTUAL_CONNECTION_STATE_WAIT_C2:
246 if (memcmp(&found, &RTS_PDU_CONN_C2_SIGNATURE, sizeof(found)) != 0)
247 {
248 WLog_Print(rpc->log, WLOG_ERROR, "unexpected RTS PDU: Expected CONN/C2");
249 rts_print_pdu_signature(rpc->log, WLOG_ERROR, &found);
250 return -1;
251 }
252
253 if (!rts_recv_CONN_C2_pdu(rpc, pdu->s))
254 {
255 WLog_Print(rpc->log, WLOG_ERROR, "rts_recv_CONN_C2_pdu failure");
256 return -1;
257 }
258
259 rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
260 VIRTUAL_CONNECTION_STATE_OPENED);
261 rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_ESTABLISHED);
262
263 if (rpc_send_bind_pdu(rpc, TRUE) < 0)
264 {
265 WLog_Print(rpc->log, WLOG_ERROR, "rpc_send_bind_pdu failure");
266 return -1;
267 }
268
269 rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK);
270 status = 1;
271 break;
272
273 case VIRTUAL_CONNECTION_STATE_OPENED:
274 break;
275
276 case VIRTUAL_CONNECTION_STATE_FINAL:
277 break;
278 default:
279 break;
280 }
281 }
282 else if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
283 {
284 if (rpc->State == RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK)
285 {
286 if (pdu->Type == PTYPE_BIND_ACK || pdu->Type == PTYPE_ALTER_CONTEXT_RESP)
287 {
288 if (!rpc_recv_bind_ack_pdu(rpc, pdu->s))
289 {
290 WLog_Print(rpc->log, WLOG_ERROR, "rpc_recv_bind_ack_pdu failure");
291 return -1;
292 }
293 }
294 else
295 {
296 WLog_Print(rpc->log, WLOG_ERROR,
297 "RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK unexpected pdu type: 0x%08" PRIX32
298 "",
299 pdu->Type);
300 return -1;
301 }
302
303 switch (rpc_bind_state(rpc))
304 {
305 case RPC_BIND_STATE_INCOMPLETE:
306 if (rpc_send_bind_pdu(rpc, FALSE) < 0)
307 {
308 WLog_Print(rpc->log, WLOG_ERROR, "rpc_send_bind_pdu failure");
309 return -1;
310 }
311 break;
312 case RPC_BIND_STATE_LAST_LEG:
313 if (rpc_send_rpc_auth_3_pdu(rpc) < 0)
314 {
315 WLog_Print(rpc->log, WLOG_ERROR,
316 "rpc_secure_bind: error sending rpc_auth_3 pdu!");
317 return -1;
318 }
319 /* fallthrough */
320 WINPR_FALLTHROUGH
321 case RPC_BIND_STATE_COMPLETE:
322 rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_CONTEXT_NEGOTIATED);
323
324 if (!tsg_proxy_begin(tsg))
325 {
326 WLog_Print(rpc->log, WLOG_ERROR, "tsg_proxy_begin failure");
327 return -1;
328 }
329 break;
330 default:
331 break;
332 }
333
334 status = 1;
335 }
336 else
337 {
338 WLog_Print(rpc->log, WLOG_ERROR, "invalid rpc->State: %u", rpc->State);
339 }
340 }
341 else if (rpc->State >= RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
342 {
343 if (!tsg_recv_pdu(tsg, pdu))
344 status = -1;
345 else
346 status = 1;
347 }
348
349 return status;
350}
351
352WINPR_ATTR_NODISCARD
353static int rpc_client_recv_pdu(rdpRpc* rpc, RPC_PDU* pdu)
354{
355 WINPR_ASSERT(rpc);
356 WINPR_ASSERT(pdu);
357
358 Stream_SealLength(pdu->s);
359 Stream_ResetPosition(pdu->s);
360
361 const size_t before = Stream_GetRemainingLength(pdu->s);
362 WLog_Print(rpc->log, WLOG_TRACE, "RPC PDU parsing %" PRIuz " bytes", before);
363 const int rc = rpc_client_recv_pdu_int(rpc, pdu);
364 if (rc < 0)
365 return rc;
366 const size_t after = Stream_GetRemainingLength(pdu->s);
367 if (after > 0)
368 {
369 /* Just log so we do not fail if we have some unprocessed padding bytes */
370 WLog_Print(rpc->log, WLOG_WARN, "Incompletely parsed RPC PDU (%" PRIuz " bytes remain)",
371 after);
372 }
373
374 return rc;
375}
376
377WINPR_ATTR_NODISCARD
378static int rpc_client_recv_fragment(rdpRpc* rpc, wStream* fragment)
379{
380 int rc = -1;
381 size_t StubOffset = 0;
382 size_t StubLength = 0;
383 RpcClientCall* call = nullptr;
384 rpcconn_hdr_t header = WINPR_C_ARRAY_INIT;
385
386 WINPR_ASSERT(rpc);
387 WINPR_ASSERT(rpc->client);
388 WINPR_ASSERT(fragment);
389
390 RPC_PDU* pdu = rpc->client->pdu;
391 WINPR_ASSERT(pdu);
392
393 Stream_SealLength(fragment);
394 Stream_ResetPosition(fragment);
395
396 if (!rts_read_pdu_header(fragment, &header))
397 goto fail;
398
399 if (header.common.ptype == PTYPE_RESPONSE)
400 {
401 rpc->VirtualConnection->DefaultOutChannel->BytesReceived += header.common.frag_length;
402 rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow -=
403 header.common.frag_length;
404
405 if (rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow <
406 (rpc->ReceiveWindow / 2))
407 {
408 if (!rts_send_flow_control_ack_pdu(rpc))
409 goto fail;
410 }
411
412 if (!rpc_get_stub_data_info(rpc, &header, &StubOffset, &StubLength))
413 {
414 WLog_Print(rpc->log, WLOG_ERROR, "expected stub");
415 goto fail;
416 }
417
418 if (StubLength == 4)
419 {
420 if ((header.common.call_id == rpc->PipeCallId) &&
421 (header.common.pfc_flags & PFC_LAST_FRAG))
422 {
423 /* End of TsProxySetupReceivePipe */
424 TerminateEventArgs e;
425 rdpContext* context = transport_get_context(rpc->transport);
426 rdpTsg* tsg = transport_get_tsg(rpc->transport);
427
428 WINPR_ASSERT(context);
429
430 if (Stream_Length(fragment) < StubOffset + 4)
431 goto fail;
432 if (!Stream_SetPosition(fragment, StubOffset))
433 goto fail;
434 Stream_Read_UINT32(fragment, rpc->result);
435
436 utils_abort_connect(context->rdp);
437 tsg_set_state(tsg, TSG_STATE_TUNNEL_CLOSE_PENDING);
438 EventArgsInit(&e, "freerdp");
439 e.code = 0;
440 rc = PubSub_OnTerminate(context->rdp->pubSub, context, &e) >= 0 ? 0 : -1;
441 goto success;
442 }
443
444 if (header.common.call_id != rpc->PipeCallId)
445 {
446 /* Ignoring non-TsProxySetupReceivePipe Response */
447 rc = 0;
448 goto success;
449 }
450 }
451
452 if (rpc->StubFragCount == 0)
453 rpc->StubCallId = header.common.call_id;
454
455 if (rpc->StubCallId != header.common.call_id)
456 {
457 WLog_Print(rpc->log, WLOG_ERROR,
458 "invalid call_id: actual: %" PRIu32 ", expected: %" PRIu32
459 ", frag_count: %" PRIu32 "",
460 rpc->StubCallId, header.common.call_id, rpc->StubFragCount);
461 }
462
463 call = rpc_client_call_find_by_id(rpc->log, rpc->client, rpc->StubCallId);
464
465 if (!call)
466 goto fail;
467
468 if (call->OpNum != TsProxySetupReceivePipeOpnum)
469 {
470 const rpcconn_response_hdr_t* response =
471 (const rpcconn_response_hdr_t*)&header.response;
472 if (!Stream_EnsureRemainingCapacity(pdu->s, StubLength))
473 goto fail;
474
475 if (Stream_Length(fragment) < StubOffset + StubLength)
476 goto fail;
477
478 if (!Stream_SetPosition(fragment, StubOffset))
479 goto fail;
480
481 Stream_Write(pdu->s, Stream_ConstPointer(fragment), StubLength);
482 rpc->StubFragCount++;
483
484 if (response->alloc_hint == StubLength)
485 {
486 pdu->Flags = RPC_PDU_FLAG_STUB;
487 pdu->Type = PTYPE_RESPONSE;
488 pdu->CallId = rpc->StubCallId;
489
490 if (rpc_client_recv_pdu(rpc, pdu) < 0)
491 goto fail;
492 if (!rpc_pdu_reset(pdu))
493 goto fail;
494 rpc->StubFragCount = 0;
495 rpc->StubCallId = 0;
496 }
497 }
498 else
499 {
500 const rpcconn_response_hdr_t* response = &header.response;
501 if (Stream_Length(fragment) < StubOffset + StubLength)
502 goto fail;
503 if (!Stream_SetPosition(fragment, StubOffset))
504 goto fail;
505 if (rpc_client_receive_pipe_write(rpc->client, Stream_ConstPointer(fragment),
506 StubLength) < 0)
507 goto fail;
508 rpc->StubFragCount++;
509
510 if (response->alloc_hint == StubLength)
511 {
512 rpc->StubFragCount = 0;
513 rpc->StubCallId = 0;
514 }
515 }
516
517 goto success;
518 }
519 else if (header.common.ptype == PTYPE_RTS)
520 {
521 if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
522 {
523 pdu->Flags = 0;
524 pdu->Type = header.common.ptype;
525 pdu->CallId = header.common.call_id;
526
527 const size_t len = Stream_Length(fragment);
528 if (!Stream_EnsureRemainingCapacity(pdu->s, len))
529 goto fail;
530
531 Stream_Write(pdu->s, Stream_Buffer(fragment), len);
532
533 if (rpc_client_recv_pdu(rpc, pdu) < 0)
534 goto fail;
535
536 if (!rpc_pdu_reset(pdu))
537 goto fail;
538 }
539 else
540 {
541 if (!rts_recv_out_of_sequence_pdu(rpc, fragment, &header))
542 goto fail;
543 }
544
545 goto success;
546 }
547 else if (header.common.ptype == PTYPE_BIND_ACK ||
548 header.common.ptype == PTYPE_ALTER_CONTEXT_RESP)
549 {
550 pdu->Flags = 0;
551 pdu->Type = header.common.ptype;
552 pdu->CallId = header.common.call_id;
553
554 const size_t len = Stream_Length(fragment);
555 if (!Stream_EnsureRemainingCapacity(pdu->s, len))
556 goto fail;
557
558 Stream_Write(pdu->s, Stream_Buffer(fragment), len);
559
560 if (rpc_client_recv_pdu(rpc, pdu) < 0)
561 goto fail;
562
563 if (!rpc_pdu_reset(pdu))
564 goto fail;
565 goto success;
566 }
567 else if (header.common.ptype == PTYPE_FAULT)
568 {
569 const rpcconn_fault_hdr_t* fault = (const rpcconn_fault_hdr_t*)&header.fault;
570 rpc_recv_fault_pdu(fault->status);
571 goto fail;
572 }
573 else
574 {
575 WLog_Print(rpc->log, WLOG_ERROR, "unexpected RPC PDU type 0x%02" PRIX8 "",
576 header.common.ptype);
577 goto fail;
578 }
579
580success:
581 rc = (rc < 0) ? 1 : 0; /* In case of default error return change to 1, otherwise we already set
582 the return code */
583fail:
584 if (rc < 1)
585 rpc_client_call_discard(rpc->log, rpc->client, header.common.call_id);
586 rts_free_pdu_header(&header, FALSE);
587 return rc;
588}
589
590WINPR_ATTR_NODISCARD
591static SSIZE_T rpc_client_default_out_channel_recv(rdpRpc* rpc)
592{
593 SSIZE_T status = -1;
594 HttpResponse* response = nullptr;
595 RpcInChannel* inChannel = nullptr;
596 RpcOutChannel* outChannel = nullptr;
597 HANDLE outChannelEvent = nullptr;
598 RpcVirtualConnection* connection = rpc->VirtualConnection;
599 inChannel = connection->DefaultInChannel;
600 outChannel = connection->DefaultOutChannel;
601 BIO_get_event(outChannel->common.tls->bio, &outChannelEvent);
602
603 if (outChannel->State < CLIENT_OUT_CHANNEL_STATE_OPENED)
604 {
605 if (WaitForSingleObject(outChannelEvent, 0) != WAIT_OBJECT_0)
606 return 1;
607
608 response = http_response_recv(outChannel->common.tls, TRUE);
609
610 if (!response)
611 return -1;
612
613 if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_SECURITY)
614 {
615 /* Receive OUT Channel Response */
616 if (!rpc_ncacn_http_recv_out_channel_response(&outChannel->common, response))
617 {
618 http_response_free(response);
619 WLog_Print(rpc->log, WLOG_ERROR,
620 "rpc_ncacn_http_recv_out_channel_response failure");
621 return -1;
622 }
623
624 /* Send OUT Channel Request */
625
626 if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, FALSE))
627 {
628 http_response_free(response);
629 WLog_Print(rpc->log, WLOG_ERROR, "rpc_ncacn_http_send_out_channel_request failure");
630 return -1;
631 }
632
633 if (rpc_ncacn_http_is_final_request(&outChannel->common))
634 {
635 rpc_ncacn_http_auth_uninit(&outChannel->common);
636 rpc_out_channel_transition_to_state(outChannel,
637 CLIENT_OUT_CHANNEL_STATE_NEGOTIATED);
638
639 /* Send CONN/A1 PDU over OUT channel */
640
641 if (!rts_send_CONN_A1_pdu(rpc))
642 {
643 http_response_free(response);
644 WLog_Print(rpc->log, WLOG_ERROR, "rpc_send_CONN_A1_pdu error!");
645 return -1;
646 }
647
648 rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_OPENED);
649
650 if (inChannel->State == CLIENT_IN_CHANNEL_STATE_OPENED)
651 {
652 rpc_virtual_connection_transition_to_state(
653 rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
654 }
655 }
656
657 status = 1;
658 }
659
660 http_response_free(response);
661 }
662 else if (connection->State == VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT)
663 {
664 /* Receive OUT channel response */
665 if (WaitForSingleObject(outChannelEvent, 0) != WAIT_OBJECT_0)
666 return 1;
667
668 response = http_response_recv(outChannel->common.tls, FALSE);
669
670 if (!response)
671 return -1;
672
673 const UINT16 statusCode = http_response_get_status_code(response);
674 if (statusCode != HTTP_STATUS_OK)
675 {
676 http_response_log_error_status(rpc->log, WLOG_ERROR, response);
677
678 if (statusCode == HTTP_STATUS_DENIED)
679 {
680 rdpContext* context = transport_get_context(rpc->transport);
681 freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_ACCESS_DENIED);
682 }
683
684 http_response_free(response);
685 return -1;
686 }
687
688 http_response_free(response);
689 rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
690 VIRTUAL_CONNECTION_STATE_WAIT_A3W);
691 status = 1;
692 }
693 else
694 {
695 wStream* fragment = rpc->client->ReceiveFragment;
696
697 while (1)
698 {
699 size_t pos = 0;
700 rpcconn_common_hdr_t header = WINPR_C_ARRAY_INIT;
701
702 while (Stream_GetPosition(fragment) < RPC_COMMON_FIELDS_LENGTH)
703 {
704 status = rpc_channel_read(&outChannel->common, fragment,
705 RPC_COMMON_FIELDS_LENGTH - Stream_GetPosition(fragment));
706
707 if (status < 0)
708 return -1;
709
710 if (Stream_GetPosition(fragment) < RPC_COMMON_FIELDS_LENGTH)
711 return 0;
712 }
713
714 pos = Stream_GetPosition(fragment);
715 Stream_ResetPosition(fragment);
716
717 /* Ignore errors, the PDU might not be complete. */
718 const rts_pdu_status_t rc = rts_read_common_pdu_header(fragment, &header, TRUE);
719 if (rc == RTS_PDU_FAIL)
720 return -1;
721
722 if (!Stream_SetPosition(fragment, pos))
723 return -1;
724
725 if (header.frag_length >= rpc->max_recv_frag)
726 {
727 WLog_Print(rpc->log, WLOG_ERROR,
728 "rpc_client_recv: invalid fragment size: %" PRIu16 " (max: %" PRIu16 ")",
729 header.frag_length, rpc->max_recv_frag);
730 winpr_HexDump(TAG, WLOG_ERROR, Stream_Buffer(fragment),
731 Stream_GetPosition(fragment));
732 return -1;
733 }
734
735 while (Stream_GetPosition(fragment) < header.frag_length)
736 {
737 status = rpc_channel_read(&outChannel->common, fragment,
738 header.frag_length - Stream_GetPosition(fragment));
739
740 if (status < 0)
741 {
742 WLog_Print(rpc->log, WLOG_ERROR, "error reading fragment body");
743 return -1;
744 }
745
746 if (Stream_GetPosition(fragment) < header.frag_length)
747 return 0;
748 }
749
750 {
751 /* complete fragment received */
752 status = rpc_client_recv_fragment(rpc, fragment);
753
754 if (status < 0)
755 return status;
756
757 /* channel recycling may update channel pointers */
758 if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_RECYCLED &&
759 connection->NonDefaultOutChannel)
760 {
761 rpc_channel_free(&connection->DefaultOutChannel->common);
762 connection->DefaultOutChannel = connection->NonDefaultOutChannel;
763 connection->NonDefaultOutChannel = nullptr;
764 rpc_out_channel_transition_to_state(connection->DefaultOutChannel,
765 CLIENT_OUT_CHANNEL_STATE_OPENED);
766 rpc_virtual_connection_transition_to_state(
767 rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
768 return 0;
769 }
770
771 Stream_ResetPosition(fragment);
772 }
773 }
774 }
775
776 return status;
777}
778
779WINPR_ATTR_NODISCARD
780static SSIZE_T rpc_client_nondefault_out_channel_recv(rdpRpc* rpc)
781{
782 SSIZE_T status = -1;
783 HttpResponse* response = nullptr;
784 RpcOutChannel* nextOutChannel = nullptr;
785 HANDLE nextOutChannelEvent = nullptr;
786 nextOutChannel = rpc->VirtualConnection->NonDefaultOutChannel;
787 BIO_get_event(nextOutChannel->common.tls->bio, &nextOutChannelEvent);
788
789 if (WaitForSingleObject(nextOutChannelEvent, 0) != WAIT_OBJECT_0)
790 return 1;
791
792 response = http_response_recv(nextOutChannel->common.tls, TRUE);
793
794 if (response)
795 {
796 switch (nextOutChannel->State)
797 {
798 case CLIENT_OUT_CHANNEL_STATE_SECURITY:
799 if (rpc_ncacn_http_recv_out_channel_response(&nextOutChannel->common, response))
800 {
801 if (rpc_ncacn_http_send_out_channel_request(&nextOutChannel->common, TRUE))
802 {
803 if (rpc_ncacn_http_is_final_request(&nextOutChannel->common))
804 {
805 rpc_ncacn_http_auth_uninit(&nextOutChannel->common);
806
807 if (rts_send_OUT_R1_A3_pdu(rpc))
808 {
809 status = 1;
810 rpc_out_channel_transition_to_state(
811 nextOutChannel, CLIENT_OUT_CHANNEL_STATE_OPENED_A6W);
812 }
813 else
814 {
815 WLog_Print(rpc->log, WLOG_ERROR, "rts_send_OUT_R1/A3_pdu failure");
816 }
817 }
818 else
819 {
820 status = 1;
821 }
822 }
823 else
824 {
825 WLog_Print(rpc->log, WLOG_ERROR,
826 "rpc_ncacn_http_send_out_channel_request failure");
827 }
828 }
829 else
830 {
831 WLog_Print(rpc->log, WLOG_ERROR,
832 "rpc_ncacn_http_recv_out_channel_response failure");
833 }
834
835 break;
836
837 case CLIENT_OUT_CHANNEL_STATE_INITIAL:
838 case CLIENT_OUT_CHANNEL_STATE_CONNECTED:
839 case CLIENT_OUT_CHANNEL_STATE_NEGOTIATED:
840 default:
841 WLog_Print(rpc->log, WLOG_ERROR,
842 "rpc_client_nondefault_out_channel_recv: Unexpected message %08" PRIx32,
843 nextOutChannel->State);
844 status = -1;
845 }
846
847 http_response_free(response);
848 }
849
850 return status;
851}
852
853int rpc_client_out_channel_recv(rdpRpc* rpc)
854{
855 SSIZE_T status = 0;
856 RpcVirtualConnection* connection = rpc->VirtualConnection;
857
858 if (connection->DefaultOutChannel)
859 {
860 status = rpc_client_default_out_channel_recv(rpc);
861
862 if (status < 0)
863 return -1;
864 }
865
866 if (connection->NonDefaultOutChannel)
867 {
868 status = rpc_client_nondefault_out_channel_recv(rpc);
869
870 if (status < 0)
871 return -1;
872 }
873
874 return 1;
875}
876
877int rpc_client_in_channel_recv(rdpRpc* rpc)
878{
879 int status = 1;
880 HttpResponse* response = nullptr;
881 RpcInChannel* inChannel = nullptr;
882 RpcOutChannel* outChannel = nullptr;
883 HANDLE InChannelEvent = nullptr;
884 RpcVirtualConnection* connection = rpc->VirtualConnection;
885 inChannel = connection->DefaultInChannel;
886 outChannel = connection->DefaultOutChannel;
887 BIO_get_event(inChannel->common.tls->bio, &InChannelEvent);
888
889 if (WaitForSingleObject(InChannelEvent, 0) != WAIT_OBJECT_0)
890 return 1;
891
892 if (inChannel->State < CLIENT_IN_CHANNEL_STATE_OPENED)
893 {
894 response = http_response_recv(inChannel->common.tls, TRUE);
895
896 if (!response)
897 return -1;
898
899 if (inChannel->State == CLIENT_IN_CHANNEL_STATE_SECURITY)
900 {
901 if (!rpc_ncacn_http_recv_in_channel_response(&inChannel->common, response))
902 {
903 WLog_Print(rpc->log, WLOG_ERROR, "rpc_ncacn_http_recv_in_channel_response failure");
904 http_response_free(response);
905 return -1;
906 }
907
908 /* Send IN Channel Request */
909
910 if (!rpc_ncacn_http_send_in_channel_request(&inChannel->common))
911 {
912 WLog_Print(rpc->log, WLOG_ERROR, "rpc_ncacn_http_send_in_channel_request failure");
913 http_response_free(response);
914 return -1;
915 }
916
917 if (rpc_ncacn_http_is_final_request(&inChannel->common))
918 {
919 rpc_ncacn_http_auth_uninit(&inChannel->common);
920 rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_NEGOTIATED);
921
922 /* Send CONN/B1 PDU over IN channel */
923
924 if (!rts_send_CONN_B1_pdu(rpc))
925 {
926 WLog_Print(rpc->log, WLOG_ERROR, "rpc_send_CONN_B1_pdu error!");
927 http_response_free(response);
928 return -1;
929 }
930
931 rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_OPENED);
932
933 if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_OPENED)
934 {
935 rpc_virtual_connection_transition_to_state(
936 rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
937 }
938 }
939
940 status = 1;
941 }
942
943 http_response_free(response);
944 }
945 else
946 {
947 response = http_response_recv(inChannel->common.tls, TRUE);
948
949 if (!response)
950 return -1;
951
952 /* We can receive an unauthorized HTTP response on the IN channel */
953 http_response_free(response);
954 }
955
956 return status;
957}
958
964RpcClientCall* rpc_client_call_find_by_id(wLog* log, RpcClient* client, UINT32 CallId)
965{
966 if (!client)
967 return nullptr;
968
969 RpcClientCall* val = HashTable_GetItemValue(client->ClientCallList, &CallId);
970 WLog_Print(log, WLOG_TRACE, "search 0x%08" PRIx32 ": %p {%" PRIuz "}", CallId, (void*)val,
971 HashTable_Count(client->ClientCallList));
972 return val;
973}
974
975BOOL rpc_client_call_add(wLog* log, RpcClient* client, RpcClientCall* call)
976{
977 WINPR_ASSERT(client);
978 if (!call)
979 return FALSE;
980
981 const BOOL rc = HashTable_Insert(client->ClientCallList, &call->CallId, call);
982 WLog_Print(log, WLOG_TRACE, "adding 0x%08" PRIx32 ": %s {%" PRIuz "}", call->CallId,
983 rc ? "success" : "failed", HashTable_Count(client->ClientCallList));
984 return rc;
985}
986
987void rpc_client_call_discard(wLog* log, RpcClient* client, UINT32 CallId)
988{
989 WINPR_ASSERT(client);
990
991 const BOOL rc = HashTable_Remove(client->ClientCallList, &CallId);
992 WLog_Print(log, WLOG_TRACE, "discard 0x%08" PRIx32 ": %s {%" PRIuz "}", CallId,
993 rc ? "success" : "failed", HashTable_Count(client->ClientCallList));
994}
995
996WINPR_ATTR_NODISCARD
997static RPC_CLIENT_CALL_STATE next(RPC_CLIENT_CALL_STATE state)
998{
999 switch (state)
1000 {
1001 case RPC_CLIENT_CALL_STATE_INITIAL:
1002 return RPC_CLIENT_CALL_STATE_SEND_PDUS;
1003 case RPC_CLIENT_CALL_STATE_SEND_PDUS:
1004 return RPC_CLIENT_CALL_STATE_DISPATCHED;
1005 case RPC_CLIENT_CALL_STATE_DISPATCHED:
1006 return RPC_CLIENT_CALL_STATE_RECEIVE_PDU;
1007 case RPC_CLIENT_CALL_STATE_RECEIVE_PDU:
1008 return RPC_CLIENT_CALL_STATE_COMPLETE;
1009 case RPC_CLIENT_CALL_STATE_COMPLETE:
1010 return RPC_CLIENT_CALL_STATE_FINAL;
1011 case RPC_CLIENT_CALL_STATE_FINAL:
1012 return RPC_CLIENT_CALL_STATE_FINAL;
1013
1014 case RPC_CLIENT_CALL_STATE_FAULT:
1015 default:
1016 return RPC_CLIENT_CALL_STATE_FAULT;
1017 }
1018}
1019
1020void rpc_client_call_advance(wLog* log, RpcClient* client, UINT32 CallId,
1021 RPC_CLIENT_CALL_STATE expected, BOOL success)
1022{
1023 WINPR_ASSERT(client);
1024 HashTable_Lock(client->ClientCallList);
1025 RpcClientCall* call = rpc_client_call_find_by_id(log, client, CallId);
1026 if (call)
1027 {
1028 if (success)
1029 call->State = next(call->State);
1030 else
1031 call->State = RPC_CLIENT_CALL_STATE_FAULT;
1032 }
1033
1034 HashTable_Unlock(client->ClientCallList);
1035}
1036
1037RpcClientCall* rpc_client_call_new(UINT32 CallId, UINT32 OpNum)
1038{
1039 RpcClientCall* clientCall = (RpcClientCall*)calloc(1, sizeof(RpcClientCall));
1040
1041 if (!clientCall)
1042 return nullptr;
1043
1044 clientCall->CallId = CallId;
1045 clientCall->OpNum = OpNum;
1046 clientCall->State = RPC_CLIENT_CALL_STATE_INITIAL;
1047 return clientCall;
1048}
1049
1050void rpc_client_call_free(RpcClientCall* clientCall)
1051{
1052 free(clientCall);
1053}
1054
1055static void rpc_array_client_call_free(void* call)
1056{
1057 rpc_client_call_free((RpcClientCall*)call);
1058}
1059
1060int rpc_in_channel_send_pdu(RpcInChannel* inChannel, const BYTE* buffer, size_t length)
1061{
1062 wStream s = Stream_Init();
1063 rpcconn_common_hdr_t header = WINPR_C_ARRAY_INIT;
1064
1065 WINPR_ASSERT(inChannel);
1066
1067 SSIZE_T status = rpc_channel_write(&inChannel->common, buffer, length);
1068
1069 if (status <= 0)
1070 goto fail;
1071
1072 Stream_StaticConstInit(&s, buffer, length);
1073 const rts_pdu_status_t rc = rts_read_common_pdu_header(&s, &header, FALSE);
1074 if (rc != RTS_PDU_VALID)
1075 {
1076 status = -1;
1077 goto fail;
1078 }
1079
1080 /*
1081 * This protocol specifies that only RPC PDUs are subject to the flow control abstract
1082 * data model. RTS PDUs and the HTTP request and response headers are not subject to flow
1083 * control. Implementations of this protocol MUST NOT include them when computing any of the
1084 * variables specified by this abstract data model.
1085 */
1086
1087 if (header.ptype == PTYPE_REQUEST)
1088 {
1089 const uint32_t ustatus = WINPR_ASSERTING_INT_CAST(uint32_t, status);
1090 inChannel->BytesSent += ustatus;
1091 inChannel->SenderAvailableWindow -= ustatus;
1092 }
1093
1094 if (status > INT32_MAX)
1095 status = -1;
1096
1097fail:
1098 rpc_client_call_advance(inChannel->common.rpc->log, inChannel->common.client, header.call_id,
1099 RPC_CLIENT_CALL_STATE_DISPATCHED, status > 0);
1100 return (int)status;
1101}
1102
1103BOOL rpc_client_write_call(rdpRpc* rpc, wStream* s, UINT16 opnum)
1104{
1105 if (!rpc)
1106 return FALSE;
1107
1108 size_t offset = 0;
1109 BYTE* buffer = nullptr;
1110 size_t stub_data_pad = 0;
1111 SecBuffer plaintext;
1112 SecBuffer ciphertext = WINPR_C_ARRAY_INIT;
1113 RpcClientCall* clientCall = nullptr;
1114 rdpCredsspAuth* auth = nullptr;
1115 rpcconn_request_hdr_t request_pdu = WINPR_C_ARRAY_INIT;
1116 RpcVirtualConnection* connection = nullptr;
1117 RpcInChannel* inChannel = nullptr;
1118 BOOL rc = FALSE;
1119
1120 if (!s)
1121 return FALSE;
1122
1123 auth = rpc->auth;
1124 connection = rpc->VirtualConnection;
1125
1126 if (!auth)
1127 {
1128 WLog_Print(rpc->log, WLOG_ERROR, "invalid auth context");
1129 goto fail;
1130 }
1131
1132 if (!connection)
1133 goto fail;
1134
1135 inChannel = connection->DefaultInChannel;
1136
1137 if (!inChannel)
1138 goto fail;
1139
1140 Stream_SealLength(s);
1141
1142 {
1143 const size_t length = Stream_Length(s);
1144 if (length > UINT32_MAX)
1145 goto fail;
1146
1147 {
1148 const size_t asize = credssp_auth_trailer_size(auth);
1149 request_pdu.header = rpc_pdu_header_init(rpc);
1150 request_pdu.header.ptype = PTYPE_REQUEST;
1151 request_pdu.header.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG;
1152 request_pdu.header.auth_length = (UINT16)asize;
1153 }
1154 request_pdu.header.call_id = rpc->CallId++;
1155 request_pdu.alloc_hint = (UINT32)length;
1156 request_pdu.p_cont_id = 0x0000;
1157 request_pdu.opnum = opnum;
1158 clientCall = rpc_client_call_new(request_pdu.header.call_id, request_pdu.opnum);
1159
1160 if (!clientCall)
1161 goto fail;
1162
1163 if (!rpc_client_call_add(rpc->log, rpc->client, clientCall))
1164 {
1165 rpc_client_call_free(clientCall);
1166 goto fail;
1167 }
1168
1169 // NOLINTNEXTLINE(clang-analyzer-unix.Malloc): ArrayList_Append takes ownership
1170 if (request_pdu.opnum == TsProxySetupReceivePipeOpnum)
1171 rpc->PipeCallId = request_pdu.header.call_id;
1172
1173 request_pdu.stub_data = Stream_Buffer(s);
1174 offset = 24;
1175 stub_data_pad = rpc_offset_align(&offset, 8);
1176 offset += length;
1177
1178 {
1179 const size_t alg = rpc_offset_align(&offset, 4);
1180 WINPR_ASSERT(alg <= UINT8_MAX);
1181 request_pdu.auth_verifier.auth_pad_length = (UINT8)alg;
1182 }
1183 request_pdu.auth_verifier.auth_type =
1184 rpc_auth_pkg_to_security_provider(credssp_auth_pkg_name(rpc->auth));
1185 request_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
1186 request_pdu.auth_verifier.auth_reserved = 0x00;
1187 request_pdu.auth_verifier.auth_context_id = 0x00000000;
1188 offset += (8 + request_pdu.header.auth_length);
1189
1190 if (offset > UINT16_MAX)
1191 goto fail;
1192 request_pdu.header.frag_length = (UINT16)offset;
1193 buffer = (BYTE*)calloc(1, request_pdu.header.frag_length);
1194
1195 if (!buffer)
1196 goto fail;
1197
1198 CopyMemory(buffer, &request_pdu, 24);
1199 offset = 24;
1200 rpc_offset_pad(&offset, stub_data_pad);
1201 CopyMemory(&buffer[offset], request_pdu.stub_data, length);
1202 offset += length;
1203 }
1204
1205 rpc_offset_pad(&offset, request_pdu.auth_verifier.auth_pad_length);
1206 CopyMemory(&buffer[offset], &request_pdu.auth_verifier.auth_type, 8);
1207 offset += 8;
1208
1209 if (offset > request_pdu.header.frag_length)
1210 goto fail;
1211
1212 plaintext.pvBuffer = buffer;
1213 plaintext.cbBuffer = (UINT32)offset;
1214 plaintext.BufferType = SECBUFFER_READONLY;
1215
1216 {
1217 size_t size = 0;
1218 if (!credssp_auth_encrypt(auth, &plaintext, &ciphertext, &size, rpc->SendSeqNum++))
1219 goto fail;
1220
1221 if (offset + size > request_pdu.header.frag_length)
1222 {
1223 sspi_SecBufferFree(&ciphertext);
1224 goto fail;
1225 }
1226
1227 CopyMemory(&buffer[offset], ciphertext.pvBuffer, size);
1228 offset += size;
1229 }
1230
1231 sspi_SecBufferFree(&ciphertext);
1232
1233 if (rpc_in_channel_send_pdu(inChannel, buffer, request_pdu.header.frag_length) < 0)
1234 goto fail;
1235
1236 rc = TRUE;
1237fail:
1238 if (!rc)
1239 rpc_client_call_discard(rpc->log, rpc->client, request_pdu.header.call_id);
1240
1241 free(buffer);
1242 Stream_Free(s, TRUE);
1243 return rc;
1244}
1245
1246WINPR_ATTR_NODISCARD
1247static BOOL rpc_client_resolve_gateway(rdpSettings* settings, char** host, UINT16* port,
1248 BOOL* isProxy)
1249{
1250 struct addrinfo* result = nullptr;
1251
1252 if (!settings || !host || !port || !isProxy)
1253 return FALSE;
1254 else
1255 {
1256 const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
1257 const char* proxyUsername = freerdp_settings_get_string(settings, FreeRDP_GatewayUsername);
1258 const char* proxyPassword = freerdp_settings_get_string(settings, FreeRDP_GatewayPassword);
1259 *port = (UINT16)freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
1260 *isProxy = proxy_prepare(settings, &peerHostname, port, &proxyUsername, &proxyPassword);
1261 result = freerdp_tcp_resolve_host(peerHostname, *port, 0);
1262
1263 if (!result)
1264 return FALSE;
1265
1266 *host = freerdp_tcp_address_to_string(
1267 WINPR_PACKED_ALIGN_CAST(const struct sockaddr_storage*, result->ai_addr), nullptr);
1268 freeaddrinfo(result);
1269 return TRUE;
1270 }
1271}
1272
1273WINPR_ATTR_NODISCARD
1274static BOOL compare_uint32(const void* objA, const void* objB)
1275{
1276 if (!objA || !objB)
1277 return FALSE;
1278
1279 const UINT32* a = objA;
1280 const UINT32* b = objB;
1281 return *a == *b;
1282}
1283
1284WINPR_ATTR_NODISCARD
1285static UINT32 hash_uint32(const void* key)
1286{
1287 if (!key)
1288 return 0;
1289 const UINT32* k = key;
1290 return *k;
1291}
1292
1293RpcClient* rpc_client_new(rdpContext* context, UINT32 max_recv_frag)
1294{
1295 wObject* obj = nullptr;
1296 RpcClient* client = (RpcClient*)calloc(1, sizeof(RpcClient));
1297
1298 if (!client)
1299 return nullptr;
1300
1301 if (!rpc_client_resolve_gateway(context->settings, &client->host, &client->port,
1302 &client->isProxy))
1303 goto fail;
1304
1305 client->context = context;
1306
1307 if (!client->context)
1308 goto fail;
1309
1310 client->pdu = rpc_pdu_new();
1311
1312 if (!client->pdu)
1313 goto fail;
1314
1315 client->ReceiveFragment = Stream_New(nullptr, max_recv_frag);
1316
1317 if (!client->ReceiveFragment)
1318 goto fail;
1319
1320 client->PipeEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr);
1321
1322 if (!client->PipeEvent)
1323 goto fail;
1324
1325 if (!ringbuffer_init(&(client->ReceivePipe), 4096))
1326 goto fail;
1327
1328 if (!InitializeCriticalSectionAndSpinCount(&(client->PipeLock), 4000))
1329 goto fail;
1330
1331 client->ClientCallList = HashTable_New(TRUE);
1332
1333 if (!client->ClientCallList)
1334 goto fail;
1335
1336 if (!HashTable_SetHashFunction(client->ClientCallList, hash_uint32))
1337 goto fail;
1338
1339 obj = HashTable_KeyObject(client->ClientCallList);
1340 obj->fnObjectEquals = compare_uint32;
1341 obj = HashTable_ValueObject(client->ClientCallList);
1342 obj->fnObjectFree = rpc_array_client_call_free;
1343 return client;
1344fail:
1345 WINPR_PRAGMA_DIAG_PUSH
1346 WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
1347 rpc_client_free(client);
1348 WINPR_PRAGMA_DIAG_POP
1349 return nullptr;
1350}
1351
1352void rpc_client_free(RpcClient* client)
1353{
1354 if (!client)
1355 return;
1356
1357 free(client->host);
1358
1359 if (client->ReceiveFragment)
1360 Stream_Free(client->ReceiveFragment, TRUE);
1361
1362 if (client->PipeEvent)
1363 (void)CloseHandle(client->PipeEvent);
1364
1365 ringbuffer_destroy(&(client->ReceivePipe));
1366 DeleteCriticalSection(&(client->PipeLock));
1367
1368 if (client->pdu)
1369 rpc_pdu_free(client->pdu);
1370
1371 HashTable_Free(client->ClientCallList);
1372
1373 free(client);
1374}
WINPR_ATTR_NODISCARD FREERDP_API const char * freerdp_settings_get_string(const rdpSettings *settings, FreeRDP_Settings_Keys_String id)
Returns a immutable string settings value.
WINPR_ATTR_NODISCARD FREERDP_API UINT32 freerdp_settings_get_uint32(const rdpSettings *settings, FreeRDP_Settings_Keys_UInt32 id)
Returns a UINT32 settings value.
a piece of data in the ring buffer, exactly like a glibc iovec
Definition ringbuffer.h:44
This struct contains function pointer to initialize/free objects.
Definition collections.h:52
OBJECT_FREE_FN fnObjectFree
Definition collections.h:59
WINPR_ATTR_NODISCARD OBJECT_EQUALS_FN fnObjectEquals
Definition collections.h:61