/** * FreeRDP: A Remote Desktop Protocol Implementation * Network Transport Layer * * Copyright 2011 Vic Lee * * 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 #include #include #include #include #ifndef _WIN32 #include #include #endif /* _WIN32 */ #ifdef HAVE_VALGRIND_MEMCHECK_H #include #endif #include "tpkt.h" #include "fastpath.h" #include "transport.h" #include "rdp.h" #include "proxy.h" #include "utils.h" #define TAG FREERDP_TAG("core.transport") #define BUFFER_SIZE 16384 struct rdp_transport { TRANSPORT_LAYER layer; BIO* frontBio; rdpRdg* rdg; rdpTsg* tsg; rdpTls* tls; rdpContext* context; rdpNla* nla; rdpSettings* settings; void* ReceiveExtra; wStream* ReceiveBuffer; TransportRecv ReceiveCallback; wStreamPool* ReceivePool; HANDLE connectedEvent; BOOL NlaMode; BOOL blocking; BOOL GatewayEnabled; CRITICAL_SECTION ReadLock; CRITICAL_SECTION WriteLock; ULONG written; HANDLE rereadEvent; BOOL haveMoreBytesToRead; wLog* log; rdpTransportIo io; }; static void transport_ssl_cb(SSL* ssl, int where, int ret) { if (where & SSL_CB_ALERT) { rdpTransport* transport = (rdpTransport*)SSL_get_app_data(ssl); WINPR_ASSERT(transport); switch (ret) { case (SSL3_AL_FATAL << 8) | SSL_AD_ACCESS_DENIED: { if (!freerdp_get_last_error(transport_get_context(transport))) { WLog_Print(transport->log, WLOG_ERROR, "%s: ACCESS DENIED", __FUNCTION__); freerdp_set_last_error_log(transport_get_context(transport), FREERDP_ERROR_AUTHENTICATION_FAILED); } } break; case (SSL3_AL_FATAL << 8) | SSL_AD_INTERNAL_ERROR: { if (transport->NlaMode) { if (!freerdp_get_last_error(transport_get_context(transport))) { UINT32 kret = nla_get_error(transport->nla); if (kret == 0) kret = FREERDP_ERROR_CONNECT_PASSWORD_CERTAINLY_EXPIRED; freerdp_set_last_error_log(transport_get_context(transport), kret); } } break; case (SSL3_AL_WARNING << 8) | SSL3_AD_CLOSE_NOTIFY: break; default: WLog_Print(transport->log, WLOG_WARN, "Unhandled SSL error (where=%d, ret=%d [%s, %s])", where, ret, SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); break; } } } } wStream* transport_send_stream_init(rdpTransport* transport, size_t size) { wStream* s; WINPR_ASSERT(transport); if (!(s = StreamPool_Take(transport->ReceivePool, size))) return NULL; if (!Stream_EnsureCapacity(s, size)) { Stream_Release(s); return NULL; } Stream_SetPosition(s, 0); return s; } BOOL transport_attach(rdpTransport* transport, int sockfd) { if (!transport) return FALSE; return IFCALLRESULT(FALSE, transport->io.TransportAttach, transport, sockfd); } static BOOL transport_default_attach(rdpTransport* transport, int sockfd) { BIO* socketBio = NULL; BIO* bufferedBio; WINPR_ASSERT(transport); socketBio = BIO_new(BIO_s_simple_socket()); if (!socketBio) goto fail; BIO_set_fd(socketBio, sockfd, BIO_CLOSE); bufferedBio = BIO_new(BIO_s_buffered_socket()); if (!bufferedBio) goto fail; bufferedBio = BIO_push(bufferedBio, socketBio); transport->frontBio = bufferedBio; return TRUE; fail: if (socketBio) BIO_free_all(socketBio); else close(sockfd); return FALSE; } BOOL transport_connect_rdp(rdpTransport* transport) { if (!transport) return FALSE; switch (utils_authenticate(transport_get_context(transport)->instance, AUTH_RDP, FALSE)) { case AUTH_SKIP: case AUTH_SUCCESS: case AUTH_NO_CREDENTIALS: return TRUE; default: return FALSE; } } BOOL transport_connect_tls(rdpTransport* transport) { if (!transport) return FALSE; WINPR_ASSERT(transport->settings); /* Only prompt for password if we use TLS (NLA also calls this function) */ if (transport->settings->SelectedProtocol == PROTOCOL_SSL) { switch (utils_authenticate(transport_get_context(transport)->instance, AUTH_TLS, FALSE)) { case AUTH_SKIP: case AUTH_SUCCESS: case AUTH_NO_CREDENTIALS: break; default: return FALSE; } } return IFCALLRESULT(FALSE, transport->io.TLSConnect, transport); } static BOOL transport_default_connect_tls(rdpTransport* transport) { int tlsStatus; rdpTls* tls = NULL; rdpContext* context; rdpSettings* settings; WINPR_ASSERT(transport); context = transport_get_context(transport); WINPR_ASSERT(context); settings = transport->settings; WINPR_ASSERT(settings); if (!(tls = tls_new(settings))) return FALSE; transport->tls = tls; if (transport->GatewayEnabled) transport->layer = TRANSPORT_LAYER_TSG_TLS; else transport->layer = TRANSPORT_LAYER_TLS; tls->hostname = settings->ServerHostname; tls->port = settings->ServerPort; if (tls->port == 0) tls->port = 3389; tls->isGatewayTransport = FALSE; tlsStatus = tls_connect(tls, transport->frontBio); 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; } transport->frontBio = tls->bio; BIO_callback_ctrl(tls->bio, BIO_CTRL_SET_CALLBACK, (bio_info_cb*)(void*)transport_ssl_cb); SSL_set_app_data(tls->ssl, transport); if (!transport->frontBio) { WLog_Print(transport->log, WLOG_ERROR, "unable to prepend a filtering TLS bio"); return FALSE; } return TRUE; } BOOL transport_connect_nla(rdpTransport* transport) { rdpContext* context = NULL; rdpSettings* settings = NULL; freerdp* instance = NULL; rdpRdp* rdp = NULL; if (!transport) return FALSE; context = transport_get_context(transport); settings = context->settings; instance = context->instance; rdp = context->rdp; WINPR_ASSERT(context); WINPR_ASSERT(settings); WINPR_ASSERT(instance); WINPR_ASSERT(rdp); if (!transport_connect_tls(transport)) return FALSE; if (!settings->Authentication) return TRUE; nla_free(rdp->nla); rdp->nla = nla_new(instance, transport, settings); if (!rdp->nla) return FALSE; transport_set_nla_mode(transport, TRUE); if (settings->AuthenticationServiceClass) { if (!nla_set_service_principal(rdp->nla, nla_make_spn(settings->AuthenticationServiceClass, settings->ServerHostname))) return FALSE; } if (nla_client_begin(rdp->nla) < 0) { WLog_Print(transport->log, WLOG_ERROR, "NLA begin failed"); freerdp_set_last_error_if_not(context, FREERDP_ERROR_AUTHENTICATION_FAILED); transport_set_nla_mode(transport, FALSE); return FALSE; } rdp_client_transition_to_state(rdp, CONNECTION_STATE_NLA); return TRUE; } BOOL transport_connect(rdpTransport* transport, const char* hostname, UINT16 port, DWORD timeout) { int sockfd; BOOL status = FALSE; rdpSettings* settings; rdpContext* context; BOOL rpcFallback; WINPR_ASSERT(transport); WINPR_ASSERT(hostname); settings = transport->settings; context = transport_get_context(transport); rpcFallback = !settings->GatewayHttpTransport; WINPR_ASSERT(settings); WINPR_ASSERT(context); if (transport->GatewayEnabled) { if (!status && settings->GatewayHttpTransport) { transport->rdg = rdg_new(context); if (!transport->rdg) return FALSE; status = rdg_connect(transport->rdg, timeout, &rpcFallback); if (status) { transport->frontBio = rdg_get_front_bio_and_take_ownership(transport->rdg); BIO_set_nonblock(transport->frontBio, 0); transport->layer = TRANSPORT_LAYER_TSG; status = TRUE; } else { rdg_free(transport->rdg); transport->rdg = NULL; } } if (!status && settings->GatewayRpcTransport && rpcFallback) { transport->tsg = tsg_new(transport); if (!transport->tsg) return FALSE; /* Reset error condition from RDG */ freerdp_set_last_error_log(context, FREERDP_ERROR_SUCCESS); status = tsg_connect(transport->tsg, hostname, port, timeout); if (status) { transport->frontBio = tsg_get_bio(transport->tsg); transport->layer = TRANSPORT_LAYER_TSG; status = TRUE; } else { tsg_free(transport->tsg); transport->tsg = NULL; } } } else { UINT16 peerPort; const char *proxyHostname, *proxyUsername, *proxyPassword; BOOL isProxyConnection = proxy_prepare(settings, &proxyHostname, &peerPort, &proxyUsername, &proxyPassword); if (isProxyConnection) sockfd = transport_tcp_connect(transport, proxyHostname, peerPort, timeout); else sockfd = transport_tcp_connect(transport, hostname, port, timeout); if (sockfd < 0) return FALSE; if (!transport_attach(transport, sockfd)) return FALSE; if (isProxyConnection) { if (!proxy_connect(settings, transport->frontBio, proxyUsername, proxyPassword, hostname, port)) return FALSE; } status = TRUE; } return status; } BOOL transport_accept_rdp(rdpTransport* transport) { if (!transport) return FALSE; /* RDP encryption */ return TRUE; } BOOL transport_accept_tls(rdpTransport* transport) { if (!transport) return FALSE; return IFCALLRESULT(FALSE, transport->io.TLSAccept, transport); } static BOOL transport_default_accept_tls(rdpTransport* transport) { rdpSettings* settings; WINPR_ASSERT(transport); settings = transport->settings; WINPR_ASSERT(settings); if (!transport->tls) transport->tls = tls_new(transport->settings); transport->layer = TRANSPORT_LAYER_TLS; if (!tls_accept(transport->tls, transport->frontBio, settings)) return FALSE; transport->frontBio = transport->tls->bio; return TRUE; } BOOL transport_accept_nla(rdpTransport* transport) { rdpSettings* settings; freerdp* instance; if (!transport) return FALSE; settings = transport->settings; if (!settings) return FALSE; instance = (freerdp*)settings->instance; if (!IFCALLRESULT(FALSE, transport->io.TLSAccept, transport)) return FALSE; /* Network Level Authentication */ if (!settings->Authentication) return TRUE; if (!transport->nla) { transport->nla = nla_new(instance, transport, settings); transport_set_nla_mode(transport, TRUE); } if (nla_authenticate(transport->nla) < 0) { WLog_Print(transport->log, WLOG_ERROR, "client authentication failure"); transport_set_nla_mode(transport, FALSE); nla_free(transport->nla); transport->nla = NULL; tls_set_alert_code(transport->tls, TLS_ALERT_LEVEL_FATAL, TLS_ALERT_DESCRIPTION_ACCESS_DENIED); tls_send_alert(transport->tls); return FALSE; } /* don't free nla module yet, we need to copy the credentials from it first */ transport_set_nla_mode(transport, FALSE); return TRUE; } #define WLog_ERR_BIO(transport, biofunc, bio) \ transport_bio_error_log(transport, biofunc, bio, __FILE__, __FUNCTION__, __LINE__) static void transport_bio_error_log(rdpTransport* transport, LPCSTR biofunc, BIO* bio, LPCSTR file, LPCSTR func, DWORD line) { unsigned long sslerr; char* buf; int saveerrno; DWORD level; WINPR_ASSERT(transport); saveerrno = errno; level = WLOG_ERROR; if (level < WLog_GetLogLevel(transport->log)) return; if (ERR_peek_error() == 0) { const char* fmt = "%s returned a system error %d: %s"; WLog_PrintMessage(transport->log, WLOG_MESSAGE_TEXT, level, line, file, func, fmt, biofunc, saveerrno, strerror(saveerrno)); return; } buf = malloc(120); if (buf) { const char* fmt = "%s returned an error: %s"; while ((sslerr = ERR_get_error())) { ERR_error_string_n(sslerr, buf, 120); WLog_PrintMessage(transport->log, WLOG_MESSAGE_TEXT, level, line, file, func, fmt, biofunc, buf); } free(buf); } } static SSIZE_T transport_read_layer(rdpTransport* transport, BYTE* data, size_t bytes) { SSIZE_T read = 0; rdpRdp* rdp; rdpContext* context; WINPR_ASSERT(transport); context = transport_get_context(transport); WINPR_ASSERT(context); rdp = context->rdp; WINPR_ASSERT(rdp); if (!transport->frontBio || (bytes > SSIZE_MAX)) { transport->layer = TRANSPORT_LAYER_CLOSED; freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED); return -1; } while (read < (SSIZE_T)bytes) { const SSIZE_T tr = (SSIZE_T)bytes - read; int r = (int)((tr > INT_MAX) ? INT_MAX : tr); int status = BIO_read(transport->frontBio, data + read, r); if (status <= 0) { if (!transport->frontBio || !BIO_should_retry(transport->frontBio)) { /* something unexpected happened, let's close */ if (!transport->frontBio) { WLog_Print(transport->log, WLOG_ERROR, "BIO_read: transport->frontBio null"); return -1; } WLog_ERR_BIO(transport, "BIO_read", transport->frontBio); transport->layer = TRANSPORT_LAYER_CLOSED; freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED); return -1; } /* non blocking will survive a partial read */ if (!transport->blocking) return read; /* blocking means that we can't continue until we have read the number of requested * bytes */ if (BIO_wait_read(transport->frontBio, 100) < 0) { WLog_ERR_BIO(transport, "BIO_wait_read", transport->frontBio); return -1; } continue; } #ifdef HAVE_VALGRIND_MEMCHECK_H VALGRIND_MAKE_MEM_DEFINED(data + read, bytes - read); #endif read += status; rdp->inBytes += status; } return read; } /** * @brief Tries to read toRead bytes from the specified transport * * Try to read toRead bytes from the transport to the stream. * In case it was not possible to read toRead bytes 0 is returned. The stream is always advanced by * the number of bytes read. * * The function assumes that the stream has enough capacity to hold the data. * * @param[in] transport rdpTransport * @param[in] s wStream * @param[in] toRead number of bytes to read * @return < 0 on error; 0 if not enough data is available (non blocking mode); 1 toRead bytes read */ static SSIZE_T transport_read_layer_bytes(rdpTransport* transport, wStream* s, size_t toRead) { SSIZE_T status; if (!transport) return -1; if (toRead > SSIZE_MAX) return 0; status = IFCALLRESULT(-1, transport->io.ReadBytes, transport, Stream_Pointer(s), toRead); if (status <= 0) return status; Stream_Seek(s, (size_t)status); return status == (SSIZE_T)toRead ? 1 : 0; } /** * @brief Try to read a complete PDU (NLA, fast-path or tpkt) from the underlying transport. * * If possible a complete PDU is read, in case of non blocking transport this might not succeed. * Except in case of an error the passed stream will point to the last byte read (correct * position). When the pdu read is completed the stream is sealed and the pointer set to 0 * * @param[in] transport rdpTransport * @param[in] s wStream * @return < 0 on error; 0 if not enough data is available (non blocking mode); > 0 number of * bytes of the *complete* pdu read */ int transport_read_pdu(rdpTransport* transport, wStream* s) { if (!transport) return -1; return IFCALLRESULT(-1, transport->io.ReadPdu, transport, s); } SSIZE_T transport_parse_pdu(rdpTransport* transport, wStream* s, BOOL* incomplete) { size_t position; size_t pduLength; BYTE* header; pduLength = 0; if (!transport) return -1; if (!s) return -1; header = Stream_Buffer(s); position = Stream_GetPosition(s); if (incomplete) *incomplete = TRUE; /* Make sure at least two bytes are read for further processing */ if (position < 2) { /* No data available at the moment */ return 0; } if (transport->NlaMode) { /* * In case NlaMode is set TSRequest package(s) are expected * 0x30 = DER encoded data with these bits set: * bit 6 P/C constructed * bit 5 tag number - sequence */ if (header[0] == 0x30) { /* TSRequest (NLA) */ if (header[1] & 0x80) { if ((header[1] & ~(0x80)) == 1) { /* check for header bytes already was readed in previous calls */ if (position < 3) return 0; pduLength = header[2]; pduLength += 3; } else if ((header[1] & ~(0x80)) == 2) { /* check for header bytes already was readed in previous calls */ if (position < 4) return 0; pduLength = (header[2] << 8) | header[3]; pduLength += 4; } else { WLog_Print(transport->log, WLOG_ERROR, "Error reading TSRequest!"); return -1; } } else { pduLength = header[1]; pduLength += 2; } } } else { if (header[0] == 0x03) { /* TPKT header */ /* check for header bytes already was readed in previous calls */ if (position < 4) return 0; pduLength = (header[2] << 8) | header[3]; /* min and max values according to ITU-T Rec. T.123 (01/2007) section 8 */ if ((pduLength < 7) || (pduLength > 0xFFFF)) { WLog_Print(transport->log, WLOG_ERROR, "tpkt - invalid pduLength: %" PRIdz, pduLength); return -1; } } else { /* Fast-Path Header */ if (header[1] & 0x80) { /* check for header bytes already was readed in previous calls */ if (position < 3) return 0; pduLength = ((header[1] & 0x7F) << 8) | header[2]; } else pduLength = header[1]; /* * fast-path has 7 bits for length so the maximum size, including headers is 0x8000 * The theoretical minimum fast-path PDU consists only of two header bytes plus one * byte for data (e.g. fast-path input synchronize pdu) */ if (pduLength < 3 || pduLength > 0x8000) { WLog_Print(transport->log, WLOG_ERROR, "fast path - invalid pduLength: %" PRIdz, pduLength); return -1; } } } if (position > pduLength) return -1; if (incomplete) *incomplete = position < pduLength; return pduLength; } static int transport_default_read_pdu(rdpTransport* transport, wStream* s) { BOOL incomplete; SSIZE_T status; size_t pduLength; size_t position; WINPR_ASSERT(transport); WINPR_ASSERT(s); /* Read in pdu length */ status = transport_parse_pdu(transport, s, &incomplete); while ((status == 0) && incomplete) { int rc; if (!Stream_EnsureRemainingCapacity(s, 1)) return -1; rc = transport_read_layer_bytes(transport, s, 1); if (rc != 1) return rc; status = transport_parse_pdu(transport, s, &incomplete); } if (status < 0) return -1; pduLength = (size_t)status; /* Read in rest of the PDU */ if (!Stream_EnsureCapacity(s, pduLength)) return -1; position = Stream_GetPosition(s); if (position > pduLength) return -1; status = transport_read_layer_bytes(transport, s, pduLength - Stream_GetPosition(s)); if (status != 1) return status; if (Stream_GetPosition(s) >= pduLength) WLog_Packet(transport->log, WLOG_TRACE, Stream_Buffer(s), pduLength, WLOG_PACKET_INBOUND); Stream_SealLength(s); Stream_SetPosition(s, 0); return Stream_Length(s); } int transport_write(rdpTransport* transport, wStream* s) { if (!transport) return -1; return IFCALLRESULT(-1, transport->io.WritePdu, transport, s); } static int transport_default_write(rdpTransport* transport, wStream* s) { size_t length; int status = -1; int writtenlength = 0; rdpRdp* rdp; rdpContext* context; if (!s) return -1; context = transport_get_context(transport); if (!transport || !context) goto fail; rdp = context->rdp; if (!rdp) goto fail; if (!transport->frontBio) { transport->layer = TRANSPORT_LAYER_CLOSED; freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED); goto fail; } EnterCriticalSection(&(transport->WriteLock)); length = Stream_GetPosition(s); writtenlength = length; Stream_SetPosition(s, 0); if (length > 0) { rdp->outBytes += length; WLog_Packet(transport->log, WLOG_TRACE, Stream_Buffer(s), length, WLOG_PACKET_OUTBOUND); } while (length > 0) { status = BIO_write(transport->frontBio, Stream_Pointer(s), length); if (status <= 0) { /* the buffered BIO that is at the end of the chain always says OK for writing, * so a retry means that for any reason we need to read. The most probable * is a SSL or TSG BIO in the chain. */ if (!BIO_should_retry(transport->frontBio)) { WLog_ERR_BIO(transport, "BIO_should_retry", transport->frontBio); goto out_cleanup; } /* non-blocking can live with blocked IOs */ if (!transport->blocking) { WLog_ERR_BIO(transport, "BIO_write", transport->frontBio); goto out_cleanup; } if (BIO_wait_write(transport->frontBio, 100) < 0) { WLog_ERR_BIO(transport, "BIO_wait_write", transport->frontBio); status = -1; goto out_cleanup; } continue; } if (transport->blocking || transport->settings->WaitForOutputBufferFlush) { while (BIO_write_blocked(transport->frontBio)) { if (BIO_wait_write(transport->frontBio, 100) < 0) { WLog_Print(transport->log, WLOG_ERROR, "error when selecting for write"); status = -1; goto out_cleanup; } if (BIO_flush(transport->frontBio) < 1) { WLog_Print(transport->log, WLOG_ERROR, "error when flushing outputBuffer"); status = -1; goto out_cleanup; } } } length -= status; Stream_Seek(s, status); } transport->written += writtenlength; out_cleanup: if (status < 0) { /* A write error indicates that the peer has dropped the connection */ transport->layer = TRANSPORT_LAYER_CLOSED; freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED); } LeaveCriticalSection(&(transport->WriteLock)); fail: Stream_Release(s); return status; } DWORD transport_get_event_handles(rdpTransport* transport, HANDLE* events, DWORD count) { DWORD nCount = 1; /* always the reread Event */ DWORD tmp; WINPR_ASSERT(transport); WINPR_ASSERT(events); WINPR_ASSERT(count > 0); if (events) { if (count < 1) { WLog_Print(transport->log, WLOG_ERROR, "%s: provided handles array is too small", __FUNCTION__); return 0; } events[0] = transport->rereadEvent; } if (!transport->GatewayEnabled) { if (events) { if (nCount >= count) { WLog_Print(transport->log, WLOG_ERROR, "%s: provided handles array is too small (count=%" PRIu32 " nCount=%" PRIu32 ")", __FUNCTION__, count, nCount); return 0; } if (transport->frontBio) { if (BIO_get_event(transport->frontBio, &events[1]) != 1) { WLog_Print(transport->log, WLOG_ERROR, "%s: error getting the frontBio handle", __FUNCTION__); return 0; } nCount++; } } } else { if (transport->rdg) { tmp = rdg_get_event_handles(transport->rdg, &events[1], count - 1); if (tmp == 0) return 0; nCount += tmp; } else if (transport->tsg) { tmp = tsg_get_event_handles(transport->tsg, &events[1], count - 1); if (tmp == 0) return 0; nCount += tmp; } } return nCount; } void transport_get_fds(rdpTransport* transport, void** rfds, int* rcount) { DWORD index; DWORD nCount; HANDLE events[MAXIMUM_WAIT_OBJECTS] = { 0 }; WINPR_ASSERT(transport); WINPR_ASSERT(rfds); WINPR_ASSERT(rcount); nCount = transport_get_event_handles(transport, events, ARRAYSIZE(events)); *rcount = nCount + 1; for (index = 0; index < nCount; index++) { rfds[index] = GetEventWaitObject(events[index]); } rfds[nCount] = GetEventWaitObject(transport->rereadEvent); } BOOL transport_is_write_blocked(rdpTransport* transport) { WINPR_ASSERT(transport); return BIO_write_blocked(transport->frontBio); } int transport_drain_output_buffer(rdpTransport* transport) { BOOL status = FALSE; WINPR_ASSERT(transport); if (BIO_write_blocked(transport->frontBio)) { if (BIO_flush(transport->frontBio) < 1) return -1; status |= BIO_write_blocked(transport->frontBio); } return status; } int transport_check_fds(rdpTransport* transport) { int status; int recv_status; wStream* received; UINT64 now = GetTickCount64(); UINT64 dueDate = 0; rdpContext* context; WINPR_ASSERT(transport); context = transport_get_context(transport); WINPR_ASSERT(context); if (transport->layer == TRANSPORT_LAYER_CLOSED) { WLog_Print(transport->log, WLOG_DEBUG, "transport_check_fds: transport layer closed"); freerdp_set_last_error_if_not(context, FREERDP_ERROR_CONNECT_TRANSPORT_FAILED); return -1; } WINPR_ASSERT(transport->settings); dueDate = now + transport->settings->MaxTimeInCheckLoop; if (transport->haveMoreBytesToRead) { transport->haveMoreBytesToRead = FALSE; ResetEvent(transport->rereadEvent); } while (now < dueDate) { WINPR_ASSERT(context); if (freerdp_shall_disconnect(context->instance)) { return -1; } /** * Note: transport_read_pdu tries to read one PDU from * the transport layer. * The ReceiveBuffer might have a position > 0 in case of a non blocking * transport. If transport_read_pdu returns 0 the pdu couldn't be read at * this point. * Note that transport->ReceiveBuffer is replaced after each iteration * of this loop with a fresh stream instance from a pool. */ if ((status = transport_read_pdu(transport, transport->ReceiveBuffer)) <= 0) { if (status < 0) WLog_Print(transport->log, WLOG_DEBUG, "transport_check_fds: transport_read_pdu() - %i", status); return status; } received = transport->ReceiveBuffer; if (!(transport->ReceiveBuffer = StreamPool_Take(transport->ReceivePool, 0))) return -1; /** * status: * -1: error * 0: success * 1: redirection */ WINPR_ASSERT(transport->ReceiveCallback); recv_status = transport->ReceiveCallback(transport, received, transport->ReceiveExtra); Stream_Release(received); /* session redirection or activation */ if (recv_status == 1 || recv_status == 2) { return recv_status; } if (recv_status < 0) { WLog_Print(transport->log, WLOG_ERROR, "transport_check_fds: transport->ReceiveCallback() - %i", recv_status); return -1; } now = GetTickCount64(); } if (now >= dueDate) { SetEvent(transport->rereadEvent); transport->haveMoreBytesToRead = TRUE; } return 0; } BOOL transport_set_blocking_mode(rdpTransport* transport, BOOL blocking) { WINPR_ASSERT(transport); transport->blocking = blocking; if (!BIO_set_nonblock(transport->frontBio, blocking ? FALSE : TRUE)) return FALSE; return TRUE; } void transport_set_gateway_enabled(rdpTransport* transport, BOOL GatewayEnabled) { WINPR_ASSERT(transport); transport->GatewayEnabled = GatewayEnabled; } void transport_set_nla_mode(rdpTransport* transport, BOOL NlaMode) { WINPR_ASSERT(transport); transport->NlaMode = NlaMode; } BOOL transport_disconnect(rdpTransport* transport) { if (!transport) return FALSE; return IFCALLRESULT(FALSE, transport->io.TransportDisconnect, transport); } static BOOL transport_default_disconnect(rdpTransport* transport) { BOOL status = TRUE; if (!transport) return FALSE; if (transport->tls) { tls_free(transport->tls); transport->tls = NULL; } else { if (transport->frontBio) BIO_free_all(transport->frontBio); } if (transport->tsg) { tsg_free(transport->tsg); transport->tsg = NULL; } if (transport->rdg) { rdg_free(transport->rdg); transport->rdg = NULL; } transport->frontBio = NULL; transport->layer = TRANSPORT_LAYER_TCP; return status; } rdpTransport* transport_new(rdpContext* context) { rdpTransport* transport = (rdpTransport*)calloc(1, sizeof(rdpTransport)); WINPR_ASSERT(context); if (!transport) return NULL; transport->log = WLog_Get(TAG); if (!transport->log) goto fail; // transport->io.DataHandler = transport_data_handler; transport->io.TCPConnect = freerdp_tcp_default_connect; transport->io.TLSConnect = transport_default_connect_tls; transport->io.TLSAccept = transport_default_accept_tls; transport->io.TransportAttach = transport_default_attach; transport->io.TransportDisconnect = transport_default_disconnect; transport->io.ReadPdu = transport_default_read_pdu; transport->io.WritePdu = transport_default_write; transport->io.ReadBytes = transport_read_layer; transport->context = context; transport->settings = context->settings; transport->ReceivePool = StreamPool_New(TRUE, BUFFER_SIZE); if (!transport->ReceivePool) goto fail; /* receive buffer for non-blocking read. */ transport->ReceiveBuffer = StreamPool_Take(transport->ReceivePool, 0); if (!transport->ReceiveBuffer) goto fail; transport->connectedEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!transport->connectedEvent || transport->connectedEvent == INVALID_HANDLE_VALUE) goto fail; transport->rereadEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (!transport->rereadEvent || transport->rereadEvent == INVALID_HANDLE_VALUE) goto fail; transport->haveMoreBytesToRead = FALSE; transport->blocking = TRUE; transport->GatewayEnabled = FALSE; transport->layer = TRANSPORT_LAYER_TCP; if (!InitializeCriticalSectionAndSpinCount(&(transport->ReadLock), 4000)) goto fail; if (!InitializeCriticalSectionAndSpinCount(&(transport->WriteLock), 4000)) goto fail; return transport; fail: transport_free(transport); return NULL; } void transport_free(rdpTransport* transport) { if (!transport) return; transport_disconnect(transport); if (transport->ReceiveBuffer) Stream_Release(transport->ReceiveBuffer); nla_free(transport->nla); StreamPool_Free(transport->ReceivePool); CloseHandle(transport->connectedEvent); CloseHandle(transport->rereadEvent); DeleteCriticalSection(&(transport->ReadLock)); DeleteCriticalSection(&(transport->WriteLock)); free(transport); } BOOL transport_set_io_callbacks(rdpTransport* transport, const rdpTransportIo* io_callbacks) { if (!transport || !io_callbacks) return FALSE; transport->io = *io_callbacks; return TRUE; } const rdpTransportIo* transport_get_io_callbacks(rdpTransport* transport) { if (!transport) return NULL; return &transport->io; } rdpContext* transport_get_context(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->context; } rdpTransport* freerdp_get_transport(rdpContext* context) { WINPR_ASSERT(context); WINPR_ASSERT(context->rdp); return context->rdp->transport; } BOOL transport_set_nla(rdpTransport* transport, rdpNla* nla) { WINPR_ASSERT(transport); nla_free(transport->nla); transport->nla = nla; return TRUE; } rdpNla* transport_get_nla(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->nla; } BOOL transport_set_tls(rdpTransport* transport, rdpTls* tls) { WINPR_ASSERT(transport); tls_free(transport->tls); transport->tls = tls; return TRUE; } rdpTls* transport_get_tls(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->tls; } BOOL transport_set_tsg(rdpTransport* transport, rdpTsg* tsg) { WINPR_ASSERT(transport); tsg_free(transport->tsg); transport->tsg = tsg; return TRUE; } rdpTsg* transport_get_tsg(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->tsg; } wStream* transport_take_from_pool(rdpTransport* transport, size_t size) { WINPR_ASSERT(transport); return StreamPool_Take(transport->ReceivePool, size); } ULONG transport_get_bytes_sent(rdpTransport* transport, BOOL resetCount) { ULONG rc; WINPR_ASSERT(transport); rc = transport->written; if (resetCount) transport->written = 0; return rc; } TRANSPORT_LAYER transport_get_layer(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->layer; } BOOL transport_set_layer(rdpTransport* transport, TRANSPORT_LAYER layer) { WINPR_ASSERT(transport); transport->layer = layer; return TRUE; } BOOL transport_set_connected_event(rdpTransport* transport) { WINPR_ASSERT(transport); return SetEvent(transport->connectedEvent); } BOOL transport_set_recv_callbacks(rdpTransport* transport, TransportRecv recv, void* extra) { WINPR_ASSERT(transport); transport->ReceiveCallback = recv; transport->ReceiveExtra = extra; } BOOL transport_get_blocking(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->blocking; } BOOL transport_set_blocking(rdpTransport* transport, BOOL blocking) { WINPR_ASSERT(transport); transport->blocking = blocking; return TRUE; } BOOL transport_have_more_bytes_to_read(rdpTransport* transport) { WINPR_ASSERT(transport); return transport->haveMoreBytesToRead; } int transport_tcp_connect(rdpTransport* transport, const char* hostname, int port, DWORD timeout) { rdpContext* context = transport_get_context(transport); WINPR_ASSERT(context); WINPR_ASSERT(context->settings); return IFCALLRESULT(-1, transport->io.TCPConnect, context, context->settings, hostname, port, timeout); } HANDLE transport_get_front_bio(rdpTransport* transport) { HANDLE hEvent = NULL; WINPR_ASSERT(transport); WINPR_ASSERT(transport->frontBio); BIO_get_event(transport->frontBio, &hEvent); return hEvent; }