892cbe3261
Covscan report contains various memory leak defects which were marked as important. I have spent some time analyzing them and although they were marked as important, most of them are in error cases, so probably nothing serious. Let's fix most of them anyway. The rest are false positives, or too complicated to fix, or already fixed in master, or simply I am unsure about them. Relates: https://github.com/FreeRDP/FreeRDP/issues/6981
1121 lines
27 KiB
C
1121 lines
27 KiB
C
/**
|
|
* FreeRDP: A Remote Desktop Protocol Implementation
|
|
* RPC Client
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <freerdp/log.h>
|
|
|
|
#include <winpr/crt.h>
|
|
#include <winpr/print.h>
|
|
#include <winpr/synch.h>
|
|
#include <winpr/thread.h>
|
|
#include <winpr/stream.h>
|
|
|
|
#include "http.h"
|
|
#include "ncacn_http.h"
|
|
|
|
#include "rpc_bind.h"
|
|
#include "rpc_fault.h"
|
|
#include "rpc_client.h"
|
|
#include "../rdp.h"
|
|
#include "../proxy.h"
|
|
|
|
#define TAG FREERDP_TAG("core.gateway.rpc")
|
|
|
|
static void rpc_pdu_reset(RPC_PDU* pdu)
|
|
{
|
|
pdu->Type = 0;
|
|
pdu->Flags = 0;
|
|
pdu->CallId = 0;
|
|
Stream_SetPosition(pdu->s, 0);
|
|
}
|
|
|
|
static RPC_PDU* rpc_pdu_new(void)
|
|
{
|
|
RPC_PDU* pdu;
|
|
pdu = (RPC_PDU*)malloc(sizeof(RPC_PDU));
|
|
|
|
if (!pdu)
|
|
return NULL;
|
|
|
|
pdu->s = Stream_New(NULL, 4096);
|
|
|
|
if (!pdu->s)
|
|
{
|
|
free(pdu);
|
|
return NULL;
|
|
}
|
|
|
|
rpc_pdu_reset(pdu);
|
|
return pdu;
|
|
}
|
|
|
|
static void rpc_pdu_free(RPC_PDU* pdu)
|
|
{
|
|
if (!pdu)
|
|
return;
|
|
|
|
Stream_Free(pdu->s, TRUE);
|
|
free(pdu);
|
|
}
|
|
|
|
static int rpc_client_receive_pipe_write(RpcClient* client, const BYTE* buffer, size_t length)
|
|
{
|
|
int status = 0;
|
|
|
|
if (!client || !buffer)
|
|
return -1;
|
|
|
|
EnterCriticalSection(&(client->PipeLock));
|
|
|
|
if (ringbuffer_write(&(client->ReceivePipe), buffer, length))
|
|
status += (int)length;
|
|
|
|
if (ringbuffer_used(&(client->ReceivePipe)) > 0)
|
|
SetEvent(client->PipeEvent);
|
|
|
|
LeaveCriticalSection(&(client->PipeLock));
|
|
return status;
|
|
}
|
|
|
|
int rpc_client_receive_pipe_read(RpcClient* client, BYTE* buffer, size_t length)
|
|
{
|
|
int index = 0;
|
|
int status = 0;
|
|
int nchunks = 0;
|
|
DataChunk chunks[2];
|
|
|
|
if (!client || !buffer)
|
|
return -1;
|
|
|
|
EnterCriticalSection(&(client->PipeLock));
|
|
nchunks = ringbuffer_peek(&(client->ReceivePipe), chunks, length);
|
|
|
|
for (index = 0; index < nchunks; index++)
|
|
{
|
|
CopyMemory(&buffer[status], chunks[index].data, chunks[index].size);
|
|
status += chunks[index].size;
|
|
}
|
|
|
|
if (status > 0)
|
|
ringbuffer_commit_read_bytes(&(client->ReceivePipe), status);
|
|
|
|
if (ringbuffer_used(&(client->ReceivePipe)) < 1)
|
|
ResetEvent(client->PipeEvent);
|
|
|
|
LeaveCriticalSection(&(client->PipeLock));
|
|
return status;
|
|
}
|
|
|
|
static int rpc_client_transition_to_state(rdpRpc* rpc, RPC_CLIENT_STATE state)
|
|
{
|
|
int status = 1;
|
|
const char* str = "RPC_CLIENT_STATE_UNKNOWN";
|
|
|
|
switch (state)
|
|
{
|
|
case RPC_CLIENT_STATE_INITIAL:
|
|
str = "RPC_CLIENT_STATE_INITIAL";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_ESTABLISHED:
|
|
str = "RPC_CLIENT_STATE_ESTABLISHED";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK:
|
|
str = "RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK:
|
|
str = "RPC_CLIENT_STATE_WAIT_UNSECURE_BIND_ACK";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE:
|
|
str = "RPC_CLIENT_STATE_WAIT_SECURE_ALTER_CONTEXT_RESPONSE";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_CONTEXT_NEGOTIATED:
|
|
str = "RPC_CLIENT_STATE_CONTEXT_NEGOTIATED";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_WAIT_RESPONSE:
|
|
str = "RPC_CLIENT_STATE_WAIT_RESPONSE";
|
|
break;
|
|
|
|
case RPC_CLIENT_STATE_FINAL:
|
|
str = "RPC_CLIENT_STATE_FINAL";
|
|
break;
|
|
}
|
|
|
|
rpc->State = state;
|
|
WLog_DBG(TAG, "%s", str);
|
|
return status;
|
|
}
|
|
|
|
static int rpc_client_recv_pdu(rdpRpc* rpc, RPC_PDU* pdu)
|
|
{
|
|
int status = -1;
|
|
rpcconn_rts_hdr_t* rts;
|
|
rdpTsg* tsg = rpc->transport->tsg;
|
|
|
|
if (rpc->VirtualConnection->State < VIRTUAL_CONNECTION_STATE_OPENED)
|
|
{
|
|
switch (rpc->VirtualConnection->State)
|
|
{
|
|
case VIRTUAL_CONNECTION_STATE_INITIAL:
|
|
break;
|
|
|
|
case VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT:
|
|
break;
|
|
|
|
case VIRTUAL_CONNECTION_STATE_WAIT_A3W:
|
|
rts = (rpcconn_rts_hdr_t*)Stream_Buffer(pdu->s);
|
|
|
|
if (!rts_match_pdu_signature(&RTS_PDU_CONN_A3_SIGNATURE, rts))
|
|
{
|
|
WLog_ERR(TAG, "unexpected RTS PDU: Expected CONN/A3");
|
|
return -1;
|
|
}
|
|
|
|
status = rts_recv_CONN_A3_pdu(rpc, Stream_Buffer(pdu->s), Stream_Length(pdu->s));
|
|
|
|
if (status < 0)
|
|
{
|
|
WLog_ERR(TAG, "rts_recv_CONN_A3_pdu failure");
|
|
return -1;
|
|
}
|
|
|
|
rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
|
|
VIRTUAL_CONNECTION_STATE_WAIT_C2);
|
|
status = 1;
|
|
break;
|
|
|
|
case VIRTUAL_CONNECTION_STATE_WAIT_C2:
|
|
rts = (rpcconn_rts_hdr_t*)Stream_Buffer(pdu->s);
|
|
|
|
if (!rts_match_pdu_signature(&RTS_PDU_CONN_C2_SIGNATURE, rts))
|
|
{
|
|
WLog_ERR(TAG, "unexpected RTS PDU: Expected CONN/C2");
|
|
return -1;
|
|
}
|
|
|
|
status = rts_recv_CONN_C2_pdu(rpc, Stream_Buffer(pdu->s), Stream_Length(pdu->s));
|
|
|
|
if (status < 0)
|
|
{
|
|
WLog_ERR(TAG, "rts_recv_CONN_C2_pdu failure");
|
|
return -1;
|
|
}
|
|
|
|
rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
|
|
VIRTUAL_CONNECTION_STATE_OPENED);
|
|
rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_ESTABLISHED);
|
|
|
|
if (rpc_send_bind_pdu(rpc) < 0)
|
|
{
|
|
WLog_ERR(TAG, "rpc_send_bind_pdu failure");
|
|
return -1;
|
|
}
|
|
|
|
rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK);
|
|
status = 1;
|
|
break;
|
|
|
|
case VIRTUAL_CONNECTION_STATE_OPENED:
|
|
break;
|
|
|
|
case VIRTUAL_CONNECTION_STATE_FINAL:
|
|
break;
|
|
}
|
|
}
|
|
else if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
|
|
{
|
|
if (rpc->State == RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK)
|
|
{
|
|
if (pdu->Type == PTYPE_BIND_ACK)
|
|
{
|
|
if (rpc_recv_bind_ack_pdu(rpc, Stream_Buffer(pdu->s), Stream_Length(pdu->s)) <= 0)
|
|
{
|
|
WLog_ERR(TAG, "rpc_recv_bind_ack_pdu failure");
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WLog_ERR(TAG,
|
|
"RPC_CLIENT_STATE_WAIT_SECURE_BIND_ACK unexpected pdu type: 0x%08" PRIX32
|
|
"",
|
|
pdu->Type);
|
|
return -1;
|
|
}
|
|
|
|
if (rpc_send_rpc_auth_3_pdu(rpc) < 0)
|
|
{
|
|
WLog_ERR(TAG, "rpc_secure_bind: error sending rpc_auth_3 pdu!");
|
|
return -1;
|
|
}
|
|
|
|
rpc_client_transition_to_state(rpc, RPC_CLIENT_STATE_CONTEXT_NEGOTIATED);
|
|
|
|
if (!tsg_proxy_begin(tsg))
|
|
{
|
|
WLog_ERR(TAG, "tsg_proxy_begin failure");
|
|
return -1;
|
|
}
|
|
|
|
status = 1;
|
|
}
|
|
else
|
|
{
|
|
WLog_ERR(TAG, "rpc_client_recv_pdu: invalid rpc->State: %d", rpc->State);
|
|
}
|
|
}
|
|
else if (rpc->State >= RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
|
|
{
|
|
if (!tsg_recv_pdu(tsg, pdu))
|
|
status = -1;
|
|
else
|
|
status = 1;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int rpc_client_recv_fragment(rdpRpc* rpc, wStream* fragment)
|
|
{
|
|
BYTE* buffer;
|
|
RPC_PDU* pdu;
|
|
UINT32 StubOffset;
|
|
UINT32 StubLength;
|
|
RpcClientCall* call;
|
|
rpcconn_hdr_t* header;
|
|
pdu = rpc->client->pdu;
|
|
buffer = (BYTE*)Stream_Buffer(fragment);
|
|
header = (rpcconn_hdr_t*)Stream_Buffer(fragment);
|
|
|
|
if (header->common.ptype == PTYPE_RESPONSE)
|
|
{
|
|
rpc->VirtualConnection->DefaultOutChannel->BytesReceived += header->common.frag_length;
|
|
rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow -=
|
|
header->common.frag_length;
|
|
|
|
if (rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow <
|
|
(rpc->ReceiveWindow / 2))
|
|
{
|
|
if (rts_send_flow_control_ack_pdu(rpc) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!rpc_get_stub_data_info(rpc, buffer, &StubOffset, &StubLength))
|
|
{
|
|
WLog_ERR(TAG, "expected stub");
|
|
return -1;
|
|
}
|
|
|
|
if (StubLength == 4)
|
|
{
|
|
if ((header->common.call_id == rpc->PipeCallId) &&
|
|
(header->common.pfc_flags & PFC_LAST_FRAG))
|
|
{
|
|
/* End of TsProxySetupReceivePipe */
|
|
TerminateEventArgs e;
|
|
rpc->result = *((UINT32*)&buffer[StubOffset]);
|
|
freerdp_abort_connect(rpc->context->instance);
|
|
tsg_set_state(rpc->transport->tsg, TSG_STATE_TUNNEL_CLOSE_PENDING);
|
|
EventArgsInit(&e, "freerdp");
|
|
e.code = 0;
|
|
PubSub_OnTerminate(rpc->context->pubSub, rpc->context, &e);
|
|
return 0;
|
|
}
|
|
|
|
if (header->common.call_id != rpc->PipeCallId)
|
|
{
|
|
/* Ignoring non-TsProxySetupReceivePipe Response */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (rpc->StubFragCount == 0)
|
|
rpc->StubCallId = header->common.call_id;
|
|
|
|
if (rpc->StubCallId != header->common.call_id)
|
|
{
|
|
WLog_ERR(TAG,
|
|
"invalid call_id: actual: %" PRIu32 ", expected: %" PRIu32
|
|
", frag_count: %" PRIu32 "",
|
|
rpc->StubCallId, header->common.call_id, rpc->StubFragCount);
|
|
}
|
|
|
|
call = rpc_client_call_find_by_id(rpc->client, rpc->StubCallId);
|
|
|
|
if (!call)
|
|
return -1;
|
|
|
|
if (call->OpNum != TsProxySetupReceivePipeOpnum)
|
|
{
|
|
if (!Stream_EnsureCapacity(pdu->s, header->response.alloc_hint))
|
|
return -1;
|
|
|
|
Stream_Write(pdu->s, &buffer[StubOffset], StubLength);
|
|
rpc->StubFragCount++;
|
|
|
|
if (header->response.alloc_hint == StubLength)
|
|
{
|
|
pdu->Flags = RPC_PDU_FLAG_STUB;
|
|
pdu->Type = PTYPE_RESPONSE;
|
|
pdu->CallId = rpc->StubCallId;
|
|
Stream_SealLength(pdu->s);
|
|
if (rpc_client_recv_pdu(rpc, pdu) < 0)
|
|
return -1;
|
|
rpc_pdu_reset(pdu);
|
|
rpc->StubFragCount = 0;
|
|
rpc->StubCallId = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rpc_client_receive_pipe_write(rpc->client, &buffer[StubOffset], (size_t)StubLength);
|
|
rpc->StubFragCount++;
|
|
|
|
if (header->response.alloc_hint == StubLength)
|
|
{
|
|
rpc->StubFragCount = 0;
|
|
rpc->StubCallId = 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else if (header->common.ptype == PTYPE_RTS)
|
|
{
|
|
if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
|
|
{
|
|
pdu->Flags = 0;
|
|
pdu->Type = header->common.ptype;
|
|
pdu->CallId = header->common.call_id;
|
|
|
|
if (!Stream_EnsureCapacity(pdu->s, Stream_Length(fragment)))
|
|
return -1;
|
|
|
|
Stream_Write(pdu->s, buffer, Stream_Length(fragment));
|
|
Stream_SealLength(pdu->s);
|
|
|
|
if (rpc_client_recv_pdu(rpc, pdu) < 0)
|
|
return -1;
|
|
|
|
rpc_pdu_reset(pdu);
|
|
}
|
|
else
|
|
{
|
|
if (rts_recv_out_of_sequence_pdu(rpc, buffer, header->common.frag_length) < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else if (header->common.ptype == PTYPE_BIND_ACK)
|
|
{
|
|
pdu->Flags = 0;
|
|
pdu->Type = header->common.ptype;
|
|
pdu->CallId = header->common.call_id;
|
|
|
|
if (!Stream_EnsureCapacity(pdu->s, Stream_Length(fragment)))
|
|
return -1;
|
|
|
|
Stream_Write(pdu->s, buffer, Stream_Length(fragment));
|
|
Stream_SealLength(pdu->s);
|
|
|
|
if (rpc_client_recv_pdu(rpc, pdu) < 0)
|
|
return -1;
|
|
|
|
rpc_pdu_reset(pdu);
|
|
return 1;
|
|
}
|
|
else if (header->common.ptype == PTYPE_FAULT)
|
|
{
|
|
rpc_recv_fault_pdu(header->fault.status);
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
WLog_ERR(TAG, "unexpected RPC PDU type 0x%02" PRIX8 "", header->common.ptype);
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int rpc_client_default_out_channel_recv(rdpRpc* rpc)
|
|
{
|
|
int status = -1;
|
|
UINT32 statusCode;
|
|
HttpResponse* response;
|
|
RpcInChannel* inChannel;
|
|
RpcOutChannel* outChannel;
|
|
HANDLE outChannelEvent = NULL;
|
|
RpcVirtualConnection* connection = rpc->VirtualConnection;
|
|
inChannel = connection->DefaultInChannel;
|
|
outChannel = connection->DefaultOutChannel;
|
|
BIO_get_event(outChannel->common.tls->bio, &outChannelEvent);
|
|
|
|
if (outChannel->State < CLIENT_OUT_CHANNEL_STATE_OPENED)
|
|
{
|
|
if (WaitForSingleObject(outChannelEvent, 0) != WAIT_OBJECT_0)
|
|
return 1;
|
|
|
|
response = http_response_recv(outChannel->common.tls, TRUE);
|
|
|
|
if (!response)
|
|
return -1;
|
|
|
|
if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_SECURITY)
|
|
{
|
|
/* Receive OUT Channel Response */
|
|
if (!rpc_ncacn_http_recv_out_channel_response(&outChannel->common, response))
|
|
{
|
|
http_response_free(response);
|
|
WLog_ERR(TAG, "rpc_ncacn_http_recv_out_channel_response failure");
|
|
return -1;
|
|
}
|
|
|
|
/* Send OUT Channel Request */
|
|
|
|
if (!rpc_ncacn_http_send_out_channel_request(&outChannel->common, FALSE))
|
|
{
|
|
http_response_free(response);
|
|
WLog_ERR(TAG, "rpc_ncacn_http_send_out_channel_request failure");
|
|
return -1;
|
|
}
|
|
|
|
rpc_ncacn_http_ntlm_uninit(&outChannel->common);
|
|
rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_NEGOTIATED);
|
|
|
|
/* Send CONN/A1 PDU over OUT channel */
|
|
|
|
if (rts_send_CONN_A1_pdu(rpc) < 0)
|
|
{
|
|
http_response_free(response);
|
|
WLog_ERR(TAG, "rpc_send_CONN_A1_pdu error!");
|
|
return -1;
|
|
}
|
|
|
|
rpc_out_channel_transition_to_state(outChannel, CLIENT_OUT_CHANNEL_STATE_OPENED);
|
|
|
|
if (inChannel->State == CLIENT_IN_CHANNEL_STATE_OPENED)
|
|
{
|
|
rpc_virtual_connection_transition_to_state(
|
|
rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
|
|
}
|
|
|
|
status = 1;
|
|
}
|
|
|
|
http_response_free(response);
|
|
}
|
|
else if (connection->State == VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT)
|
|
{
|
|
/* Receive OUT channel response */
|
|
if (WaitForSingleObject(outChannelEvent, 0) != WAIT_OBJECT_0)
|
|
return 1;
|
|
|
|
response = http_response_recv(outChannel->common.tls, FALSE);
|
|
|
|
if (!response)
|
|
return -1;
|
|
|
|
statusCode = http_response_get_status_code(response);
|
|
|
|
if (statusCode != HTTP_STATUS_OK)
|
|
{
|
|
WLog_ERR(TAG, "error! Status Code: %" PRIu32 "", statusCode);
|
|
http_response_print(response);
|
|
|
|
if (statusCode == HTTP_STATUS_DENIED)
|
|
{
|
|
freerdp_set_last_error_if_not(rpc->context, FREERDP_ERROR_AUTHENTICATION_FAILED);
|
|
}
|
|
|
|
http_response_free(response);
|
|
return -1;
|
|
}
|
|
|
|
http_response_free(response);
|
|
rpc_virtual_connection_transition_to_state(rpc, rpc->VirtualConnection,
|
|
VIRTUAL_CONNECTION_STATE_WAIT_A3W);
|
|
status = 1;
|
|
}
|
|
else
|
|
{
|
|
wStream* fragment;
|
|
rpcconn_common_hdr_t* header;
|
|
fragment = rpc->client->ReceiveFragment;
|
|
|
|
while (1)
|
|
{
|
|
while (Stream_GetPosition(fragment) < RPC_COMMON_FIELDS_LENGTH)
|
|
{
|
|
status = rpc_channel_read(&outChannel->common, fragment,
|
|
RPC_COMMON_FIELDS_LENGTH - Stream_GetPosition(fragment));
|
|
|
|
if (status < 0)
|
|
return -1;
|
|
|
|
if (Stream_GetPosition(fragment) < RPC_COMMON_FIELDS_LENGTH)
|
|
return 0;
|
|
}
|
|
|
|
header = (rpcconn_common_hdr_t*)Stream_Buffer(fragment);
|
|
|
|
if (header->frag_length > rpc->max_recv_frag)
|
|
{
|
|
WLog_ERR(TAG,
|
|
"rpc_client_recv: invalid fragment size: %" PRIu16 " (max: %" PRIu16 ")",
|
|
header->frag_length, rpc->max_recv_frag);
|
|
winpr_HexDump(TAG, WLOG_ERROR, Stream_Buffer(fragment),
|
|
Stream_GetPosition(fragment));
|
|
return -1;
|
|
}
|
|
|
|
while (Stream_GetPosition(fragment) < header->frag_length)
|
|
{
|
|
status = rpc_channel_read(&outChannel->common, fragment,
|
|
header->frag_length - Stream_GetPosition(fragment));
|
|
|
|
if (status < 0)
|
|
{
|
|
WLog_ERR(TAG, "error reading fragment body");
|
|
return -1;
|
|
}
|
|
|
|
if (Stream_GetPosition(fragment) < header->frag_length)
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
/* complete fragment received */
|
|
Stream_SealLength(fragment);
|
|
Stream_SetPosition(fragment, 0);
|
|
status = rpc_client_recv_fragment(rpc, fragment);
|
|
|
|
if (status < 0)
|
|
return status;
|
|
|
|
/* channel recycling may update channel pointers */
|
|
if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_RECYCLED &&
|
|
connection->NonDefaultOutChannel)
|
|
{
|
|
rpc_channel_free(&connection->DefaultOutChannel->common);
|
|
connection->DefaultOutChannel = connection->NonDefaultOutChannel;
|
|
connection->NonDefaultOutChannel = NULL;
|
|
rpc_out_channel_transition_to_state(connection->DefaultOutChannel,
|
|
CLIENT_OUT_CHANNEL_STATE_OPENED);
|
|
rpc_virtual_connection_transition_to_state(
|
|
rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
|
|
return 0;
|
|
}
|
|
|
|
Stream_SetPosition(fragment, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static int rpc_client_nondefault_out_channel_recv(rdpRpc* rpc)
|
|
{
|
|
int status = -1;
|
|
HttpResponse* response;
|
|
RpcOutChannel* nextOutChannel;
|
|
HANDLE nextOutChannelEvent = NULL;
|
|
nextOutChannel = rpc->VirtualConnection->NonDefaultOutChannel;
|
|
BIO_get_event(nextOutChannel->common.tls->bio, &nextOutChannelEvent);
|
|
|
|
if (WaitForSingleObject(nextOutChannelEvent, 0) != WAIT_OBJECT_0)
|
|
return 1;
|
|
|
|
response = http_response_recv(nextOutChannel->common.tls, TRUE);
|
|
|
|
if (response)
|
|
{
|
|
switch (nextOutChannel->State)
|
|
{
|
|
case CLIENT_OUT_CHANNEL_STATE_SECURITY:
|
|
if (rpc_ncacn_http_recv_out_channel_response(&nextOutChannel->common, response))
|
|
{
|
|
if (rpc_ncacn_http_send_out_channel_request(&nextOutChannel->common, TRUE))
|
|
{
|
|
rpc_ncacn_http_ntlm_uninit(&nextOutChannel->common);
|
|
status = rts_send_OUT_R1_A3_pdu(rpc);
|
|
|
|
if (status >= 0)
|
|
{
|
|
rpc_out_channel_transition_to_state(
|
|
nextOutChannel, CLIENT_OUT_CHANNEL_STATE_OPENED_A6W);
|
|
}
|
|
else
|
|
{
|
|
WLog_ERR(TAG, "rts_send_OUT_R1/A3_pdu failure");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WLog_ERR(TAG, "rpc_ncacn_http_send_out_channel_request failure");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WLog_ERR(TAG, "rpc_ncacn_http_recv_out_channel_response failure");
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
WLog_ERR(TAG,
|
|
"rpc_client_nondefault_out_channel_recv: Unexpected message %08" PRIx32,
|
|
nextOutChannel->State);
|
|
status = -1;
|
|
}
|
|
|
|
http_response_free(response);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int rpc_client_out_channel_recv(rdpRpc* rpc)
|
|
{
|
|
int status;
|
|
RpcVirtualConnection* connection = rpc->VirtualConnection;
|
|
|
|
if (connection->DefaultOutChannel)
|
|
{
|
|
status = rpc_client_default_out_channel_recv(rpc);
|
|
|
|
if (status < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (connection->NonDefaultOutChannel)
|
|
{
|
|
status = rpc_client_nondefault_out_channel_recv(rpc);
|
|
|
|
if (status < 0)
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int rpc_client_in_channel_recv(rdpRpc* rpc)
|
|
{
|
|
int status = 1;
|
|
HttpResponse* response;
|
|
RpcInChannel* inChannel;
|
|
RpcOutChannel* outChannel;
|
|
HANDLE InChannelEvent = NULL;
|
|
RpcVirtualConnection* connection = rpc->VirtualConnection;
|
|
inChannel = connection->DefaultInChannel;
|
|
outChannel = connection->DefaultOutChannel;
|
|
BIO_get_event(inChannel->common.tls->bio, &InChannelEvent);
|
|
|
|
if (WaitForSingleObject(InChannelEvent, 0) != WAIT_OBJECT_0)
|
|
return 1;
|
|
|
|
if (inChannel->State < CLIENT_IN_CHANNEL_STATE_OPENED)
|
|
{
|
|
response = http_response_recv(inChannel->common.tls, TRUE);
|
|
|
|
if (!response)
|
|
return -1;
|
|
|
|
if (inChannel->State == CLIENT_IN_CHANNEL_STATE_SECURITY)
|
|
{
|
|
if (!rpc_ncacn_http_recv_in_channel_response(&inChannel->common, response))
|
|
{
|
|
WLog_ERR(TAG, "rpc_ncacn_http_recv_in_channel_response failure");
|
|
http_response_free(response);
|
|
return -1;
|
|
}
|
|
|
|
/* Send IN Channel Request */
|
|
|
|
if (!rpc_ncacn_http_send_in_channel_request(&inChannel->common))
|
|
{
|
|
WLog_ERR(TAG, "rpc_ncacn_http_send_in_channel_request failure");
|
|
http_response_free(response);
|
|
return -1;
|
|
}
|
|
|
|
rpc_ncacn_http_ntlm_uninit(&inChannel->common);
|
|
rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_NEGOTIATED);
|
|
|
|
/* Send CONN/B1 PDU over IN channel */
|
|
|
|
if (rts_send_CONN_B1_pdu(rpc) < 0)
|
|
{
|
|
WLog_ERR(TAG, "rpc_send_CONN_B1_pdu error!");
|
|
http_response_free(response);
|
|
return -1;
|
|
}
|
|
|
|
rpc_in_channel_transition_to_state(inChannel, CLIENT_IN_CHANNEL_STATE_OPENED);
|
|
|
|
if (outChannel->State == CLIENT_OUT_CHANNEL_STATE_OPENED)
|
|
{
|
|
rpc_virtual_connection_transition_to_state(
|
|
rpc, connection, VIRTUAL_CONNECTION_STATE_OUT_CHANNEL_WAIT);
|
|
}
|
|
|
|
status = 1;
|
|
}
|
|
|
|
http_response_free(response);
|
|
}
|
|
else
|
|
{
|
|
response = http_response_recv(inChannel->common.tls, TRUE);
|
|
|
|
if (!response)
|
|
return -1;
|
|
|
|
/* We can receive an unauthorized HTTP response on the IN channel */
|
|
http_response_free(response);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* [MS-RPCE] Client Call:
|
|
* http://msdn.microsoft.com/en-us/library/gg593159/
|
|
*/
|
|
|
|
RpcClientCall* rpc_client_call_find_by_id(RpcClient* client, UINT32 CallId)
|
|
{
|
|
int index;
|
|
int count;
|
|
RpcClientCall* clientCall = NULL;
|
|
|
|
if (!client)
|
|
return NULL;
|
|
|
|
ArrayList_Lock(client->ClientCallList);
|
|
count = ArrayList_Count(client->ClientCallList);
|
|
|
|
for (index = 0; index < count; index++)
|
|
{
|
|
clientCall = (RpcClientCall*)ArrayList_GetItem(client->ClientCallList, index);
|
|
|
|
if (clientCall->CallId == CallId)
|
|
break;
|
|
}
|
|
|
|
ArrayList_Unlock(client->ClientCallList);
|
|
return clientCall;
|
|
}
|
|
|
|
RpcClientCall* rpc_client_call_new(UINT32 CallId, UINT32 OpNum)
|
|
{
|
|
RpcClientCall* clientCall;
|
|
clientCall = (RpcClientCall*)calloc(1, sizeof(RpcClientCall));
|
|
|
|
if (!clientCall)
|
|
return NULL;
|
|
|
|
clientCall->CallId = CallId;
|
|
clientCall->OpNum = OpNum;
|
|
clientCall->State = RPC_CLIENT_CALL_STATE_SEND_PDUS;
|
|
return clientCall;
|
|
}
|
|
|
|
void rpc_client_call_free(RpcClientCall* clientCall)
|
|
{
|
|
free(clientCall);
|
|
}
|
|
|
|
static void rpc_array_client_call_free(void* call)
|
|
{
|
|
rpc_client_call_free((RpcClientCall*)call);
|
|
}
|
|
|
|
int rpc_in_channel_send_pdu(RpcInChannel* inChannel, BYTE* buffer, UINT32 length)
|
|
{
|
|
int status;
|
|
RpcClientCall* clientCall;
|
|
rpcconn_common_hdr_t* header;
|
|
status = rpc_channel_write(&inChannel->common, buffer, length);
|
|
|
|
if (status <= 0)
|
|
return -1;
|
|
|
|
header = (rpcconn_common_hdr_t*)buffer;
|
|
clientCall = rpc_client_call_find_by_id(inChannel->common.client, header->call_id);
|
|
clientCall->State = RPC_CLIENT_CALL_STATE_DISPATCHED;
|
|
|
|
/*
|
|
* This protocol specifies that only RPC PDUs are subject to the flow control abstract
|
|
* data model. RTS PDUs and the HTTP request and response headers are not subject to flow
|
|
* control. Implementations of this protocol MUST NOT include them when computing any of the
|
|
* variables specified by this abstract data model.
|
|
*/
|
|
|
|
if (header->ptype == PTYPE_REQUEST)
|
|
{
|
|
inChannel->BytesSent += status;
|
|
inChannel->SenderAvailableWindow -= status;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
BOOL rpc_client_write_call(rdpRpc* rpc, wStream* s, UINT16 opnum)
|
|
{
|
|
UINT32 offset;
|
|
BYTE* buffer = NULL;
|
|
UINT32 stub_data_pad;
|
|
SecBuffer Buffers[2] = { 0 };
|
|
SecBufferDesc Message;
|
|
RpcClientCall* clientCall = NULL;
|
|
rdpNtlm* ntlm;
|
|
rpcconn_request_hdr_t request_pdu = { 0 };
|
|
RpcVirtualConnection* connection;
|
|
RpcInChannel* inChannel;
|
|
size_t length;
|
|
SSIZE_T size;
|
|
BOOL rc = FALSE;
|
|
|
|
if (!s)
|
|
return FALSE;
|
|
|
|
if (!rpc)
|
|
goto fail;
|
|
|
|
ntlm = rpc->ntlm;
|
|
connection = rpc->VirtualConnection;
|
|
|
|
if (!ntlm)
|
|
{
|
|
WLog_ERR(TAG, "invalid ntlm context");
|
|
goto fail;
|
|
}
|
|
|
|
if (!connection)
|
|
goto fail;
|
|
|
|
inChannel = connection->DefaultInChannel;
|
|
|
|
if (!inChannel)
|
|
goto fail;
|
|
|
|
Stream_SealLength(s);
|
|
length = Stream_Length(s);
|
|
|
|
if (ntlm_client_query_auth_size(ntlm) < 0)
|
|
goto fail;
|
|
|
|
size = ntlm_client_get_context_max_size(ntlm);
|
|
|
|
if (size < 0)
|
|
goto fail;
|
|
|
|
rpc_pdu_header_init(rpc, &request_pdu.header);
|
|
request_pdu.header.ptype = PTYPE_REQUEST;
|
|
request_pdu.header.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG;
|
|
request_pdu.header.auth_length = (UINT16)size;
|
|
request_pdu.header.call_id = rpc->CallId++;
|
|
request_pdu.alloc_hint = length;
|
|
request_pdu.p_cont_id = 0x0000;
|
|
request_pdu.opnum = opnum;
|
|
clientCall = rpc_client_call_new(request_pdu.header.call_id, request_pdu.opnum);
|
|
|
|
if (!clientCall)
|
|
goto fail;
|
|
|
|
if (ArrayList_Add(rpc->client->ClientCallList, clientCall) < 0)
|
|
{
|
|
rpc_client_call_free(clientCall);
|
|
goto fail;
|
|
}
|
|
|
|
if (request_pdu.opnum == TsProxySetupReceivePipeOpnum)
|
|
rpc->PipeCallId = request_pdu.header.call_id;
|
|
|
|
request_pdu.stub_data = Stream_Buffer(s);
|
|
offset = 24;
|
|
stub_data_pad = rpc_offset_align(&offset, 8);
|
|
offset += length;
|
|
request_pdu.auth_verifier.auth_pad_length = rpc_offset_align(&offset, 4);
|
|
request_pdu.auth_verifier.auth_type = RPC_C_AUTHN_WINNT;
|
|
request_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
|
|
request_pdu.auth_verifier.auth_reserved = 0x00;
|
|
request_pdu.auth_verifier.auth_context_id = 0x00000000;
|
|
offset += (8 + request_pdu.header.auth_length);
|
|
request_pdu.header.frag_length = offset;
|
|
buffer = (BYTE*)calloc(1, request_pdu.header.frag_length);
|
|
|
|
if (!buffer)
|
|
goto fail;
|
|
|
|
CopyMemory(buffer, &request_pdu, 24);
|
|
offset = 24;
|
|
rpc_offset_pad(&offset, stub_data_pad);
|
|
CopyMemory(&buffer[offset], request_pdu.stub_data, length);
|
|
offset += length;
|
|
rpc_offset_pad(&offset, request_pdu.auth_verifier.auth_pad_length);
|
|
CopyMemory(&buffer[offset], &request_pdu.auth_verifier.auth_type, 8);
|
|
offset += 8;
|
|
Buffers[0].BufferType = SECBUFFER_DATA | SECBUFFER_READONLY; /* auth_data */
|
|
Buffers[1].BufferType = SECBUFFER_TOKEN; /* signature */
|
|
Buffers[0].pvBuffer = buffer;
|
|
Buffers[0].cbBuffer = offset;
|
|
Buffers[1].cbBuffer = size;
|
|
Buffers[1].pvBuffer = calloc(1, Buffers[1].cbBuffer);
|
|
|
|
if (!Buffers[1].pvBuffer)
|
|
goto fail;
|
|
|
|
Message.cBuffers = 2;
|
|
Message.ulVersion = SECBUFFER_VERSION;
|
|
Message.pBuffers = (PSecBuffer)&Buffers;
|
|
|
|
if (!ntlm_client_encrypt(ntlm, 0, &Message, rpc->SendSeqNum++))
|
|
goto fail;
|
|
|
|
CopyMemory(&buffer[offset], Buffers[1].pvBuffer, Buffers[1].cbBuffer);
|
|
offset += Buffers[1].cbBuffer;
|
|
|
|
if (rpc_in_channel_send_pdu(inChannel, buffer, request_pdu.header.frag_length) < 0)
|
|
goto fail;
|
|
|
|
rc = TRUE;
|
|
fail:
|
|
free(buffer);
|
|
free(Buffers[1].pvBuffer);
|
|
Stream_Free(s, TRUE);
|
|
return rc;
|
|
}
|
|
|
|
static BOOL rpc_client_resolve_gateway(rdpSettings* settings, char** host, UINT16* port,
|
|
BOOL* isProxy)
|
|
{
|
|
struct addrinfo* result;
|
|
|
|
if (!settings || !host || !port || !isProxy)
|
|
return FALSE;
|
|
else
|
|
{
|
|
const char* peerHostname = freerdp_settings_get_string(settings, FreeRDP_GatewayHostname);
|
|
const char* proxyUsername = freerdp_settings_get_string(settings, FreeRDP_GatewayUsername);
|
|
const char* proxyPassword = freerdp_settings_get_string(settings, FreeRDP_GatewayPassword);
|
|
*port = freerdp_settings_get_uint32(settings, FreeRDP_GatewayPort);
|
|
*isProxy = proxy_prepare(settings, &peerHostname, port, &proxyUsername, &proxyPassword);
|
|
result = freerdp_tcp_resolve_host(peerHostname, *port, 0);
|
|
|
|
if (!result)
|
|
return FALSE;
|
|
|
|
*host =
|
|
freerdp_tcp_address_to_string((const struct sockaddr_storage*)result->ai_addr, NULL);
|
|
freeaddrinfo(result);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
RpcClient* rpc_client_new(rdpContext* context, UINT32 max_recv_frag)
|
|
{
|
|
wObject* obj;
|
|
RpcClient* client = (RpcClient*)calloc(1, sizeof(RpcClient));
|
|
|
|
if (!client)
|
|
return NULL;
|
|
|
|
if (!rpc_client_resolve_gateway(context->settings, &client->host, &client->port,
|
|
&client->isProxy))
|
|
goto fail;
|
|
|
|
client->context = context;
|
|
|
|
if (!client->context)
|
|
goto fail;
|
|
|
|
client->pdu = rpc_pdu_new();
|
|
|
|
if (!client->pdu)
|
|
goto fail;
|
|
|
|
client->ReceiveFragment = Stream_New(NULL, max_recv_frag);
|
|
|
|
if (!client->ReceiveFragment)
|
|
goto fail;
|
|
|
|
client->PipeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
if (!client->PipeEvent)
|
|
goto fail;
|
|
|
|
if (!ringbuffer_init(&(client->ReceivePipe), 4096))
|
|
goto fail;
|
|
|
|
if (!InitializeCriticalSectionAndSpinCount(&(client->PipeLock), 4000))
|
|
goto fail;
|
|
|
|
client->ClientCallList = ArrayList_New(TRUE);
|
|
|
|
if (!client->ClientCallList)
|
|
goto fail;
|
|
|
|
obj = ArrayList_Object(client->ClientCallList);
|
|
if (!obj)
|
|
goto fail;
|
|
|
|
obj->fnObjectFree = rpc_array_client_call_free;
|
|
return client;
|
|
fail:
|
|
rpc_client_free(client);
|
|
return NULL;
|
|
}
|
|
|
|
void rpc_client_free(RpcClient* client)
|
|
{
|
|
if (!client)
|
|
return;
|
|
|
|
free(client->host);
|
|
|
|
if (client->ReceiveFragment)
|
|
Stream_Free(client->ReceiveFragment, TRUE);
|
|
|
|
if (client->PipeEvent)
|
|
CloseHandle(client->PipeEvent);
|
|
|
|
ringbuffer_destroy(&(client->ReceivePipe));
|
|
DeleteCriticalSection(&(client->PipeLock));
|
|
|
|
if (client->pdu)
|
|
rpc_pdu_free(client->pdu);
|
|
|
|
if (client->ClientCallList)
|
|
ArrayList_Free(client->ClientCallList);
|
|
|
|
free(client);
|
|
}
|