/* -*- c-basic-offset: 8 -*- rdesktop: A Remote Desktop Protocol client. Protocol services - TCP layer Copyright (C) Matthew Chapman 1999-2008 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _WIN32 #include /* select read write close */ #include /* socket connect setsockopt */ #include /* timeval */ #include /* gethostbyname */ #include /* sockaddr_in */ #include /* TCP_NODELAY */ #include /* inet_addr */ #include /* errno */ #include /* fcntl F_GETFL F_SETFL O_NONBLOCK */ #endif #include "frdp.h" #include "tcp.h" #include "iso.h" #include "mcs.h" #include "secure.h" #include "rdp.h" #include "mem.h" #ifdef _WIN32 #define socklen_t int #define TCP_CLOSE(_sck) closesocket(_sck) #define TCP_STRERROR "tcp error" #define TCP_BLOCKS (WSAGetLastError() == WSAEWOULDBLOCK) #define MSG_NOSIGNAL 0 #else #define TCP_CLOSE(_sck) close(_sck) #define TCP_STRERROR strerror(errno) #define TCP_BLOCKS (errno == EWOULDBLOCK) #endif #ifdef __APPLE__ #define MSG_NOSIGNAL SO_NOSIGPIPE #endif #ifndef INADDR_NONE #define INADDR_NONE ((unsigned long) -1) #endif /*****************************************************************************/ /* returns boolean */ static RD_BOOL tcp_socket_ok(int sck) { #if defined(_WIN32) int opt; int opt_len; #else int opt; unsigned int opt_len; #endif opt_len = sizeof(opt); if (getsockopt(sck, SOL_SOCKET, SO_ERROR, (char *) (&opt), &opt_len) == 0) { if (opt == 0) { return True; } } return False; } /* wait till socket is ready to write or timeout */ RD_BOOL tcp_can_send(int sck, int millis) { fd_set wfds; struct timeval time; int sel_count; time.tv_sec = millis / 1000; time.tv_usec = (millis * 1000) % 1000000; FD_ZERO(&wfds); FD_SET(sck, &wfds); sel_count = select(sck + 1, 0, &wfds, 0, &time); if (sel_count > 0) { return tcp_socket_ok(sck); } return False; } /* wait till socket is ready to read or timeout */ RD_BOOL tcp_can_recv(int sck, int millis) { fd_set rfds; struct timeval time; int sel_count; time.tv_sec = millis / 1000; time.tv_usec = (millis * 1000) % 1000000; FD_ZERO(&rfds); FD_SET(sck, &rfds); sel_count = select(sck + 1, &rfds, 0, 0, &time); if (sel_count > 0) { return tcp_socket_ok(sck); } return False; } /* Initialise and return STREAM. * The stream will have room for at least minsize. * The tcp layers out stream will be used. */ STREAM tcp_init(rdpTcp * tcp, uint32 minsize) { STREAM result = &(tcp->out); if (minsize > result->size) { result->data = (uint8 *) xrealloc(result->data, minsize); result->size = minsize; } result->p = result->data; result->end = result->data + result->size; return result; } /* Send data from stream to tcp socket. * Will block until all data has been sent. */ void tcp_send(rdpTcp * tcp, STREAM s) { int sent = 0; int total = 0; int length = s->end - s->data; #ifndef DISABLE_TLS if (tcp->iso->mcs->sec->tls_connected) { tls_write(tcp->iso->mcs->sec->ssl, (char*) s->data, length); } else #endif { while (total < length) { while (total < length) { sent = send(tcp->sock, s->data + total, length - total, MSG_NOSIGNAL); if (sent <= 0) { if (sent == -1 && TCP_BLOCKS) { tcp_can_send(tcp->sock, 100); sent = 0; } else { ui_error(tcp->iso->mcs->sec->rdp->inst, "send: %s\n", TCP_STRERROR); return; } } total += sent; } } } } /* Read length bytes from tcp socket to stream and return it. * Appends to stream s if specified, otherwise it uses stream from tcp layer. * Will block until data available. * Returns NULL on error. */ STREAM tcp_recv(rdpTcp * tcp, STREAM s, uint32 length) { int rcvd = 0; uint32 p_offset; uint32 new_length; uint32 end_offset; if (s == NULL) { /* read into "new" stream */ if (length > tcp->in.size) { tcp->in.data = (uint8 *) xrealloc(tcp->in.data, length); tcp->in.size = length; } tcp->in.end = tcp->in.p = tcp->in.data; s = &(tcp->in); } else { /* append to existing stream */ new_length = (s->end - s->data) + length; if (new_length > s->size) { p_offset = s->p - s->data; end_offset = s->end - s->data; s->data = (uint8 *) xrealloc(s->data, new_length); s->size = new_length; s->p = s->data + p_offset; s->end = s->data + end_offset; } } while (length > 0) { #ifndef DISABLE_TLS if (tcp->iso->mcs->sec->tls_connected) { rcvd = tls_read(tcp->iso->mcs->sec->ssl, (char*) s->end, length); if (rcvd < 0) return NULL; } else #endif { if (!ui_select(tcp->iso->mcs->sec->rdp->inst, tcp->sock)) return NULL; /* user quit */ rcvd = recv(tcp->sock, s->end, length, 0); if (rcvd < 0) { if (rcvd == -1 && TCP_BLOCKS) { tcp_can_recv(tcp->sock, 1); rcvd = 0; } else { ui_error(tcp->iso->mcs->sec->rdp->inst, "recv: %s\n", TCP_STRERROR); return NULL; } } else if (rcvd == 0) { ui_error(tcp->iso->mcs->sec->rdp->inst, "Connection closed\n"); return NULL; } } s->end += rcvd; length -= rcvd; } return s; } /* Establish a connection on the TCP layer */ RD_BOOL tcp_connect(rdpTcp * tcp, char * server, int port) { int sock; uint32 option_value; socklen_t option_len; #ifdef IPv6 int n; struct addrinfo hints, *res, *ressave; char tcp_port_rdp_s[10]; printf("connecting to %s:%d\n", server, port); snprintf(tcp_port_rdp_s, 10, "%d", port); memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((n = getaddrinfo(server, tcp_port_rdp_s, &hints, &res))) { ui_error(tcp->iso->mcs->sec->rdp->inst, "getaddrinfo: %s\n", gai_strerror(n)); return False; } ressave = res; sock = -1; while (res) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (!(sock < 0)) { if (connect(sock, res->ai_addr, res->ai_addrlen) == 0) break; TCP_CLOSE(sock); sock = -1; } res = res->ai_next; } freeaddrinfo(ressave); if (sock == -1) { ui_error(tcp->iso->mcs->sec->rdp->inst, "%s: unable to connect\n", server); return False; } #else /* no IPv6 support */ struct hostent *nslookup; struct sockaddr_in servaddr; printf("connecting to %s:%d\n", server, port); if ((nslookup = gethostbyname(server)) != NULL) { memcpy(&servaddr.sin_addr, nslookup->h_addr, sizeof(servaddr.sin_addr)); } else if ((servaddr.sin_addr.s_addr = inet_addr(server)) == INADDR_NONE) { ui_error(tcp->iso->mcs->sec->rdp->inst, "%s: unable to resolve host\n", server); return False; } if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { ui_error(tcp->iso->mcs->sec->rdp->inst, "socket: %s\n", TCP_STRERROR); return False; } servaddr.sin_family = AF_INET; servaddr.sin_port = htons((uint16) port); if (connect(sock, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) < 0) { ui_error(tcp->iso->mcs->sec->rdp->inst, "connect: %s\n", TCP_STRERROR); TCP_CLOSE(sock); return False; } #endif /* IPv6 */ tcp->sock = sock; /* set socket as non blocking */ #ifdef _WIN32 { u_long arg = 1; ioctlsocket(tcp->sock, FIONBIO, &arg); tcp->wsa_event = WSACreateEvent(); WSAEventSelect(tcp->sock, tcp->wsa_event, FD_READ); } #else option_value = fcntl(tcp->sock, F_GETFL); option_value = option_value | O_NONBLOCK; fcntl(tcp->sock, F_SETFL, option_value); #endif option_value = 1; option_len = sizeof(option_value); setsockopt(tcp->sock, IPPROTO_TCP, TCP_NODELAY, (void *) &option_value, option_len); /* receive buffer must be a least 16 K */ if (getsockopt(tcp->sock, SOL_SOCKET, SO_RCVBUF, (void *) &option_value, &option_len) == 0) { if (option_value < (1024 * 16)) { option_value = 1024 * 16; option_len = sizeof(option_value); setsockopt(tcp->sock, SOL_SOCKET, SO_RCVBUF, (void *) &option_value, option_len); } } return True; } /* Disconnect on the TCP layer */ void tcp_disconnect(rdpTcp * tcp) { if (tcp->sock != -1) { TCP_CLOSE(tcp->sock); tcp->sock = -1; } #ifdef _WIN32 if (tcp->wsa_event) { WSACloseEvent(tcp->wsa_event); tcp->wsa_event = 0; } #endif } /* Returns pointer to internal buffer with lifetime as tcp */ char * tcp_get_address(rdpTcp * tcp) { struct sockaddr_in sockaddr; socklen_t len = sizeof(sockaddr); if (getsockname(tcp->sock, (struct sockaddr *) &sockaddr, &len) == 0) { uint8 *ip = (uint8 *) & sockaddr.sin_addr; snprintf(tcp->ipaddr, sizeof(tcp->ipaddr), "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); } else strncpy(tcp->ipaddr, "127.0.0.1", sizeof(tcp->ipaddr)); tcp->ipaddr[sizeof(tcp->ipaddr) - 1] = 0; return tcp->ipaddr; } rdpTcp * tcp_new(struct rdp_iso * iso) { rdpTcp * self; self = (rdpTcp *) xmalloc(sizeof(rdpTcp)); if (self != NULL) { memset(self, 0, sizeof(rdpTcp)); self->iso = iso; self->in.size = 4096; self->in.data = (uint8 *) xmalloc(self->in.size); self->out.size = 4096; self->out.data = (uint8 *) xmalloc(self->out.size); self->sock = -1; } return self; } void tcp_free(rdpTcp * tcp) { if (tcp != NULL) { xfree(tcp->in.data); xfree(tcp->out.data); xfree(tcp); } }