979 lines
27 KiB
C
979 lines
27 KiB
C
/*
|
||
* FreeRDP: A Remote Desktop Protocol Implementation
|
||
* RPC over HTTP
|
||
*
|
||
* Copyright 2012 Fujitsu Technology Solutions GmbH
|
||
* Copyright 2012 Dmitrij Jasnov <dmitrij.jasnov@ts.fujitsu.com>
|
||
* Copyright 2012 Marc-Andre Moreau <marcandre.moreau@gmail.com>
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
#include <freerdp/config.h>
|
||
|
||
#include "../settings.h"
|
||
|
||
#include <winpr/crt.h>
|
||
#include <winpr/assert.h>
|
||
#include <winpr/tchar.h>
|
||
#include <winpr/synch.h>
|
||
#include <winpr/dsparse.h>
|
||
#include <winpr/crypto.h>
|
||
|
||
#include <freerdp/log.h>
|
||
|
||
#ifdef FREERDP_HAVE_VALGRIND_MEMCHECK_H
|
||
#include <valgrind/memcheck.h>
|
||
#endif
|
||
|
||
#include "../proxy.h"
|
||
#include "http.h"
|
||
#include "../credssp_auth.h"
|
||
#include "ncacn_http.h"
|
||
#include "rpc_bind.h"
|
||
#include "rpc_fault.h"
|
||
#include "rpc_client.h"
|
||
|
||
#include "rpc.h"
|
||
#include "rts.h"
|
||
|
||
#define TAG FREERDP_TAG("core.gateway.rpc")
|
||
|
||
static const char* PTYPE_STRINGS[] = { "PTYPE_REQUEST", "PTYPE_PING",
|
||
"PTYPE_RESPONSE", "PTYPE_FAULT",
|
||
"PTYPE_WORKING", "PTYPE_NOCALL",
|
||
"PTYPE_REJECT", "PTYPE_ACK",
|
||
"PTYPE_CL_CANCEL", "PTYPE_FACK",
|
||
"PTYPE_CANCEL_ACK", "PTYPE_BIND",
|
||
"PTYPE_BIND_ACK", "PTYPE_BIND_NAK",
|
||
"PTYPE_ALTER_CONTEXT", "PTYPE_ALTER_CONTEXT_RESP",
|
||
"PTYPE_RPC_AUTH_3", "PTYPE_SHUTDOWN",
|
||
"PTYPE_CO_CANCEL", "PTYPE_ORPHANED",
|
||
"PTYPE_RTS", "" };
|
||
|
||
static const char* client_in_state_str(CLIENT_IN_CHANNEL_STATE state)
|
||
{
|
||
// NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
|
||
const char* str = "CLIENT_IN_CHANNEL_STATE_UNKNOWN";
|
||
|
||
switch (state)
|
||
{
|
||
case CLIENT_IN_CHANNEL_STATE_INITIAL:
|
||
str = "CLIENT_IN_CHANNEL_STATE_INITIAL";
|
||
break;
|
||
|
||
case CLIENT_IN_CHANNEL_STATE_CONNECTED:
|
||
str = "CLIENT_IN_CHANNEL_STATE_CONNECTED";
|
||
break;
|
||
|
||
case CLIENT_IN_CHANNEL_STATE_SECURITY:
|
||
str = "CLIENT_IN_CHANNEL_STATE_SECURITY";
|
||
break;
|
||
|
||
case CLIENT_IN_CHANNEL_STATE_NEGOTIATED:
|
||
str = "CLIENT_IN_CHANNEL_STATE_NEGOTIATED";
|
||
break;
|
||
|
||
case CLIENT_IN_CHANNEL_STATE_OPENED:
|
||
str = "CLIENT_IN_CHANNEL_STATE_OPENED";
|
||
break;
|
||
|
||
case CLIENT_IN_CHANNEL_STATE_OPENED_A4W:
|
||
str = "CLIENT_IN_CHANNEL_STATE_OPENED_A4W";
|
||
break;
|
||
|
||
case CLIENT_IN_CHANNEL_STATE_FINAL:
|
||
str = "CLIENT_IN_CHANNEL_STATE_FINAL";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
static const char* client_out_state_str(CLIENT_OUT_CHANNEL_STATE state)
|
||
{
|
||
// NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
|
||
const char* str = "CLIENT_OUT_CHANNEL_STATE_UNKNOWN";
|
||
|
||
switch (state)
|
||
{
|
||
case CLIENT_OUT_CHANNEL_STATE_INITIAL:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_INITIAL";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_CONNECTED:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_CONNECTED";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_SECURITY:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_SECURITY";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_NEGOTIATED:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_NEGOTIATED";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_OPENED:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_OPENED";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_OPENED_A6W:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_OPENED_A6W";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_OPENED_A10W:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_OPENED_A10W";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_OPENED_B3W:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_OPENED_B3W";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_RECYCLED:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_RECYCLED";
|
||
break;
|
||
|
||
case CLIENT_OUT_CHANNEL_STATE_FINAL:
|
||
str = "CLIENT_OUT_CHANNEL_STATE_FINAL";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
const char* rpc_vc_state_str(VIRTUAL_CONNECTION_STATE state)
|
||
{
|
||
// NOLINTNEXTLINE(clang-analyzer-deadcode.DeadStores)
|
||
const char* str = "VIRTUAL_CONNECTION_STATE_UNKNOWN";
|
||
|
||
switch (state)
|
||
{
|
||
case VIRTUAL_CONNECTION_STATE_INITIAL:
|
||
str = "VIRTUAL_CONNECTION_STATE_INITIAL";
|
||
break;
|
||
|
||
case VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT:
|
||
str = "VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT";
|
||
break;
|
||
|
||
case VIRTUAL_CONNECTION_STATE_WAIT_A3W:
|
||
str = "VIRTUAL_CONNECTION_STATE_WAIT_A3W";
|
||
break;
|
||
|
||
case VIRTUAL_CONNECTION_STATE_WAIT_C2:
|
||
str = "VIRTUAL_CONNECTION_STATE_WAIT_C2";
|
||
break;
|
||
|
||
case VIRTUAL_CONNECTION_STATE_OPENED:
|
||
str = "VIRTUAL_CONNECTION_STATE_OPENED";
|
||
break;
|
||
|
||
case VIRTUAL_CONNECTION_STATE_FINAL:
|
||
str = "VIRTUAL_CONNECTION_STATE_FINAL";
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return str;
|
||
}
|
||
|
||
/*
|
||
* [MS-RPCH]: Remote Procedure Call over HTTP Protocol Specification:
|
||
* http://msdn.microsoft.com/en-us/library/cc243950/
|
||
*
|
||
*
|
||
*
|
||
* Connection Establishment
|
||
*
|
||
* Client Outbound Proxy Inbound Proxy Server
|
||
* | | | |
|
||
* |-----------------IN Channel Request--------------->| |
|
||
* |---OUT Channel Request-->| |<-Legacy Server Response-|
|
||
* | |<--------------Legacy Server Response--------------|
|
||
* | | | |
|
||
* |---------CONN_A1-------->| | |
|
||
* |----------------------CONN_B1--------------------->| |
|
||
* | |----------------------CONN_A2--------------------->|
|
||
* | | | |
|
||
* |<--OUT Channel Response--| |---------CONN_B2-------->|
|
||
* |<--------CONN_A3---------| | |
|
||
* | |<---------------------CONN_C1----------------------|
|
||
* | | |<--------CONN_B3---------|
|
||
* |<--------CONN_C2---------| | |
|
||
* | | | |
|
||
*
|
||
*/
|
||
|
||
void rpc_pdu_header_print(wLog* log, const rpcconn_hdr_t* header)
|
||
{
|
||
WINPR_ASSERT(header);
|
||
|
||
WLog_Print(log, WLOG_INFO, "rpc_vers: %" PRIu8 "", header->common.rpc_vers);
|
||
WLog_Print(log, WLOG_INFO, "rpc_vers_minor: %" PRIu8 "", header->common.rpc_vers_minor);
|
||
|
||
if (header->common.ptype > PTYPE_RTS)
|
||
WLog_Print(log, WLOG_INFO, "ptype: %s (%" PRIu8 ")", "PTYPE_UNKNOWN", header->common.ptype);
|
||
else
|
||
WLog_Print(log, WLOG_INFO, "ptype: %s (%" PRIu8 ")", PTYPE_STRINGS[header->common.ptype],
|
||
header->common.ptype);
|
||
|
||
WLog_Print(log, WLOG_INFO, "pfc_flags (0x%02" PRIX8 ") = {", header->common.pfc_flags);
|
||
|
||
if (header->common.pfc_flags & PFC_FIRST_FRAG)
|
||
WLog_Print(log, WLOG_INFO, " PFC_FIRST_FRAG");
|
||
|
||
if (header->common.pfc_flags & PFC_LAST_FRAG)
|
||
WLog_Print(log, WLOG_INFO, " PFC_LAST_FRAG");
|
||
|
||
if (header->common.pfc_flags & PFC_PENDING_CANCEL)
|
||
WLog_Print(log, WLOG_INFO, " PFC_PENDING_CANCEL");
|
||
|
||
if (header->common.pfc_flags & PFC_RESERVED_1)
|
||
WLog_Print(log, WLOG_INFO, " PFC_RESERVED_1");
|
||
|
||
if (header->common.pfc_flags & PFC_CONC_MPX)
|
||
WLog_Print(log, WLOG_INFO, " PFC_CONC_MPX");
|
||
|
||
if (header->common.pfc_flags & PFC_DID_NOT_EXECUTE)
|
||
WLog_Print(log, WLOG_INFO, " PFC_DID_NOT_EXECUTE");
|
||
|
||
if (header->common.pfc_flags & PFC_OBJECT_UUID)
|
||
WLog_Print(log, WLOG_INFO, " PFC_OBJECT_UUID");
|
||
|
||
WLog_Print(log, WLOG_INFO, " }");
|
||
WLog_Print(log, WLOG_INFO,
|
||
"packed_drep[4]: %02" PRIX8 " %02" PRIX8 " %02" PRIX8 " %02" PRIX8 "",
|
||
header->common.packed_drep[0], header->common.packed_drep[1],
|
||
header->common.packed_drep[2], header->common.packed_drep[3]);
|
||
WLog_Print(log, WLOG_INFO, "frag_length: %" PRIu16 "", header->common.frag_length);
|
||
WLog_Print(log, WLOG_INFO, "auth_length: %" PRIu16 "", header->common.auth_length);
|
||
WLog_Print(log, WLOG_INFO, "call_id: %" PRIu32 "", header->common.call_id);
|
||
|
||
if (header->common.ptype == PTYPE_RESPONSE)
|
||
{
|
||
WLog_Print(log, WLOG_INFO, "alloc_hint: %" PRIu32 "", header->response.alloc_hint);
|
||
WLog_Print(log, WLOG_INFO, "p_cont_id: %" PRIu16 "", header->response.p_cont_id);
|
||
WLog_Print(log, WLOG_INFO, "cancel_count: %" PRIu8 "", header->response.cancel_count);
|
||
WLog_Print(log, WLOG_INFO, "reserved: %" PRIu8 "", header->response.reserved);
|
||
}
|
||
}
|
||
|
||
rpcconn_common_hdr_t rpc_pdu_header_init(const rdpRpc* rpc)
|
||
{
|
||
rpcconn_common_hdr_t header = { 0 };
|
||
WINPR_ASSERT(rpc);
|
||
|
||
header.rpc_vers = rpc->rpc_vers;
|
||
header.rpc_vers_minor = rpc->rpc_vers_minor;
|
||
header.packed_drep[0] = rpc->packed_drep[0];
|
||
header.packed_drep[1] = rpc->packed_drep[1];
|
||
header.packed_drep[2] = rpc->packed_drep[2];
|
||
header.packed_drep[3] = rpc->packed_drep[3];
|
||
return header;
|
||
}
|
||
|
||
size_t rpc_offset_align(size_t* offset, size_t alignment)
|
||
{
|
||
size_t pad = 0;
|
||
pad = *offset;
|
||
*offset = (*offset + alignment - 1) & ~(alignment - 1);
|
||
pad = *offset - pad;
|
||
return pad;
|
||
}
|
||
|
||
size_t rpc_offset_pad(size_t* offset, size_t pad)
|
||
{
|
||
*offset += pad;
|
||
return pad;
|
||
}
|
||
|
||
/*
|
||
* PDU Segments:
|
||
* ________________________________
|
||
* | |
|
||
* | PDU Header |
|
||
* |________________________________|
|
||
* | |
|
||
* | |
|
||
* | PDU Body |
|
||
* | |
|
||
* |________________________________|
|
||
* | |
|
||
* | Security Trailer |
|
||
* |________________________________|
|
||
* | |
|
||
* | Authentication Token |
|
||
* |________________________________|
|
||
*/
|
||
|
||
/*
|
||
* PDU Structure with verification trailer
|
||
*
|
||
* MUST only appear in a request PDU!
|
||
* ________________________________
|
||
* | |
|
||
* | PDU Header |
|
||
* |________________________________| _______
|
||
* | | /|\
|
||
* | | |
|
||
* | Stub Data | |
|
||
* | | |
|
||
* |________________________________| |
|
||
* | | PDU Body
|
||
* | Stub Pad | |
|
||
* |________________________________| |
|
||
* | | |
|
||
* | Verification Trailer | |
|
||
* |________________________________| |
|
||
* | | |
|
||
* | Authentication Pad | |
|
||
* |________________________________| __\|/__
|
||
* | |
|
||
* | Security Trailer |
|
||
* |________________________________|
|
||
* | |
|
||
* | Authentication Token |
|
||
* |________________________________|
|
||
*
|
||
*/
|
||
|
||
/*
|
||
* Security Trailer:
|
||
*
|
||
* The sec_trailer structure MUST be placed at the end of the PDU, including past stub data,
|
||
* when present. The sec_trailer structure MUST be 4-byte aligned with respect to the beginning
|
||
* of the PDU. Padding octets MUST be used to align the sec_trailer structure if its natural
|
||
* beginning is not already 4-byte aligned.
|
||
*
|
||
* All PDUs that carry sec_trailer information share certain common fields:
|
||
* frag_length and auth_length. The beginning of the sec_trailer structure for each PDU MUST be
|
||
* calculated to start from offset (frag_length – auth_length – 8) from the beginning of the PDU.
|
||
*
|
||
* Immediately after the sec_trailer structure, there MUST be a BLOB carrying the authentication
|
||
* information produced by the security provider. This BLOB is called the authentication token and
|
||
* MUST be of size auth_length. The size MUST also be equal to the length from the first octet
|
||
* immediately after the sec_trailer structure all the way to the end of the fragment;
|
||
* the two values MUST be the same.
|
||
*
|
||
* A client or a server that (during composing of a PDU) has allocated more space for the
|
||
* authentication token than the security provider fills in SHOULD fill in the rest of
|
||
* the allocated space with zero octets. These zero octets are still considered to belong
|
||
* to the authentication token part of the PDU.
|
||
*
|
||
*/
|
||
|
||
BOOL rpc_get_stub_data_info(rdpRpc* rpc, const rpcconn_hdr_t* header, size_t* poffset,
|
||
size_t* length)
|
||
{
|
||
size_t used = 0;
|
||
size_t offset = 0;
|
||
BOOL rc = FALSE;
|
||
UINT32 frag_length = 0;
|
||
UINT32 auth_length = 0;
|
||
UINT32 auth_pad_length = 0;
|
||
UINT32 sec_trailer_offset = 0;
|
||
const rpc_sec_trailer* sec_trailer = NULL;
|
||
|
||
WINPR_ASSERT(rpc);
|
||
WINPR_ASSERT(header);
|
||
WINPR_ASSERT(poffset);
|
||
WINPR_ASSERT(length);
|
||
|
||
offset = RPC_COMMON_FIELDS_LENGTH;
|
||
|
||
switch (header->common.ptype)
|
||
{
|
||
case PTYPE_RESPONSE:
|
||
offset += 8;
|
||
rpc_offset_align(&offset, 8);
|
||
sec_trailer = &header->response.auth_verifier;
|
||
break;
|
||
|
||
case PTYPE_REQUEST:
|
||
offset += 4;
|
||
rpc_offset_align(&offset, 8);
|
||
sec_trailer = &header->request.auth_verifier;
|
||
break;
|
||
|
||
case PTYPE_RTS:
|
||
offset += 4;
|
||
break;
|
||
|
||
default:
|
||
WLog_Print(rpc->log, WLOG_ERROR, "Unknown PTYPE: 0x%02" PRIX8 "", header->common.ptype);
|
||
goto fail;
|
||
}
|
||
|
||
frag_length = header->common.frag_length;
|
||
auth_length = header->common.auth_length;
|
||
|
||
if (poffset)
|
||
*poffset = offset;
|
||
|
||
/* The fragment must be larger than the authentication trailer */
|
||
used = offset + auth_length + 8ull;
|
||
if (sec_trailer)
|
||
{
|
||
auth_pad_length = sec_trailer->auth_pad_length;
|
||
used += sec_trailer->auth_pad_length;
|
||
}
|
||
|
||
if (frag_length < used)
|
||
goto fail;
|
||
|
||
if (!length)
|
||
return TRUE;
|
||
|
||
sec_trailer_offset = frag_length - auth_length - 8;
|
||
|
||
/*
|
||
* According to [MS-RPCE], auth_pad_length is the number of padding
|
||
* octets used to 4-byte align the security trailer, but in practice
|
||
* we get values up to 15, which indicates 16-byte alignment.
|
||
*/
|
||
|
||
if ((frag_length - (sec_trailer_offset + 8)) != auth_length)
|
||
{
|
||
WLog_Print(rpc->log, WLOG_ERROR,
|
||
"invalid auth_length: actual: %" PRIu32 ", expected: %" PRIu32 "", auth_length,
|
||
(frag_length - (sec_trailer_offset + 8)));
|
||
}
|
||
|
||
*length = sec_trailer_offset - auth_pad_length - offset;
|
||
|
||
rc = TRUE;
|
||
fail:
|
||
return rc;
|
||
}
|
||
|
||
SSIZE_T rpc_channel_read(RpcChannel* channel, wStream* s, size_t length)
|
||
{
|
||
int status = 0;
|
||
|
||
if (!channel || (length > INT32_MAX))
|
||
return -1;
|
||
|
||
ERR_clear_error();
|
||
status = BIO_read(channel->tls->bio, Stream_Pointer(s), (INT32)length);
|
||
|
||
if (status > 0)
|
||
{
|
||
Stream_Seek(s, (size_t)status);
|
||
return status;
|
||
}
|
||
|
||
if (BIO_should_retry(channel->tls->bio))
|
||
return 0;
|
||
|
||
WLog_Print(channel->rpc->log, WLOG_ERROR, "rpc_channel_read: Out of retries");
|
||
return -1;
|
||
}
|
||
|
||
SSIZE_T rpc_channel_write(RpcChannel* channel, const BYTE* data, size_t length)
|
||
{
|
||
int status = 0;
|
||
|
||
if (!channel || (length > INT32_MAX))
|
||
return -1;
|
||
|
||
status = freerdp_tls_write_all(channel->tls, data, (INT32)length);
|
||
return status;
|
||
}
|
||
|
||
BOOL rpc_in_channel_transition_to_state(RpcInChannel* inChannel, CLIENT_IN_CHANNEL_STATE state)
|
||
{
|
||
if (!inChannel)
|
||
return FALSE;
|
||
|
||
inChannel->State = state;
|
||
WLog_Print(inChannel->common.rpc->log, WLOG_DEBUG, "%s", client_in_state_str(state));
|
||
return TRUE;
|
||
}
|
||
|
||
static int rpc_channel_rpch_init(RpcClient* client, RpcChannel* channel, const char* inout,
|
||
const GUID* guid)
|
||
{
|
||
HttpContext* http = NULL;
|
||
rdpSettings* settings = NULL;
|
||
UINT32 timeout = 0;
|
||
|
||
if (!client || !channel || !inout || !client->context || !client->context->settings)
|
||
return -1;
|
||
|
||
settings = client->context->settings;
|
||
channel->auth = credssp_auth_new(client->context);
|
||
rts_generate_cookie((BYTE*)&channel->Cookie);
|
||
channel->client = client;
|
||
|
||
if (!channel->auth)
|
||
return -1;
|
||
|
||
channel->http = http_context_new();
|
||
|
||
if (!channel->http)
|
||
return -1;
|
||
|
||
http = channel->http;
|
||
|
||
{
|
||
if (!http_context_set_pragma(http, "ResourceTypeUuid=44e265dd-7daf-42cd-8560-3cdb6e7a2729"))
|
||
return -1;
|
||
|
||
if (guid)
|
||
{
|
||
char* strguid = NULL;
|
||
RPC_STATUS rpcStatus = UuidToStringA(guid, &strguid);
|
||
|
||
if (rpcStatus != RPC_S_OK)
|
||
return -1;
|
||
|
||
const BOOL rc = http_context_append_pragma(http, "SessionId=%s", strguid);
|
||
RpcStringFreeA(&strguid);
|
||
if (!rc)
|
||
return -1;
|
||
}
|
||
if (timeout)
|
||
{
|
||
if (!http_context_append_pragma(http, "MinConnTimeout=%" PRIu32, timeout))
|
||
return -1;
|
||
}
|
||
|
||
if (!http_context_set_rdg_correlation_id(http, guid) ||
|
||
!http_context_set_rdg_connection_id(http, guid))
|
||
return -1;
|
||
}
|
||
|
||
/* TODO: "/rpcwithcert/rpcproxy.dll". */
|
||
if (!http_context_set_method(http, inout) ||
|
||
!http_context_set_uri(http, "/rpc/rpcproxy.dll?localhost:3388") ||
|
||
!http_context_set_accept(http, "application/rpc") ||
|
||
!http_context_set_cache_control(http, "no-cache") ||
|
||
!http_context_set_connection(http, "Keep-Alive") ||
|
||
!http_context_set_user_agent(http, "MSRPC") ||
|
||
!http_context_set_host(http, settings->GatewayHostname))
|
||
return -1;
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int rpc_in_channel_init(rdpRpc* rpc, RpcInChannel* inChannel, const GUID* guid)
|
||
{
|
||
WINPR_ASSERT(rpc);
|
||
WINPR_ASSERT(inChannel);
|
||
|
||
inChannel->common.rpc = rpc;
|
||
inChannel->State = CLIENT_IN_CHANNEL_STATE_INITIAL;
|
||
inChannel->BytesSent = 0;
|
||
inChannel->SenderAvailableWindow = rpc->ReceiveWindow;
|
||
inChannel->PingOriginator.ConnectionTimeout = 30;
|
||
inChannel->PingOriginator.KeepAliveInterval = 0;
|
||
|
||
if (rpc_channel_rpch_init(rpc->client, &inChannel->common, "RPC_IN_DATA", guid) < 0)
|
||
return -1;
|
||
|
||
return 1;
|
||
}
|
||
|
||
static RpcInChannel* rpc_in_channel_new(rdpRpc* rpc, const GUID* guid)
|
||
{
|
||
RpcInChannel* inChannel = (RpcInChannel*)calloc(1, sizeof(RpcInChannel));
|
||
|
||
if (inChannel)
|
||
{
|
||
rpc_in_channel_init(rpc, inChannel, guid);
|
||
}
|
||
|
||
return inChannel;
|
||
}
|
||
|
||
void rpc_channel_free(RpcChannel* channel)
|
||
{
|
||
if (!channel)
|
||
return;
|
||
|
||
credssp_auth_free(channel->auth);
|
||
http_context_free(channel->http);
|
||
freerdp_tls_free(channel->tls);
|
||
free(channel);
|
||
}
|
||
|
||
BOOL rpc_out_channel_transition_to_state(RpcOutChannel* outChannel, CLIENT_OUT_CHANNEL_STATE state)
|
||
{
|
||
if (!outChannel)
|
||
return FALSE;
|
||
|
||
outChannel->State = state;
|
||
WLog_Print(outChannel->common.rpc->log, WLOG_DEBUG, "%s", client_out_state_str(state));
|
||
return TRUE;
|
||
}
|
||
|
||
static int rpc_out_channel_init(rdpRpc* rpc, RpcOutChannel* outChannel, const GUID* guid)
|
||
{
|
||
WINPR_ASSERT(rpc);
|
||
WINPR_ASSERT(outChannel);
|
||
|
||
outChannel->common.rpc = rpc;
|
||
outChannel->State = CLIENT_OUT_CHANNEL_STATE_INITIAL;
|
||
outChannel->BytesReceived = 0;
|
||
outChannel->ReceiverAvailableWindow = rpc->ReceiveWindow;
|
||
outChannel->ReceiveWindow = rpc->ReceiveWindow;
|
||
outChannel->ReceiveWindowSize = rpc->ReceiveWindow;
|
||
outChannel->AvailableWindowAdvertised = rpc->ReceiveWindow;
|
||
|
||
if (rpc_channel_rpch_init(rpc->client, &outChannel->common, "RPC_OUT_DATA", guid) < 0)
|
||
return -1;
|
||
|
||
return 1;
|
||
}
|
||
|
||
RpcOutChannel* rpc_out_channel_new(rdpRpc* rpc, const GUID* guid)
|
||
{
|
||
RpcOutChannel* outChannel = (RpcOutChannel*)calloc(1, sizeof(RpcOutChannel));
|
||
|
||
if (outChannel)
|
||
{
|
||
rpc_out_channel_init(rpc, outChannel, guid);
|
||
}
|
||
|
||
return outChannel;
|
||
}
|
||
|
||
BOOL rpc_virtual_connection_transition_to_state(rdpRpc* rpc, RpcVirtualConnection* connection,
|
||
VIRTUAL_CONNECTION_STATE state)
|
||
{
|
||
if (!connection)
|
||
return FALSE;
|
||
|
||
WINPR_ASSERT(rpc);
|
||
connection->State = state;
|
||
WLog_Print(rpc->log, WLOG_DEBUG, "%s", rpc_vc_state_str(state));
|
||
return TRUE;
|
||
}
|
||
|
||
static void rpc_virtual_connection_free(RpcVirtualConnection* connection)
|
||
{
|
||
if (!connection)
|
||
return;
|
||
|
||
if (connection->DefaultInChannel)
|
||
rpc_channel_free(&connection->DefaultInChannel->common);
|
||
if (connection->NonDefaultInChannel)
|
||
rpc_channel_free(&connection->NonDefaultInChannel->common);
|
||
if (connection->DefaultOutChannel)
|
||
rpc_channel_free(&connection->DefaultOutChannel->common);
|
||
if (connection->NonDefaultOutChannel)
|
||
rpc_channel_free(&connection->NonDefaultOutChannel->common);
|
||
free(connection);
|
||
}
|
||
|
||
static RpcVirtualConnection* rpc_virtual_connection_new(rdpRpc* rpc)
|
||
{
|
||
WINPR_ASSERT(rpc);
|
||
|
||
RpcVirtualConnection* connection =
|
||
(RpcVirtualConnection*)calloc(1, sizeof(RpcVirtualConnection));
|
||
|
||
if (!connection)
|
||
return NULL;
|
||
|
||
rts_generate_cookie((BYTE*)&(connection->Cookie));
|
||
rts_generate_cookie((BYTE*)&(connection->AssociationGroupId));
|
||
connection->State = VIRTUAL_CONNECTION_STATE_INITIAL;
|
||
|
||
connection->DefaultInChannel = rpc_in_channel_new(rpc, &connection->Cookie);
|
||
|
||
if (!connection->DefaultInChannel)
|
||
goto fail;
|
||
|
||
connection->DefaultOutChannel = rpc_out_channel_new(rpc, &connection->Cookie);
|
||
|
||
if (!connection->DefaultOutChannel)
|
||
goto fail;
|
||
|
||
return connection;
|
||
fail:
|
||
rpc_virtual_connection_free(connection);
|
||
return NULL;
|
||
}
|
||
|
||
static BOOL rpc_channel_tls_connect(RpcChannel* channel, UINT32 timeout)
|
||
{
|
||
if (!channel || !channel->client || !channel->client->context ||
|
||
!channel->client->context->settings)
|
||
return FALSE;
|
||
|
||
rdpContext* context = channel->client->context;
|
||
WINPR_ASSERT(context);
|
||
|
||
rdpSettings* settings = context->settings;
|
||
WINPR_ASSERT(settings);
|
||
|
||
const char* proxyUsername = freerdp_settings_get_string(settings, FreeRDP_ProxyUsername);
|
||
const char* proxyPassword = freerdp_settings_get_string(settings, FreeRDP_ProxyPassword);
|
||
|
||
rdpTransport* transport = freerdp_get_transport(context);
|
||
rdpTransportLayer* layer =
|
||
transport_connect_layer(transport, channel->client->host, channel->client->port, timeout);
|
||
|
||
if (!layer)
|
||
return FALSE;
|
||
|
||
BIO* layerBio = BIO_new(BIO_s_transport_layer());
|
||
if (!layerBio)
|
||
{
|
||
transport_layer_free(layer);
|
||
return FALSE;
|
||
}
|
||
BIO_set_data(layerBio, layer);
|
||
|
||
BIO* bufferedBio = BIO_new(BIO_s_buffered_socket());
|
||
if (!bufferedBio)
|
||
{
|
||
BIO_free_all(layerBio);
|
||
return FALSE;
|
||
}
|
||
|
||
bufferedBio = BIO_push(bufferedBio, layerBio);
|
||
|
||
if (!BIO_set_nonblock(bufferedBio, TRUE))
|
||
{
|
||
BIO_free_all(bufferedBio);
|
||
return FALSE;
|
||
}
|
||
|
||
if (channel->client->isProxy)
|
||
{
|
||
if (!proxy_connect(context, bufferedBio, proxyUsername, proxyPassword,
|
||
settings->GatewayHostname, settings->GatewayPort))
|
||
{
|
||
BIO_free_all(bufferedBio);
|
||
return FALSE;
|
||
}
|
||
}
|
||
|
||
channel->bio = bufferedBio;
|
||
rdpTls* tls = channel->tls = freerdp_tls_new(context);
|
||
|
||
if (!tls)
|
||
return FALSE;
|
||
|
||
tls->hostname = settings->GatewayHostname;
|
||
tls->port = settings->GatewayPort;
|
||
tls->isGatewayTransport = TRUE;
|
||
int tlsStatus = freerdp_tls_connect(tls, bufferedBio);
|
||
|
||
if (tlsStatus < 1)
|
||
{
|
||
if (tlsStatus < 0)
|
||
{
|
||
freerdp_set_last_error_if_not(context, FREERDP_ERROR_TLS_CONNECT_FAILED);
|
||
}
|
||
else
|
||
{
|
||
freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_CANCELLED);
|
||
}
|
||
|
||
return FALSE;
|
||
}
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
static int rpc_in_channel_connect(RpcInChannel* inChannel, UINT32 timeout)
|
||
{
|
||
rdpContext* context = NULL;
|
||
|
||
if (!inChannel || !inChannel->common.client || !inChannel->common.client->context)
|
||
return -1;
|
||
|
||
context = inChannel->common.client->context;
|
||
|
||
/* Connect IN Channel */
|
||
|
||
if (!rpc_channel_tls_connect(&inChannel->common, timeout))
|
||
return -1;
|
||
|
||
rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_CONNECTED);
|
||
|
||
if (!rpc_ncacn_http_auth_init(context, &inChannel->common))
|
||
return -1;
|
||
|
||
/* Send IN Channel Request */
|
||
|
||
if (!rpc_ncacn_http_send_in_channel_request(&inChannel->common))
|
||
{
|
||
WLog_Print(inChannel->common.rpc->log, WLOG_ERROR,
|
||
"rpc_ncacn_http_send_in_channel_request failure");
|
||
return -1;
|
||
}
|
||
|
||
if (!rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_SECURITY))
|
||
return -1;
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int rpc_out_channel_connect(RpcOutChannel* outChannel, int timeout)
|
||
{
|
||
rdpContext* context = NULL;
|
||
|
||
if (!outChannel || !outChannel->common.client || !outChannel->common.client->context)
|
||
return -1;
|
||
|
||
context = outChannel->common.client->context;
|
||
|
||
/* Connect OUT Channel */
|
||
|
||
if (!rpc_channel_tls_connect(&outChannel->common, timeout))
|
||
return -1;
|
||
|
||
rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_CONNECTED);
|
||
|
||
if (!rpc_ncacn_http_auth_init(context, &outChannel->common))
|
||
return FALSE;
|
||
|
||
/* Send OUT Channel Request */
|
||
|
||
if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, FALSE))
|
||
{
|
||
WLog_Print(outChannel->common.rpc->log, WLOG_ERROR,
|
||
"rpc_ncacn_http_send_out_channel_request failure");
|
||
return FALSE;
|
||
}
|
||
|
||
rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_SECURITY);
|
||
return 1;
|
||
}
|
||
|
||
int rpc_out_channel_replacement_connect(RpcOutChannel* outChannel, int timeout)
|
||
{
|
||
rdpContext* context = NULL;
|
||
|
||
if (!outChannel || !outChannel->common.client || !outChannel->common.client->context)
|
||
return -1;
|
||
|
||
context = outChannel->common.client->context;
|
||
|
||
/* Connect OUT Channel */
|
||
|
||
if (!rpc_channel_tls_connect(&outChannel->common, timeout))
|
||
return -1;
|
||
|
||
rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_CONNECTED);
|
||
|
||
if (!rpc_ncacn_http_auth_init(context, (RpcChannel*)outChannel))
|
||
return FALSE;
|
||
|
||
/* Send OUT Channel Request */
|
||
|
||
if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, TRUE))
|
||
{
|
||
WLog_Print(outChannel->common.rpc->log, WLOG_ERROR,
|
||
"rpc_ncacn_http_send_out_channel_request failure");
|
||
return FALSE;
|
||
}
|
||
|
||
rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_SECURITY);
|
||
return 1;
|
||
}
|
||
|
||
BOOL rpc_connect(rdpRpc* rpc, UINT32 timeout)
|
||
{
|
||
RpcInChannel* inChannel = NULL;
|
||
RpcOutChannel* outChannel = NULL;
|
||
RpcVirtualConnection* connection = NULL;
|
||
rpc->VirtualConnection = rpc_virtual_connection_new(rpc);
|
||
|
||
if (!rpc->VirtualConnection)
|
||
return FALSE;
|
||
|
||
connection = rpc->VirtualConnection;
|
||
inChannel = connection->DefaultInChannel;
|
||
outChannel = connection->DefaultOutChannel;
|
||
rpc_virtual_connection_transition_to_state(rpc, connection, VIRTUAL_CONNECTION_STATE_INITIAL);
|
||
|
||
if (rpc_in_channel_connect(inChannel, timeout) < 0)
|
||
return FALSE;
|
||
|
||
if (rpc_out_channel_connect(outChannel, timeout) < 0)
|
||
return FALSE;
|
||
|
||
return TRUE;
|
||
}
|
||
|
||
rdpRpc* rpc_new(rdpTransport* transport)
|
||
{
|
||
rdpContext* context = transport_get_context(transport);
|
||
rdpRpc* rpc = NULL;
|
||
|
||
WINPR_ASSERT(context);
|
||
|
||
rpc = (rdpRpc*)calloc(1, sizeof(rdpRpc));
|
||
|
||
if (!rpc)
|
||
return NULL;
|
||
|
||
rpc->log = WLog_Get(TAG);
|
||
rpc->State = RPC_CLIENT_STATE_INITIAL;
|
||
rpc->transport = transport;
|
||
rpc->SendSeqNum = 0;
|
||
rpc->auth = credssp_auth_new(context);
|
||
|
||
if (!rpc->auth)
|
||
goto out_free;
|
||
|
||
rpc->PipeCallId = 0;
|
||
rpc->StubCallId = 0;
|
||
rpc->StubFragCount = 0;
|
||
rpc->rpc_vers = 5;
|
||
rpc->rpc_vers_minor = 0;
|
||
/* little-endian data representation */
|
||
rpc->packed_drep[0] = 0x10;
|
||
rpc->packed_drep[1] = 0x00;
|
||
rpc->packed_drep[2] = 0x00;
|
||
rpc->packed_drep[3] = 0x00;
|
||
rpc->max_xmit_frag = 0x0FF8;
|
||
rpc->max_recv_frag = 0x0FF8;
|
||
rpc->ReceiveWindow = 0x00010000;
|
||
rpc->ChannelLifetime = 0x40000000;
|
||
rpc->KeepAliveInterval = 300000;
|
||
rpc->CurrentKeepAliveInterval = rpc->KeepAliveInterval;
|
||
rpc->CurrentKeepAliveTime = 0;
|
||
rpc->CallId = 2;
|
||
rpc->client = rpc_client_new(context, rpc->max_recv_frag);
|
||
|
||
if (!rpc->client)
|
||
goto out_free;
|
||
|
||
return rpc;
|
||
out_free:
|
||
WINPR_PRAGMA_DIAG_PUSH
|
||
WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC
|
||
rpc_free(rpc);
|
||
WINPR_PRAGMA_DIAG_POP
|
||
return NULL;
|
||
}
|
||
|
||
void rpc_free(rdpRpc* rpc)
|
||
{
|
||
if (rpc)
|
||
{
|
||
rpc_client_free(rpc->client);
|
||
credssp_auth_free(rpc->auth);
|
||
rpc_virtual_connection_free(rpc->VirtualConnection);
|
||
free(rpc);
|
||
}
|
||
}
|