freerdp/libfreerdp/core/gateway/rpc_bind.c

433 lines
14 KiB
C
Raw Permalink Normal View History

2023-05-09 21:29:50 +00:00
/**
* FreeRDP: A Remote Desktop Protocol Implementation
* RPC Secure Context Binding
*
* 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 <winpr/crt.h>
#include <assert.h>
#include <freerdp/log.h>
#include "rpc_client.h"
#include "rts.h"
#include "rpc_bind.h"
#define TAG FREERDP_TAG("core.gateway.rpc")
/**
* Connection-Oriented RPC Protocol Client Details:
* http://msdn.microsoft.com/en-us/library/cc243724/
*/
/* Syntax UUIDs */
const p_uuid_t TSGU_UUID = {
0x44E265DD, /* time_low */
0x7DAF, /* time_mid */
0x42CD, /* time_hi_and_version */
0x85, /* clock_seq_hi_and_reserved */
0x60, /* clock_seq_low */
{ 0x3C, 0xDB, 0x6E, 0x7A, 0x27, 0x29 } /* node[6] */
};
const p_uuid_t NDR_UUID = {
0x8A885D04, /* time_low */
0x1CEB, /* time_mid */
0x11C9, /* time_hi_and_version */
0x9F, /* clock_seq_hi_and_reserved */
0xE8, /* clock_seq_low */
{ 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60 } /* node[6] */
};
const p_uuid_t BTFN_UUID = {
0x6CB71C2C, /* time_low */
0x9812, /* time_mid */
0x4540, /* time_hi_and_version */
0x03, /* clock_seq_hi_and_reserved */
0x00, /* clock_seq_low */
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } /* node[6] */
};
/**
* Secure Connection-Oriented RPC Packet Sequence
*
* Client Server
* | |
* |-------------------SECURE_BIND-------------------->|
* | |
* |<----------------SECURE_BIND_ACK-------------------|
* | |
* |--------------------RPC_AUTH_3-------------------->|
* | |
* | |
* |------------------REQUEST_PDU_#1------------------>|
* |------------------REQUEST_PDU_#2------------------>|
* | |
* | ... |
* | |
* |<-----------------RESPONSE_PDU_#1------------------|
* |<-----------------RESPONSE_PDU_#2------------------|
* | |
* | ... |
*/
/**
* SECURE_BIND: RPC bind PDU with sec_trailer and auth_token. Auth_token is generated by calling
* the implementation equivalent of the abstract GSS_Init_sec_context call. Upon receiving that, the
* server calls the implementation equivalent of the abstract GSS_Accept_sec_context call, which
* returns an auth_token and continue status in this example. Assume the following:
*
* 1) The client chooses the auth_context_id field in the sec_trailer sent with this PDU to be 1.
*
* 2) The client uses the RPC_C_AUTHN_LEVEL_PKT_PRIVACY authentication level and the
* Authentication Service (AS) NTLM.
*
* 3) The client sets the PFC_SUPPORT_HEADER_SIGN flag in the PDU header.
*/
int rpc_send_bind_pdu(rdpRpc* rpc)
{
BOOL continueNeeded = FALSE;
int status = -1;
wStream* buffer = NULL;
UINT32 offset;
RpcClientCall* clientCall;
p_cont_elem_t* p_cont_elem;
rpcconn_bind_hdr_t bind_pdu = { 0 };
BOOL promptPassword = FALSE;
rdpSettings* settings;
freerdp* instance;
RpcVirtualConnection* connection;
RpcInChannel* inChannel;
const SecBuffer* sbuffer = NULL;
assert(rpc);
settings = rpc->settings;
assert(settings);
instance = (freerdp*)settings->instance;
assert(instance);
connection = rpc->VirtualConnection;
assert(connection);
inChannel = connection->DefaultInChannel;
WLog_DBG(TAG, "Sending Bind PDU");
ntlm_free(rpc->ntlm);
rpc->ntlm = ntlm_new();
if (!rpc->ntlm)
goto fail;
if ((!settings->GatewayPassword) || (!settings->GatewayUsername) ||
(!strlen(settings->GatewayPassword)) || (!strlen(settings->GatewayUsername)))
{
promptPassword = TRUE;
}
if (promptPassword)
{
if (freerdp_shall_disconnect(instance))
return -1;
if (!instance->GatewayAuthenticate)
{
freerdp_set_last_error_log(instance->context,
FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS);
return 0;
}
else
{
BOOL proceed =
instance->GatewayAuthenticate(instance, &settings->GatewayUsername,
&settings->GatewayPassword, &settings->GatewayDomain);
if (!proceed)
{
freerdp_set_last_error_log(instance->context,
FREERDP_ERROR_CONNECT_NO_OR_MISSING_CREDENTIALS);
return 0;
}
if (settings->GatewayUseSameCredentials)
{
settings->Username = _strdup(settings->GatewayUsername);
settings->Domain = _strdup(settings->GatewayDomain);
settings->Password = _strdup(settings->GatewayPassword);
if (!settings->Username || !settings->Domain || settings->Password)
goto fail;
}
}
}
if (!ntlm_client_init(rpc->ntlm, FALSE, settings->GatewayUsername, settings->GatewayDomain,
settings->GatewayPassword, NULL))
goto fail;
if (!ntlm_client_make_spn(rpc->ntlm, NULL, settings->GatewayHostname))
goto fail;
if (!ntlm_authenticate(rpc->ntlm, &continueNeeded))
goto fail;
if (!continueNeeded)
goto fail;
sbuffer = ntlm_client_get_output_buffer(rpc->ntlm);
if (!sbuffer)
goto fail;
bind_pdu.header = rpc_pdu_header_init(rpc);
bind_pdu.header.auth_length = (UINT16)sbuffer->cbBuffer;
bind_pdu.auth_verifier.auth_value = sbuffer->pvBuffer;
bind_pdu.header.ptype = PTYPE_BIND;
bind_pdu.header.pfc_flags =
PFC_FIRST_FRAG | PFC_LAST_FRAG | PFC_SUPPORT_HEADER_SIGN | PFC_CONC_MPX;
bind_pdu.header.call_id = 2;
bind_pdu.max_xmit_frag = rpc->max_xmit_frag;
bind_pdu.max_recv_frag = rpc->max_recv_frag;
bind_pdu.assoc_group_id = 0;
bind_pdu.p_context_elem.n_context_elem = 2;
bind_pdu.p_context_elem.reserved = 0;
bind_pdu.p_context_elem.reserved2 = 0;
bind_pdu.p_context_elem.p_cont_elem =
calloc(bind_pdu.p_context_elem.n_context_elem, sizeof(p_cont_elem_t));
if (!bind_pdu.p_context_elem.p_cont_elem)
goto fail;
p_cont_elem = &bind_pdu.p_context_elem.p_cont_elem[0];
p_cont_elem->p_cont_id = 0;
p_cont_elem->n_transfer_syn = 1;
p_cont_elem->reserved = 0;
CopyMemory(&(p_cont_elem->abstract_syntax.if_uuid), &TSGU_UUID, sizeof(p_uuid_t));
p_cont_elem->abstract_syntax.if_version = TSGU_SYNTAX_IF_VERSION;
p_cont_elem->transfer_syntaxes = malloc(sizeof(p_syntax_id_t));
if (!p_cont_elem->transfer_syntaxes)
goto fail;
CopyMemory(&(p_cont_elem->transfer_syntaxes[0].if_uuid), &NDR_UUID, sizeof(p_uuid_t));
p_cont_elem->transfer_syntaxes[0].if_version = NDR_SYNTAX_IF_VERSION;
p_cont_elem = &bind_pdu.p_context_elem.p_cont_elem[1];
p_cont_elem->p_cont_id = 1;
p_cont_elem->n_transfer_syn = 1;
p_cont_elem->reserved = 0;
CopyMemory(&(p_cont_elem->abstract_syntax.if_uuid), &TSGU_UUID, sizeof(p_uuid_t));
p_cont_elem->abstract_syntax.if_version = TSGU_SYNTAX_IF_VERSION;
p_cont_elem->transfer_syntaxes = malloc(sizeof(p_syntax_id_t));
if (!p_cont_elem->transfer_syntaxes)
goto fail;
CopyMemory(&(p_cont_elem->transfer_syntaxes[0].if_uuid), &BTFN_UUID, sizeof(p_uuid_t));
p_cont_elem->transfer_syntaxes[0].if_version = BTFN_SYNTAX_IF_VERSION;
offset = 116;
bind_pdu.auth_verifier.auth_type = RPC_C_AUTHN_WINNT;
bind_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
bind_pdu.auth_verifier.auth_reserved = 0x00;
bind_pdu.auth_verifier.auth_context_id = 0x00000000;
offset += (8 + bind_pdu.header.auth_length);
bind_pdu.header.frag_length = offset;
buffer = Stream_New(NULL, bind_pdu.header.frag_length);
if (!buffer)
goto fail;
if (!rts_write_pdu_bind(buffer, &bind_pdu))
goto fail;
clientCall = rpc_client_call_new(bind_pdu.header.call_id, 0);
if (!clientCall)
goto fail;
if (ArrayList_Add(rpc->client->ClientCallList, clientCall) < 0)
{
rpc_client_call_free(clientCall);
goto fail;
}
Stream_SealLength(buffer);
status = rpc_in_channel_send_pdu(inChannel, Stream_Buffer(buffer), Stream_Length(buffer));
fail:
if (bind_pdu.p_context_elem.p_cont_elem)
{
free(bind_pdu.p_context_elem.p_cont_elem[0].transfer_syntaxes);
free(bind_pdu.p_context_elem.p_cont_elem[1].transfer_syntaxes);
}
free(bind_pdu.p_context_elem.p_cont_elem);
Stream_Free(buffer, TRUE);
return (status > 0) ? 1 : -1;
}
/**
* Maximum Transmit/Receive Fragment Size Negotiation
*
* The client determines, and then sends in the bind PDU, its desired maximum size for transmitting
* fragments, and its desired maximum receive fragment size. Similarly, the server determines its
* desired maximum sizes for transmitting and receiving fragments. Transmit and receive sizes may be
* different to help preserve buffering. When the server receives the clients values, it sets its
* operational transmit size to the minimum of the clients receive size (from the bind PDU) and its
* own desired transmit size. Then it sets its actual receive size to the minimum of the clients
* transmit size (from the bind) and its own desired receive size. The server then returns its
* operational values in the bind_ack PDU. The client then sets its operational values from the
* received bind_ack PDU. The received transmit size becomes the clients receive size, and the
* received receive size becomes the clients transmit size. Either party may use receive buffers
* larger than negotiated although this will not provide any advantage but may not transmit
* larger fragments than negotiated.
*/
/**
*
* SECURE_BIND_ACK: RPC bind_ack PDU with sec_trailer and auth_token. The PFC_SUPPORT_HEADER_SIGN
* flag in the PDU header is also set in this example. Auth_token is generated by the server in the
* previous step. Upon receiving that PDU, the client calls the implementation equivalent of the
* abstract GSS_Init_sec_context call, which returns an auth_token and continue status in this
* example.
*/
BOOL rpc_recv_bind_ack_pdu(rdpRpc* rpc, wStream* s)
{
BOOL rc = FALSE;
BOOL continueNeeded = FALSE;
const BYTE* auth_data;
size_t pos, end;
rpcconn_hdr_t header = { 0 };
assert(rpc);
assert(rpc->ntlm);
assert(s);
pos = Stream_GetPosition(s);
if (!rts_read_pdu_header(s, &header))
goto fail;
WLog_DBG(TAG, "Receiving BindAck PDU");
rpc->max_recv_frag = header.bind_ack.max_xmit_frag;
rpc->max_xmit_frag = header.bind_ack.max_recv_frag;
/* Get the correct offset in the input data and pass that on as input buffer.
* rts_read_pdu_header did already do consistency checks */
end = Stream_GetPosition(s);
Stream_SetPosition(s, pos + header.common.frag_length - header.common.auth_length);
auth_data = Stream_Pointer(s);
Stream_SetPosition(s, end);
if (!ntlm_client_set_input_buffer(rpc->ntlm, TRUE, auth_data, header.common.auth_length))
goto fail;
if (!ntlm_authenticate(rpc->ntlm, &continueNeeded))
goto fail;
if (continueNeeded)
goto fail;
rc = TRUE;
fail:
rts_free_pdu_header(&header, FALSE);
return rc;
}
/**
* RPC_AUTH_3: The client knows that this is an NTLM that uses three legs. It sends an rpc_auth_3
* PDU with the auth_token obtained in the previous step. Upon receiving this PDU, the server calls
* the implementation equivalent of the abstract GSS_Accept_sec_context call, which returns success
* status in this example.
*/
int rpc_send_rpc_auth_3_pdu(rdpRpc* rpc)
{
int status = -1;
wStream* buffer;
size_t offset;
const SecBuffer* sbuffer;
RpcClientCall* clientCall;
rpcconn_rpc_auth_3_hdr_t auth_3_pdu = { 0 };
RpcVirtualConnection* connection;
RpcInChannel* inChannel;
assert(rpc);
connection = rpc->VirtualConnection;
assert(connection);
inChannel = connection->DefaultInChannel;
assert(inChannel);
WLog_DBG(TAG, "Sending RpcAuth3 PDU");
sbuffer = ntlm_client_get_output_buffer(rpc->ntlm);
if (!sbuffer)
return -1;
auth_3_pdu.header = rpc_pdu_header_init(rpc);
auth_3_pdu.header.auth_length = (UINT16)sbuffer->cbBuffer;
auth_3_pdu.auth_verifier.auth_value = sbuffer->pvBuffer;
auth_3_pdu.header.ptype = PTYPE_RPC_AUTH_3;
auth_3_pdu.header.pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG | PFC_CONC_MPX;
auth_3_pdu.header.call_id = 2;
auth_3_pdu.max_xmit_frag = rpc->max_xmit_frag;
auth_3_pdu.max_recv_frag = rpc->max_recv_frag;
offset = 20;
auth_3_pdu.auth_verifier.auth_pad_length = rpc_offset_align(&offset, 4);
auth_3_pdu.auth_verifier.auth_type = RPC_C_AUTHN_WINNT;
auth_3_pdu.auth_verifier.auth_level = RPC_C_AUTHN_LEVEL_PKT_INTEGRITY;
auth_3_pdu.auth_verifier.auth_reserved = 0x00;
auth_3_pdu.auth_verifier.auth_context_id = 0x00000000;
offset += (8 + auth_3_pdu.header.auth_length);
auth_3_pdu.header.frag_length = offset;
buffer = Stream_New(NULL, auth_3_pdu.header.frag_length);
if (!buffer)
return -1;
if (!rts_write_pdu_auth3(buffer, &auth_3_pdu))
goto fail;
clientCall = rpc_client_call_new(auth_3_pdu.header.call_id, 0);
if (ArrayList_Add(rpc->client->ClientCallList, clientCall) >= 0)
{
Stream_SealLength(buffer);
status = rpc_in_channel_send_pdu(inChannel, Stream_Buffer(buffer), Stream_Length(buffer));
}
fail:
Stream_Free(buffer, TRUE);
return (status > 0) ? 1 : -1;
}