/** * FreeRDP: A Remote Desktop Protocol Implementation * RPC over HTTP * * Copyright 2012 Fujitsu Technology Solutions GmbH * Copyright 2012 Dmitrij Jasnov * Copyright 2012 Marc-Andre Moreau * * 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 #include #include #include #include #include #include #include #include #include #ifdef HAVE_VALGRIND_MEMCHECK_H #include #endif #include "http.h" #include "ntlm.h" #include "ncacn_http.h" #include "rpc_bind.h" #include "rpc_fault.h" #include "rpc_client.h" #include "rpc.h" #define TAG FREERDP_TAG("core.gateway.rpc") /* Security Verification Trailer Signature */ rpc_sec_verification_trailer RPC_SEC_VERIFICATION_TRAILER = { { 0x8a, 0xe3, 0x13, 0x71, 0x02, 0xf4, 0x36, 0x71 } }; static 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", "" }; const RPC_SECURITY_PROVIDER_INFO RPC_SECURITY_PROVIDER_INFO_TABLE[] = { { RPC_C_AUTHN_NONE, TRUE, -1 }, { RPC_C_AUTHN_GSS_NEGOTIATE, TRUE, -1 }, { RPC_C_AUTHN_WINNT, FALSE, 3 }, { RPC_C_AUTHN_GSS_SCHANNEL, TRUE, -1 }, { RPC_C_AUTHN_GSS_KERBEROS, TRUE, -1 }, { RPC_C_AUTHN_DEFAULT, -1, -1 }, { 0, -1, -1 } }; void rpc_pdu_header_print(rpcconn_hdr_t* header) { WLog_INFO(TAG, "rpc_vers: %d", header->common.rpc_vers); WLog_INFO(TAG, "rpc_vers_minor: %d", header->common.rpc_vers_minor); if (header->common.ptype > PTYPE_RTS) WLog_INFO(TAG, "ptype: %s (%d)", "PTYPE_UNKNOWN", header->common.ptype); else WLog_INFO(TAG, "ptype: %s (%d)", PTYPE_STRINGS[header->common.ptype], header->common.ptype); WLog_INFO(TAG, "pfc_flags (0x%02X) = {", header->common.pfc_flags); if (header->common.pfc_flags & PFC_FIRST_FRAG) WLog_INFO(TAG, " PFC_FIRST_FRAG"); if (header->common.pfc_flags & PFC_LAST_FRAG) WLog_INFO(TAG, " PFC_LAST_FRAG"); if (header->common.pfc_flags & PFC_PENDING_CANCEL) WLog_INFO(TAG, " PFC_PENDING_CANCEL"); if (header->common.pfc_flags & PFC_RESERVED_1) WLog_INFO(TAG, " PFC_RESERVED_1"); if (header->common.pfc_flags & PFC_CONC_MPX) WLog_INFO(TAG, " PFC_CONC_MPX"); if (header->common.pfc_flags & PFC_DID_NOT_EXECUTE) WLog_INFO(TAG, " PFC_DID_NOT_EXECUTE"); if (header->common.pfc_flags & PFC_OBJECT_UUID) WLog_INFO(TAG, " PFC_OBJECT_UUID"); WLog_INFO(TAG, " }"); WLog_INFO(TAG, "packed_drep[4]: %02X %02X %02X %02X", header->common.packed_drep[0], header->common.packed_drep[1], header->common.packed_drep[2], header->common.packed_drep[3]); WLog_INFO(TAG, "frag_length: %d", header->common.frag_length); WLog_INFO(TAG, "auth_length: %d", header->common.auth_length); WLog_INFO(TAG, "call_id: %d", header->common.call_id); if (header->common.ptype == PTYPE_RESPONSE) { WLog_INFO(TAG, "alloc_hint: %d", header->response.alloc_hint); WLog_INFO(TAG, "p_cont_id: %d", header->response.p_cont_id); WLog_INFO(TAG, "cancel_count: %d", header->response.cancel_count); WLog_INFO(TAG, "reserved: %d", header->response.reserved); } } void rpc_pdu_header_init(rdpRpc* rpc, rpcconn_hdr_t* header) { header->common.rpc_vers = rpc->rpc_vers; header->common.rpc_vers_minor = rpc->rpc_vers_minor; header->common.packed_drep[0] = rpc->packed_drep[0]; header->common.packed_drep[1] = rpc->packed_drep[1]; header->common.packed_drep[2] = rpc->packed_drep[2]; header->common.packed_drep[3] = rpc->packed_drep[3]; } UINT32 rpc_offset_align(UINT32* offset, UINT32 alignment) { UINT32 pad; pad = *offset; *offset = (*offset + alignment - 1) & ~(alignment - 1); pad = *offset - pad; return pad; } UINT32 rpc_offset_pad(UINT32* offset, UINT32 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, BYTE* buffer, UINT32* offset, UINT32* length) { UINT32 alloc_hint = 0; rpcconn_hdr_t* header; UINT32 frag_length; UINT32 auth_length; UINT32 auth_pad_length; UINT32 sec_trailer_offset; rpc_sec_trailer* sec_trailer; *offset = RPC_COMMON_FIELDS_LENGTH; header = ((rpcconn_hdr_t*) buffer); switch (header->common.ptype) { case PTYPE_RESPONSE: *offset += 8; rpc_offset_align(offset, 8); alloc_hint = header->response.alloc_hint; break; case PTYPE_REQUEST: *offset += 4; rpc_offset_align(offset, 8); alloc_hint = header->request.alloc_hint; break; case PTYPE_RTS: *offset += 4; break; default: WLog_ERR(TAG, "unknown ptype=0x%x", header->common.ptype); return FALSE; } if (!length) return TRUE; if (header->common.ptype == PTYPE_REQUEST) { UINT32 sec_trailer_offset; sec_trailer_offset = header->common.frag_length - header->common.auth_length - 8; *length = sec_trailer_offset - *offset; return TRUE; } frag_length = header->common.frag_length; auth_length = header->common.auth_length; sec_trailer_offset = frag_length - auth_length - 8; sec_trailer = (rpc_sec_trailer*) &buffer[sec_trailer_offset]; auth_pad_length = sec_trailer->auth_pad_length; #if 0 WLog_ERR(TAG, "sec_trailer: type: %d level: %d pad_length: %d reserved: %d context_id: %d", sec_trailer->auth_type, sec_trailer->auth_level, sec_trailer->auth_pad_length, sec_trailer->auth_reserved, sec_trailer->auth_context_id); #endif /** * 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_ERR(TAG, "invalid auth_length: actual: %d, expected: %d", auth_length, (frag_length - (sec_trailer_offset + 8))); } *length = frag_length - auth_length - 24 - 8 - auth_pad_length; return TRUE; } int rpc_out_read(rdpRpc* rpc, BYTE* data, int length) { int status; status = BIO_read(rpc->TlsOut->bio, data, length); if (status > 0) { #ifdef HAVE_VALGRIND_MEMCHECK_H VALGRIND_MAKE_MEM_DEFINED(data, status); #endif return status; } if (BIO_should_retry(rpc->TlsOut->bio)) return 0; return -1; } int rpc_out_write(rdpRpc* rpc, const BYTE* data, int length) { int status; status = tls_write_all(rpc->TlsOut, data, length); return status; } int rpc_in_write(rdpRpc* rpc, const BYTE* data, int length) { int status; #ifdef WITH_DEBUG_TSG WLog_DBG(TAG, "Sending PDU (length: %d)", length); rpc_pdu_header_print((rpcconn_hdr_t*) data); winpr_HexDump(TAG, WLOG_DEBUG, data, length); #endif status = tls_write_all(rpc->TlsIn, data, length); return status; } int rpc_write(rdpRpc* rpc, BYTE* data, int length, UINT16 opnum) { BYTE* buffer; UINT32 offset; rdpNtlm* ntlm; UINT32 stub_data_pad; SecBuffer Buffers[2]; SecBufferDesc Message; RpcClientCall* clientCall; SECURITY_STATUS encrypt_status; rpcconn_request_hdr_t* request_pdu; ntlm = rpc->ntlm; if (!ntlm || !ntlm->table) { WLog_ERR(TAG, "invalid ntlm context"); return -1; } if (ntlm->table->QueryContextAttributes(&ntlm->context, SECPKG_ATTR_SIZES, &ntlm->ContextSizes) != SEC_E_OK) { WLog_ERR(TAG, "QueryContextAttributes SECPKG_ATTR_SIZES failure"); return -1; } request_pdu = (rpcconn_request_hdr_t*) calloc(1, sizeof(rpcconn_request_hdr_t)); if (!request_pdu) return -1; rpc_pdu_header_init(rpc, (rpcconn_hdr_t*) request_pdu); request_pdu->ptype = PTYPE_REQUEST; request_pdu->pfc_flags = PFC_FIRST_FRAG | PFC_LAST_FRAG; request_pdu->auth_length = (UINT16) ntlm->ContextSizes.cbMaxSignature; request_pdu->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->call_id, request_pdu->opnum); if (!clientCall) goto out_free_pdu; if (ArrayList_Add(rpc->client->ClientCallList, clientCall) < 0) goto out_free_clientCall; if (request_pdu->opnum == TsProxySetupReceivePipeOpnum) rpc->PipeCallId = request_pdu->call_id; request_pdu->stub_data = data; offset = 24; stub_data_pad = 0; 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->auth_length); request_pdu->frag_length = offset; buffer = (BYTE*) calloc(1, request_pdu->frag_length); if (!buffer) goto out_free_pdu; 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; /* auth_data */ Buffers[1].BufferType = SECBUFFER_TOKEN; /* signature */ Buffers[0].pvBuffer = buffer; Buffers[0].cbBuffer = offset; Buffers[1].cbBuffer = ntlm->ContextSizes.cbMaxSignature; Buffers[1].pvBuffer = calloc(1, Buffers[1].cbBuffer); if (!Buffers[1].pvBuffer) return -1; Message.cBuffers = 2; Message.ulVersion = SECBUFFER_VERSION; Message.pBuffers = (PSecBuffer) &Buffers; encrypt_status = ntlm->table->EncryptMessage(&ntlm->context, 0, &Message, rpc->SendSeqNum++); if (encrypt_status != SEC_E_OK) { WLog_ERR(TAG, "EncryptMessage status: 0x%08X", encrypt_status); free(request_pdu); return -1; } CopyMemory(&buffer[offset], Buffers[1].pvBuffer, Buffers[1].cbBuffer); offset += Buffers[1].cbBuffer; free(Buffers[1].pvBuffer); if (rpc_send_enqueue_pdu(rpc, buffer, request_pdu->frag_length) < 0) length = -1; free(request_pdu); return length; out_free_clientCall: rpc_client_call_free(clientCall); out_free_pdu: free(request_pdu); return -1; } BOOL rpc_connect(rdpRpc* rpc) { rpc->TlsIn = rpc->transport->TlsIn; rpc->TlsOut = rpc->transport->TlsOut; if (!rts_connect(rpc)) { WLog_ERR(TAG, "rts_connect error!"); return FALSE; } rpc->State = RPC_CLIENT_STATE_ESTABLISHED; if (rpc_secure_bind(rpc) != 0) { WLog_ERR(TAG, "rpc_secure_bind error!"); return FALSE; } return TRUE; } void rpc_client_virtual_connection_init(rdpRpc* rpc, RpcVirtualConnection* connection) { connection->DefaultInChannel->State = CLIENT_IN_CHANNEL_STATE_INITIAL; connection->DefaultInChannel->BytesSent = 0; connection->DefaultInChannel->SenderAvailableWindow = rpc->ReceiveWindow; connection->DefaultInChannel->PingOriginator.ConnectionTimeout = 30; connection->DefaultInChannel->PingOriginator.KeepAliveInterval = 0; connection->DefaultInChannel->Mutex = CreateMutex(NULL, FALSE, NULL); connection->DefaultOutChannel->State = CLIENT_OUT_CHANNEL_STATE_INITIAL; connection->DefaultOutChannel->BytesReceived = 0; connection->DefaultOutChannel->ReceiverAvailableWindow = rpc->ReceiveWindow; connection->DefaultOutChannel->ReceiveWindow = rpc->ReceiveWindow; connection->DefaultOutChannel->ReceiveWindowSize = rpc->ReceiveWindow; connection->DefaultOutChannel->AvailableWindowAdvertised = rpc->ReceiveWindow; connection->DefaultOutChannel->Mutex = CreateMutex(NULL, FALSE, NULL); } RpcVirtualConnection* rpc_client_virtual_connection_new(rdpRpc* rpc) { RpcVirtualConnection* connection = (RpcVirtualConnection*) calloc(1, sizeof(RpcVirtualConnection)); if (!connection) return NULL; connection->State = VIRTUAL_CONNECTION_STATE_INITIAL; connection->DefaultInChannel = (RpcInChannel*)calloc(1, sizeof(RpcInChannel)); if (!connection->DefaultInChannel) goto out_free; connection->DefaultOutChannel = (RpcOutChannel*)calloc(1, sizeof(RpcOutChannel)); if (!connection->DefaultOutChannel) goto out_default_in; rpc_client_virtual_connection_init(rpc, connection); return connection; out_default_in: free(connection->DefaultInChannel); out_free: free(connection); return NULL; } void rpc_client_virtual_connection_free(RpcVirtualConnection* virtual_connection) { if (virtual_connection != NULL) { free(virtual_connection->DefaultInChannel); free(virtual_connection->DefaultOutChannel); free(virtual_connection); } } rdpRpc* rpc_new(rdpTransport* transport) { rdpRpc* rpc = (rdpRpc*) calloc(1, sizeof(rdpRpc)); if (!rpc) return NULL; rpc->State = RPC_CLIENT_STATE_INITIAL; rpc->transport = transport; rpc->settings = transport->settings; rpc->SendSeqNum = 0; rpc->ntlm = ntlm_new(); if (!rpc->ntlm) goto out_free; rpc->NtlmHttpIn = ntlm_http_new(); if (!rpc->NtlmHttpIn) goto out_free_ntlm; rpc->NtlmHttpOut = ntlm_http_new(); if (!rpc->NtlmHttpOut) goto out_free_ntlm_http_in; rpc_ntlm_http_init_channel(rpc, rpc->NtlmHttpIn, TSG_CHANNEL_IN); rpc_ntlm_http_init_channel(rpc, rpc->NtlmHttpOut, TSG_CHANNEL_OUT); 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->ChannelLifetimeSet = 0; rpc->KeepAliveInterval = 300000; rpc->CurrentKeepAliveInterval = rpc->KeepAliveInterval; rpc->CurrentKeepAliveTime = 0; rpc->VirtualConnection = rpc_client_virtual_connection_new(rpc); if (!rpc->VirtualConnection) goto out_free_ntlm_http_out; rpc->VirtualConnectionCookieTable = ArrayList_New(TRUE); if (!rpc->VirtualConnectionCookieTable) goto out_free_virtual_connection; rpc->CallId = 2; if (rpc_client_new(rpc) < 0) goto out_free_virtualConnectionCookieTable; rpc->client->SynchronousSend = TRUE; rpc->client->SynchronousReceive = TRUE; return rpc; out_free_virtualConnectionCookieTable: rpc_client_free(rpc); ArrayList_Free(rpc->VirtualConnectionCookieTable); out_free_virtual_connection: rpc_client_virtual_connection_free(rpc->VirtualConnection); out_free_ntlm_http_out: ntlm_http_free(rpc->NtlmHttpOut); out_free_ntlm_http_in: ntlm_http_free(rpc->NtlmHttpIn); out_free_ntlm: ntlm_free(rpc->ntlm); out_free: free(rpc); return NULL; } void rpc_free(rdpRpc* rpc) { if (rpc != NULL) { rpc_client_stop(rpc); if (rpc->State >= RPC_CLIENT_STATE_CONTEXT_NEGOTIATED) { ntlm_client_uninit(rpc->ntlm); ntlm_free(rpc->ntlm); } rpc_client_virtual_connection_free(rpc->VirtualConnection); ArrayList_Clear(rpc->VirtualConnectionCookieTable); ArrayList_Free(rpc->VirtualConnectionCookieTable); free(rpc); } }