2012-11-28 21:47:04 +04:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
|
2014-09-12 16:36:29 +04:00
|
|
|
#include <freerdp/log.h>
|
2013-12-21 03:22:29 +04:00
|
|
|
|
2012-11-28 21:47:04 +04:00
|
|
|
#include <winpr/crt.h>
|
2012-12-13 00:55:42 +04:00
|
|
|
#include <winpr/print.h>
|
2012-11-28 21:47:04 +04:00
|
|
|
#include <winpr/synch.h>
|
|
|
|
#include <winpr/thread.h>
|
2012-12-11 00:43:07 +04:00
|
|
|
#include <winpr/stream.h>
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2012-12-12 04:17:57 +04:00
|
|
|
#include "rpc_fault.h"
|
2012-11-28 21:47:04 +04:00
|
|
|
#include "rpc_client.h"
|
2013-05-22 12:58:11 +04:00
|
|
|
#include "../rdp.h"
|
|
|
|
|
2014-09-12 16:36:29 +04:00
|
|
|
#define TAG FREERDP_TAG("core.gateway")
|
2012-12-12 04:17:57 +04:00
|
|
|
|
2015-02-01 00:56:25 +03:00
|
|
|
#define SYNCHRONOUS_TIMEOUT 5000
|
2012-12-12 09:49:15 +04:00
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
RPC_PDU* rpc_client_receive_pool_take(rdpRpc* rpc)
|
2012-12-12 09:49:15 +04:00
|
|
|
{
|
2014-08-18 21:34:47 +04:00
|
|
|
RPC_PDU* pdu = NULL;
|
2012-12-12 09:49:15 +04:00
|
|
|
|
|
|
|
if (WaitForSingleObject(Queue_Event(rpc->client->ReceivePool), 0) == WAIT_OBJECT_0)
|
|
|
|
pdu = Queue_Dequeue(rpc->client->ReceivePool);
|
|
|
|
|
|
|
|
if (!pdu)
|
|
|
|
{
|
2015-01-28 22:54:03 +03:00
|
|
|
pdu = (RPC_PDU*) malloc(sizeof(RPC_PDU));
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!pdu)
|
|
|
|
return NULL;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2012-12-13 00:55:42 +04:00
|
|
|
pdu->s = Stream_New(NULL, rpc->max_recv_frag);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!pdu->s)
|
|
|
|
{
|
|
|
|
free(pdu);
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-12-12 09:49:15 +04:00
|
|
|
}
|
2012-12-12 04:17:57 +04:00
|
|
|
|
2012-12-12 09:49:15 +04:00
|
|
|
pdu->CallId = 0;
|
|
|
|
pdu->Flags = 0;
|
2012-12-13 00:55:42 +04:00
|
|
|
Stream_Length(pdu->s) = 0;
|
|
|
|
Stream_SetPosition(pdu->s, 0);
|
2012-12-12 09:49:15 +04:00
|
|
|
return pdu;
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_client_receive_pool_return(rdpRpc* rpc, RPC_PDU* pdu)
|
2012-12-12 09:49:15 +04:00
|
|
|
{
|
2014-05-21 19:32:14 +04:00
|
|
|
return Queue_Enqueue(rpc->client->ReceivePool, pdu) == TRUE ? 0 : -1;
|
2012-12-12 04:17:57 +04:00
|
|
|
}
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
int rpc_client_receive_pipe_write(rdpRpc* rpc, const BYTE* buffer, size_t length)
|
2015-02-01 00:56:25 +03:00
|
|
|
{
|
2015-02-01 21:09:28 +03:00
|
|
|
int status = 0;
|
|
|
|
RpcClient* client = rpc->client;
|
|
|
|
|
|
|
|
EnterCriticalSection(&(rpc->client->PipeLock));
|
|
|
|
|
|
|
|
if (ringbuffer_write(&(client->ReceivePipe), buffer, length))
|
|
|
|
status += (int) length;
|
|
|
|
|
|
|
|
if (ringbuffer_used(&(client->ReceivePipe)) > 0)
|
|
|
|
SetEvent(client->PipeEvent);
|
|
|
|
|
|
|
|
LeaveCriticalSection(&(rpc->client->PipeLock));
|
|
|
|
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rpc_client_receive_pipe_read(rdpRpc* rpc, BYTE* buffer, size_t length)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
int status = 0;
|
|
|
|
int nchunks = 0;
|
|
|
|
DataChunk chunks[2];
|
|
|
|
RpcClient* client = rpc->client;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rpc_client_on_pdu_received_event(rdpRpc* rpc, RPC_PDU* pdu)
|
|
|
|
{
|
|
|
|
RpcClientCall* call;
|
|
|
|
|
|
|
|
if (pdu->Type != PTYPE_RESPONSE)
|
|
|
|
{
|
|
|
|
Queue_Enqueue(rpc->client->ReceiveQueue, pdu);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
call = rpc_client_call_find_by_id(rpc, pdu->CallId);
|
|
|
|
|
|
|
|
if (call->OpNum != TsProxySetupReceivePipeOpnum)
|
|
|
|
{
|
|
|
|
Queue_Enqueue(rpc->client->ReceiveQueue, pdu);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rpc_client_receive_pipe_write(rpc, Stream_Buffer(pdu->s), Stream_Length(pdu->s));
|
|
|
|
rpc_client_receive_pool_return(rpc, pdu);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-01 00:56:25 +03:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int rpc_client_on_fragment_received_event(rdpRpc* rpc, wStream* fragment)
|
2012-12-12 04:17:57 +04:00
|
|
|
{
|
2014-08-18 21:34:47 +04:00
|
|
|
BYTE* buffer;
|
2012-12-12 04:17:57 +04:00
|
|
|
UINT32 StubOffset;
|
|
|
|
UINT32 StubLength;
|
2014-08-18 21:34:47 +04:00
|
|
|
rpcconn_hdr_t* header;
|
2012-12-12 04:17:57 +04:00
|
|
|
|
2012-12-13 07:03:40 +04:00
|
|
|
if (!rpc->client->pdu)
|
|
|
|
rpc->client->pdu = rpc_client_receive_pool_take(rpc);
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
buffer = (BYTE*) Stream_Buffer(fragment);
|
|
|
|
header = (rpcconn_hdr_t*) Stream_Buffer(fragment);
|
2012-12-12 08:34:51 +04:00
|
|
|
|
2012-12-12 04:17:57 +04:00
|
|
|
if (rpc->State < RPC_CLIENT_STATE_CONTEXT_NEGOTIATED)
|
|
|
|
{
|
2012-12-13 07:03:40 +04:00
|
|
|
rpc->client->pdu->Flags = 0;
|
2015-02-01 00:56:25 +03:00
|
|
|
rpc->client->pdu->Type = header->common.ptype;
|
2012-12-13 07:03:40 +04:00
|
|
|
rpc->client->pdu->CallId = header->common.call_id;
|
|
|
|
Stream_EnsureCapacity(rpc->client->pdu->s, Stream_Length(fragment));
|
|
|
|
Stream_Write(rpc->client->pdu->s, buffer, Stream_Length(fragment));
|
2015-02-01 00:56:25 +03:00
|
|
|
Stream_SealLength(rpc->client->pdu->s);
|
2015-02-01 21:09:28 +03:00
|
|
|
rpc_client_on_pdu_received_event(rpc, rpc->client->pdu);
|
|
|
|
rpc->client->pdu = NULL;
|
2012-12-12 04:17:57 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
switch (header->common.ptype)
|
2012-12-12 04:17:57 +04:00
|
|
|
{
|
2014-05-21 19:32:14 +04:00
|
|
|
case PTYPE_RTS:
|
2014-08-19 20:26:39 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (rpc->VirtualConnection->State < VIRTUAL_CONNECTION_STATE_OPENED)
|
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "warning: unhandled RTS PDU");
|
2014-05-21 19:32:14 +04:00
|
|
|
return 0;
|
|
|
|
}
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-12-12 17:08:39 +03:00
|
|
|
WLog_DBG(TAG, "Receiving Out-of-Sequence RTS PDU");
|
2012-12-12 08:34:51 +04:00
|
|
|
rts_recv_out_of_sequence_pdu(rpc, buffer, header->common.frag_length);
|
2014-05-21 19:32:14 +04:00
|
|
|
return 0;
|
2015-01-13 21:50:46 +03:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
case PTYPE_FAULT:
|
|
|
|
rpc_recv_fault_pdu(header);
|
|
|
|
return -1;
|
2015-01-13 21:50:46 +03:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
case PTYPE_RESPONSE:
|
|
|
|
break;
|
2015-01-13 21:50:46 +03:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
default:
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "unexpected RPC PDU type %d", header->common.ptype);
|
2014-05-21 19:32:14 +04:00
|
|
|
return -1;
|
2012-12-12 08:34:51 +04:00
|
|
|
}
|
|
|
|
|
2015-02-01 00:56:25 +03:00
|
|
|
/* PTYPE_RESPONSE */
|
|
|
|
|
2012-12-12 08:34:51 +04:00
|
|
|
rpc->VirtualConnection->DefaultOutChannel->BytesReceived += header->common.frag_length;
|
|
|
|
rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow -= header->common.frag_length;
|
|
|
|
|
|
|
|
if (!rpc_get_stub_data_info(rpc, buffer, &StubOffset, &StubLength))
|
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "expected stub");
|
2012-12-12 08:34:51 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (StubLength == 4)
|
|
|
|
{
|
2013-06-19 00:55:23 +04:00
|
|
|
/* received a disconnect request from the server? */
|
|
|
|
if ((header->common.call_id == rpc->PipeCallId) && (header->common.pfc_flags & PFC_LAST_FRAG))
|
|
|
|
{
|
2015-01-15 23:04:35 +03:00
|
|
|
TerminateEventArgs e;
|
|
|
|
|
2015-01-15 22:52:35 +03:00
|
|
|
rpc->result = *((UINT32*) &buffer[StubOffset]);
|
2015-01-15 00:49:21 +03:00
|
|
|
|
2014-12-15 17:42:04 +03:00
|
|
|
rpc->context->rdp->disconnect = TRUE;
|
2013-06-19 00:55:23 +04:00
|
|
|
rpc->transport->tsg->state = TSG_STATE_TUNNEL_CLOSE_PENDING;
|
|
|
|
EventArgsInit(&e, "freerdp");
|
|
|
|
e.code = 0;
|
2014-12-15 17:42:04 +03:00
|
|
|
PubSub_OnTerminate(rpc->context->pubSub, rpc->context, &e);
|
2013-06-19 00:55:23 +04:00
|
|
|
}
|
|
|
|
|
2012-12-12 08:34:51 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-12 04:17:57 +04:00
|
|
|
if (rpc->StubFragCount == 0)
|
|
|
|
rpc->StubCallId = header->common.call_id;
|
|
|
|
|
|
|
|
if (rpc->StubCallId != header->common.call_id)
|
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "invalid call_id: actual: %d, expected: %d, frag_count: %d",
|
|
|
|
rpc->StubCallId, header->common.call_id, rpc->StubFragCount);
|
2012-12-12 04:17:57 +04:00
|
|
|
}
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
Stream_EnsureCapacity(rpc->client->pdu->s, header->response.alloc_hint);
|
2012-12-13 07:03:40 +04:00
|
|
|
Stream_Write(rpc->client->pdu->s, &buffer[StubOffset], StubLength);
|
2012-12-12 04:17:57 +04:00
|
|
|
rpc->StubFragCount++;
|
|
|
|
|
|
|
|
if (rpc->VirtualConnection->DefaultOutChannel->ReceiverAvailableWindow < (rpc->ReceiveWindow / 2))
|
|
|
|
{
|
|
|
|
rts_send_flow_control_ack_pdu(rpc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If alloc_hint is set to a nonzero value and a request or a response is fragmented into multiple
|
|
|
|
* PDUs, implementations of these extensions SHOULD set the alloc_hint field in every PDU to be the
|
|
|
|
* combined stub data length of all remaining fragment PDUs.
|
|
|
|
*/
|
|
|
|
|
2013-01-23 22:44:58 +04:00
|
|
|
if (header->response.alloc_hint == StubLength)
|
2012-12-12 04:17:57 +04:00
|
|
|
{
|
2012-12-13 07:03:40 +04:00
|
|
|
rpc->client->pdu->Flags = RPC_PDU_FLAG_STUB;
|
2015-02-01 00:56:25 +03:00
|
|
|
rpc->client->pdu->Type = PTYPE_RESPONSE;
|
2012-12-13 07:03:40 +04:00
|
|
|
rpc->client->pdu->CallId = rpc->StubCallId;
|
2015-02-01 00:56:25 +03:00
|
|
|
Stream_SealLength(rpc->client->pdu->s);
|
2013-03-14 20:57:45 +04:00
|
|
|
rpc->StubFragCount = 0;
|
|
|
|
rpc->StubCallId = 0;
|
2015-02-01 21:09:28 +03:00
|
|
|
rpc_client_on_pdu_received_event(rpc, rpc->client->pdu);
|
|
|
|
rpc->client->pdu = NULL;
|
2012-12-12 04:17:57 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_client_on_read_event(rdpRpc* rpc)
|
2012-12-11 00:43:07 +04:00
|
|
|
{
|
|
|
|
int position;
|
|
|
|
int status = -1;
|
2014-08-18 21:34:47 +04:00
|
|
|
rpcconn_common_hdr_t* header;
|
2012-12-11 00:43:07 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
while (1)
|
2012-12-11 00:43:07 +04:00
|
|
|
{
|
2015-02-01 21:09:28 +03:00
|
|
|
position = Stream_GetPosition(rpc->client->ReceiveFragment);
|
2014-05-21 19:32:14 +04:00
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
while (Stream_GetPosition(rpc->client->ReceiveFragment) < RPC_COMMON_FIELDS_LENGTH)
|
2012-12-11 00:43:07 +04:00
|
|
|
{
|
2015-02-01 21:09:28 +03:00
|
|
|
status = rpc_out_read(rpc, Stream_Pointer(rpc->client->ReceiveFragment),
|
|
|
|
RPC_COMMON_FIELDS_LENGTH - Stream_GetPosition(rpc->client->ReceiveFragment));
|
2014-05-21 19:32:14 +04:00
|
|
|
|
|
|
|
if (status < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!status)
|
|
|
|
return 0;
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
Stream_Seek(rpc->client->ReceiveFragment, status);
|
2012-12-11 00:43:07 +04:00
|
|
|
}
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
if (Stream_GetPosition(rpc->client->ReceiveFragment) < RPC_COMMON_FIELDS_LENGTH)
|
2014-05-21 19:32:14 +04:00
|
|
|
return status;
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
header = (rpcconn_common_hdr_t*) Stream_Buffer(rpc->client->ReceiveFragment);
|
2012-12-11 00:43:07 +04:00
|
|
|
|
|
|
|
if (header->frag_length > rpc->max_recv_frag)
|
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "rpc_client_frag_read: invalid fragment size: %d (max: %d)",
|
|
|
|
header->frag_length, rpc->max_recv_frag);
|
2015-02-01 21:09:28 +03:00
|
|
|
winpr_HexDump(TAG, WLOG_ERROR, Stream_Buffer(rpc->client->ReceiveFragment), Stream_GetPosition(rpc->client->ReceiveFragment));
|
2012-12-11 00:43:07 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
while (Stream_GetPosition(rpc->client->ReceiveFragment) < header->frag_length)
|
2012-12-11 00:43:07 +04:00
|
|
|
{
|
2015-02-01 21:09:28 +03:00
|
|
|
status = rpc_out_read(rpc, Stream_Pointer(rpc->client->ReceiveFragment),
|
|
|
|
header->frag_length - Stream_GetPosition(rpc->client->ReceiveFragment));
|
2012-12-11 00:43:07 +04:00
|
|
|
|
|
|
|
if (status < 0)
|
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "error reading fragment body");
|
2012-12-11 00:43:07 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!status)
|
|
|
|
return 0;
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
Stream_Seek(rpc->client->ReceiveFragment, status);
|
2012-12-11 00:43:07 +04:00
|
|
|
}
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (status < 0)
|
|
|
|
return -1;
|
2012-12-11 00:43:07 +04:00
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
status = Stream_GetPosition(rpc->client->ReceiveFragment) - position;
|
2012-12-11 00:43:07 +04:00
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
if (Stream_GetPosition(rpc->client->ReceiveFragment) >= header->frag_length)
|
2014-05-21 19:32:14 +04:00
|
|
|
{
|
|
|
|
/* complete fragment received */
|
2015-02-01 00:56:25 +03:00
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
Stream_SealLength(rpc->client->ReceiveFragment);
|
|
|
|
Stream_SetPosition(rpc->client->ReceiveFragment, 0);
|
2012-12-12 04:17:57 +04:00
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
status = rpc_client_on_fragment_received_event(rpc, rpc->client->ReceiveFragment);
|
2015-02-01 00:56:25 +03:00
|
|
|
|
|
|
|
if (status < 0)
|
|
|
|
return status;
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
Stream_SetPosition(rpc->client->ReceiveFragment, 0);
|
2014-05-21 19:32:14 +04:00
|
|
|
}
|
2014-12-15 17:42:04 +03:00
|
|
|
|
|
|
|
if (WaitForSingleObject(Queue_Event(rpc->client->SendQueue), 0) == WAIT_OBJECT_0)
|
|
|
|
break;
|
2012-12-11 00:43:07 +04:00
|
|
|
}
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
return 0;
|
2012-12-11 00:43:07 +04:00
|
|
|
}
|
|
|
|
|
2012-12-08 06:09:55 +04:00
|
|
|
/**
|
|
|
|
* [MS-RPCE] Client Call:
|
|
|
|
* http://msdn.microsoft.com/en-us/library/gg593159/
|
|
|
|
*/
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
RpcClientCall* rpc_client_call_find_by_id(rdpRpc* rpc, UINT32 CallId)
|
2012-12-08 06:09:55 +04:00
|
|
|
{
|
|
|
|
int index;
|
|
|
|
int count;
|
2015-01-28 22:54:03 +03:00
|
|
|
RpcClientCall* clientCall = NULL;
|
|
|
|
|
2012-12-12 08:34:51 +04:00
|
|
|
ArrayList_Lock(rpc->client->ClientCallList);
|
2015-01-28 22:54:03 +03:00
|
|
|
|
2012-12-12 08:34:51 +04:00
|
|
|
count = ArrayList_Count(rpc->client->ClientCallList);
|
2012-12-08 06:09:55 +04:00
|
|
|
|
|
|
|
for (index = 0; index < count; index++)
|
|
|
|
{
|
2014-08-18 21:34:47 +04:00
|
|
|
clientCall = (RpcClientCall*) ArrayList_GetItem(rpc->client->ClientCallList, index);
|
2012-12-08 06:09:55 +04:00
|
|
|
|
2012-12-12 09:49:15 +04:00
|
|
|
if (clientCall->CallId == CallId)
|
2012-12-08 06:09:55 +04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-12-12 08:34:51 +04:00
|
|
|
ArrayList_Unlock(rpc->client->ClientCallList);
|
2015-01-28 22:54:03 +03:00
|
|
|
|
2012-12-12 09:49:15 +04:00
|
|
|
return clientCall;
|
2012-12-08 06:09:55 +04:00
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
RpcClientCall* rpc_client_call_new(UINT32 CallId, UINT32 OpNum)
|
2012-12-08 06:09:55 +04:00
|
|
|
{
|
2014-08-18 21:34:47 +04:00
|
|
|
RpcClientCall* clientCall;
|
2015-01-28 22:54:03 +03:00
|
|
|
|
|
|
|
clientCall = (RpcClientCall*) calloc(1, sizeof(RpcClientCall));
|
2012-12-08 06:09:55 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!clientCall)
|
|
|
|
return NULL;
|
2012-12-08 06:09:55 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
clientCall->CallId = CallId;
|
|
|
|
clientCall->OpNum = OpNum;
|
|
|
|
clientCall->State = RPC_CLIENT_CALL_STATE_SEND_PDUS;
|
2015-01-28 22:54:03 +03:00
|
|
|
|
2012-12-12 09:49:15 +04:00
|
|
|
return clientCall;
|
2012-12-08 06:09:55 +04:00
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
void rpc_client_call_free(RpcClientCall* clientCall)
|
2012-12-08 06:09:55 +04:00
|
|
|
{
|
2012-12-12 09:49:15 +04:00
|
|
|
free(clientCall);
|
2012-12-08 06:09:55 +04:00
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_send_enqueue_pdu(rdpRpc* rpc, BYTE* buffer, UINT32 length)
|
2012-11-28 21:47:04 +04:00
|
|
|
{
|
2013-12-21 03:22:29 +04:00
|
|
|
int status;
|
2015-01-28 22:54:03 +03:00
|
|
|
RPC_PDU* pdu;
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
pdu = (RPC_PDU*) malloc(sizeof(RPC_PDU));
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!pdu)
|
2014-11-27 16:39:47 +03:00
|
|
|
{
|
|
|
|
free(buffer);
|
2014-05-21 19:32:14 +04:00
|
|
|
return -1;
|
2014-11-27 16:39:47 +03:00
|
|
|
}
|
2014-05-21 19:32:14 +04:00
|
|
|
|
2012-12-13 00:55:42 +04:00
|
|
|
pdu->s = Stream_New(buffer, length);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!pdu->s)
|
|
|
|
goto out_free;
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!Queue_Enqueue(rpc->client->SendQueue, pdu))
|
|
|
|
goto out_free_stream;
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2012-11-29 05:30:03 +04:00
|
|
|
if (rpc->client->SynchronousSend)
|
|
|
|
{
|
2013-12-21 03:22:29 +04:00
|
|
|
status = WaitForSingleObject(rpc->client->PduSentEvent, SYNCHRONOUS_TIMEOUT);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2013-12-21 03:22:29 +04:00
|
|
|
if (status == WAIT_TIMEOUT)
|
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "timed out waiting for pdu sent event %p", rpc->client->PduSentEvent);
|
2013-12-21 03:22:29 +04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2012-11-29 05:30:03 +04:00
|
|
|
ResetEvent(rpc->client->PduSentEvent);
|
|
|
|
}
|
|
|
|
|
2012-11-28 21:47:04 +04:00
|
|
|
return 0;
|
2014-05-21 19:32:14 +04:00
|
|
|
out_free_stream:
|
|
|
|
Stream_Free(pdu->s, TRUE);
|
|
|
|
out_free:
|
|
|
|
free(pdu);
|
2015-01-29 22:07:02 +03:00
|
|
|
WLog_ERR(TAG, "rpc_send_enqueue_pdu failure");
|
2014-05-21 19:32:14 +04:00
|
|
|
return -1;
|
2012-11-28 21:47:04 +04:00
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_send_dequeue_pdu(rdpRpc* rpc)
|
2012-11-28 21:47:04 +04:00
|
|
|
{
|
|
|
|
int status;
|
2014-08-18 21:34:47 +04:00
|
|
|
RPC_PDU* pdu;
|
|
|
|
RpcClientCall* clientCall;
|
|
|
|
rpcconn_common_hdr_t* header;
|
|
|
|
RpcInChannel* inChannel;
|
2014-12-15 17:42:04 +03:00
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
pdu = (RPC_PDU*) Queue_Dequeue(rpc->client->SendQueue);
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2012-11-29 05:30:03 +04:00
|
|
|
if (!pdu)
|
2012-11-28 22:38:01 +04:00
|
|
|
return 0;
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
inChannel = rpc->VirtualConnection->DefaultInChannel;
|
|
|
|
WaitForSingleObject(inChannel->Mutex, INFINITE);
|
2012-12-13 00:55:42 +04:00
|
|
|
status = rpc_in_write(rpc, Stream_Buffer(pdu->s), Stream_Length(pdu->s));
|
2014-08-18 21:34:47 +04:00
|
|
|
header = (rpcconn_common_hdr_t*) Stream_Buffer(pdu->s);
|
2012-12-12 09:49:15 +04:00
|
|
|
clientCall = rpc_client_call_find_by_id(rpc, header->call_id);
|
|
|
|
clientCall->State = RPC_CLIENT_CALL_STATE_DISPATCHED;
|
2014-05-21 19:32:14 +04:00
|
|
|
ReleaseMutex(inChannel->Mutex);
|
2012-11-28 21:47:04 +04:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2012-12-13 07:03:40 +04:00
|
|
|
|
|
|
|
if (header->ptype == PTYPE_REQUEST)
|
|
|
|
{
|
2014-05-21 19:32:14 +04:00
|
|
|
inChannel->BytesSent += status;
|
|
|
|
inChannel->SenderAvailableWindow -= status;
|
2012-12-13 07:03:40 +04:00
|
|
|
}
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2012-12-13 00:55:42 +04:00
|
|
|
Stream_Free(pdu->s, TRUE);
|
2012-12-06 01:04:01 +04:00
|
|
|
free(pdu);
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2012-11-29 05:30:03 +04:00
|
|
|
if (rpc->client->SynchronousSend)
|
|
|
|
SetEvent(rpc->client->PduSentEvent);
|
2012-11-28 22:38:01 +04:00
|
|
|
|
2012-11-28 21:47:04 +04:00
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
RPC_PDU* rpc_recv_dequeue_pdu(rdpRpc* rpc)
|
2012-11-29 05:30:03 +04:00
|
|
|
{
|
2014-08-18 21:34:47 +04:00
|
|
|
RPC_PDU* pdu;
|
2015-02-01 00:56:25 +03:00
|
|
|
DWORD timeout;
|
2015-01-29 19:35:52 +03:00
|
|
|
DWORD waitStatus;
|
2015-01-13 21:50:46 +03:00
|
|
|
|
2015-02-01 00:56:25 +03:00
|
|
|
timeout = rpc->client->SynchronousReceive ? SYNCHRONOUS_TIMEOUT * 4 : 0;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2015-02-01 00:56:25 +03:00
|
|
|
waitStatus = WaitForSingleObject(Queue_Event(rpc->client->ReceiveQueue), timeout);
|
2015-01-29 19:35:52 +03:00
|
|
|
|
|
|
|
if (waitStatus == WAIT_TIMEOUT)
|
2013-12-21 03:22:29 +04:00
|
|
|
{
|
2014-09-12 16:36:29 +04:00
|
|
|
WLog_ERR(TAG, "timed out waiting for receive event");
|
2013-12-21 03:22:29 +04:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-01-29 19:35:52 +03:00
|
|
|
if (waitStatus != WAIT_OBJECT_0)
|
2014-05-21 19:32:14 +04:00
|
|
|
return NULL;
|
2013-03-14 20:57:45 +04:00
|
|
|
|
2015-01-13 21:50:46 +03:00
|
|
|
pdu = (RPC_PDU*) Queue_Dequeue(rpc->client->ReceiveQueue);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2012-11-29 07:03:18 +04:00
|
|
|
return pdu;
|
2012-11-29 05:30:03 +04:00
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
static void* rpc_client_thread(void* arg)
|
2012-11-28 21:47:04 +04:00
|
|
|
{
|
2012-11-28 22:38:01 +04:00
|
|
|
DWORD nCount;
|
2015-01-29 19:35:52 +03:00
|
|
|
HANDLE events[8];
|
|
|
|
DWORD waitStatus;
|
2015-01-28 22:54:03 +03:00
|
|
|
HANDLE ReadEvent = NULL;
|
2014-12-15 17:42:04 +03:00
|
|
|
rdpRpc* rpc = (rdpRpc*) arg;
|
|
|
|
|
2015-01-28 22:54:03 +03:00
|
|
|
BIO_get_event(rpc->TlsOut->bio, &ReadEvent);
|
|
|
|
|
2012-11-28 22:38:01 +04:00
|
|
|
nCount = 0;
|
|
|
|
events[nCount++] = rpc->client->StopEvent;
|
2012-12-12 08:34:51 +04:00
|
|
|
events[nCount++] = Queue_Event(rpc->client->SendQueue);
|
2012-11-29 05:30:03 +04:00
|
|
|
events[nCount++] = ReadEvent;
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2013-12-21 03:23:57 +04:00
|
|
|
while (rpc->transport->layer != TRANSPORT_LAYER_CLOSED)
|
2012-11-28 21:47:04 +04:00
|
|
|
{
|
2015-01-29 19:35:52 +03:00
|
|
|
waitStatus = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);
|
2014-05-21 19:32:14 +04:00
|
|
|
|
|
|
|
if (WaitForSingleObject(rpc->client->StopEvent, 0) == WAIT_OBJECT_0)
|
|
|
|
break;
|
|
|
|
|
2015-01-29 19:35:52 +03:00
|
|
|
if (WaitForSingleObject(ReadEvent, 0) == WAIT_OBJECT_0)
|
2012-11-29 05:30:03 +04:00
|
|
|
{
|
2014-05-21 19:32:14 +04:00
|
|
|
if (rpc_client_on_read_event(rpc) < 0)
|
2015-01-14 23:39:35 +03:00
|
|
|
{
|
|
|
|
rpc->transport->layer = TRANSPORT_LAYER_CLOSED;
|
2012-12-12 08:34:51 +04:00
|
|
|
break;
|
2015-01-14 23:39:35 +03:00
|
|
|
}
|
2014-05-21 19:32:14 +04:00
|
|
|
}
|
2013-12-21 03:23:57 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (WaitForSingleObject(Queue_Event(rpc->client->SendQueue), 0) == WAIT_OBJECT_0)
|
|
|
|
{
|
|
|
|
rpc_send_dequeue_pdu(rpc);
|
2012-12-11 03:56:53 +04:00
|
|
|
}
|
2012-11-28 21:47:04 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
static void rpc_pdu_free(RPC_PDU* pdu)
|
2012-12-12 08:34:51 +04:00
|
|
|
{
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!pdu)
|
|
|
|
return;
|
|
|
|
|
2012-12-13 00:55:42 +04:00
|
|
|
Stream_Free(pdu->s, TRUE);
|
2012-12-12 08:34:51 +04:00
|
|
|
free(pdu);
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_client_new(rdpRpc* rpc)
|
2012-11-28 21:47:04 +04:00
|
|
|
{
|
2014-12-11 19:25:34 +03:00
|
|
|
RpcClient* client;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-12-11 19:25:34 +03:00
|
|
|
client = (RpcClient*) calloc(1, sizeof(RpcClient));
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2014-12-11 19:25:34 +03:00
|
|
|
rpc->client = client;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-12-11 19:25:34 +03:00
|
|
|
if (!client)
|
2014-05-21 19:32:14 +04:00
|
|
|
return -1;
|
2012-11-28 21:47:04 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client->StopEvent)
|
|
|
|
return -1;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->PduSentEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client->PduSentEvent)
|
|
|
|
return -1;
|
2012-12-12 09:49:15 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->SendQueue = Queue_New(TRUE, -1, -1);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client->SendQueue)
|
|
|
|
return -1;
|
2012-12-12 08:34:51 +04:00
|
|
|
|
2014-08-18 19:22:43 +04:00
|
|
|
Queue_Object(client->SendQueue)->fnObjectFree = (OBJECT_FREE_FN) rpc_pdu_free;
|
2015-02-01 00:56:25 +03:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->pdu = NULL;
|
2015-02-01 00:56:25 +03:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->ReceivePool = Queue_New(TRUE, -1, -1);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client->ReceivePool)
|
|
|
|
return -1;
|
2012-12-11 03:56:53 +04:00
|
|
|
|
2014-08-18 19:22:43 +04:00
|
|
|
Queue_Object(client->ReceivePool)->fnObjectFree = (OBJECT_FREE_FN) rpc_pdu_free;
|
2015-02-01 00:56:25 +03:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->ReceiveQueue = Queue_New(TRUE, -1, -1);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client->ReceiveQueue)
|
|
|
|
return -1;
|
2012-12-12 08:34:51 +04:00
|
|
|
|
2014-08-18 19:22:43 +04:00
|
|
|
Queue_Object(client->ReceiveQueue)->fnObjectFree = (OBJECT_FREE_FN) rpc_pdu_free;
|
2012-12-13 05:02:56 +04:00
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
client->ReceiveFragment = Stream_New(NULL, rpc->max_recv_frag);
|
|
|
|
|
|
|
|
if (!client->ReceiveFragment)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
client->PipeEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
|
|
|
|
if (!client->PipeEvent)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!ringbuffer_init(&(client->ReceivePipe), 4096))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (!InitializeCriticalSectionAndSpinCount(&(client->PipeLock), 4000))
|
|
|
|
return -1;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
client->ClientCallList = ArrayList_New(TRUE);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client->ClientCallList)
|
|
|
|
return -1;
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
ArrayList_Object(client->ClientCallList)->fnObjectFree = (OBJECT_FREE_FN) rpc_client_call_free;
|
2012-11-29 05:30:03 +04:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_client_start(rdpRpc* rpc)
|
2012-11-29 05:30:03 +04:00
|
|
|
{
|
2014-04-25 01:05:10 +04:00
|
|
|
rpc->client->Thread = CreateThread(NULL, 0,
|
2014-12-11 19:25:34 +03:00
|
|
|
(LPTHREAD_START_ROUTINE) rpc_client_thread, rpc, 0, NULL);
|
|
|
|
|
2012-11-28 21:47:04 +04:00
|
|
|
return 0;
|
|
|
|
}
|
2012-12-12 08:34:51 +04:00
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_client_stop(rdpRpc* rpc)
|
2012-12-12 08:34:51 +04:00
|
|
|
{
|
2014-04-25 01:05:10 +04:00
|
|
|
if (rpc->client->Thread)
|
2014-04-24 22:07:11 +04:00
|
|
|
{
|
|
|
|
SetEvent(rpc->client->StopEvent);
|
|
|
|
WaitForSingleObject(rpc->client->Thread, INFINITE);
|
2014-04-25 01:05:10 +04:00
|
|
|
rpc->client->Thread = NULL;
|
2014-04-24 22:07:11 +04:00
|
|
|
}
|
2012-12-13 05:02:56 +04:00
|
|
|
|
2014-12-15 17:42:04 +03:00
|
|
|
return 0;
|
2012-12-12 08:34:51 +04:00
|
|
|
}
|
|
|
|
|
2014-08-18 21:34:47 +04:00
|
|
|
int rpc_client_free(rdpRpc* rpc)
|
2012-12-12 08:34:51 +04:00
|
|
|
{
|
2014-12-15 17:42:04 +03:00
|
|
|
RpcClient* client = rpc->client;
|
2012-12-12 09:49:15 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (!client)
|
|
|
|
return 0;
|
|
|
|
|
2014-12-15 17:42:04 +03:00
|
|
|
rpc_client_stop(rpc);
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->SendQueue)
|
2012-12-13 05:02:56 +04:00
|
|
|
Queue_Free(client->SendQueue);
|
|
|
|
|
2015-02-01 21:09:28 +03:00
|
|
|
if (client->ReceiveFragment)
|
|
|
|
Stream_Free(client->ReceiveFragment, TRUE);
|
|
|
|
|
|
|
|
if (client->PipeEvent)
|
|
|
|
CloseHandle(client->PipeEvent);
|
|
|
|
|
|
|
|
ringbuffer_destroy(&(client->ReceivePipe));
|
|
|
|
|
|
|
|
DeleteCriticalSection(&(client->PipeLock));
|
2012-12-13 07:03:40 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->pdu)
|
|
|
|
rpc_pdu_free(client->pdu);
|
2012-12-13 05:02:56 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->ReceivePool)
|
2012-12-13 07:03:40 +04:00
|
|
|
Queue_Free(client->ReceivePool);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->ReceiveQueue)
|
2012-12-13 05:02:56 +04:00
|
|
|
Queue_Free(client->ReceiveQueue);
|
2012-12-12 08:34:51 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->ClientCallList)
|
2012-12-13 05:02:56 +04:00
|
|
|
ArrayList_Free(client->ClientCallList);
|
2012-12-12 09:49:15 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->StopEvent)
|
2012-12-13 05:02:56 +04:00
|
|
|
CloseHandle(client->StopEvent);
|
2014-08-18 19:22:43 +04:00
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->PduSentEvent)
|
2012-12-13 05:02:56 +04:00
|
|
|
CloseHandle(client->PduSentEvent);
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
if (client->Thread)
|
2012-12-13 07:03:40 +04:00
|
|
|
CloseHandle(client->Thread);
|
|
|
|
|
2014-05-21 19:32:14 +04:00
|
|
|
free(client);
|
2014-12-15 17:42:04 +03:00
|
|
|
rpc->client = NULL;
|
|
|
|
|
2012-12-12 08:34:51 +04:00
|
|
|
return 0;
|
|
|
|
}
|