Mercurial > hg > freeDiameter
diff libfdcore/cnxctx.c @ 658:f198d16fa7f4
Initial commit for 1.1.0:
* Restructuring:
* libfreeDiameter:
- renamed folder & binary into libfdproto
- renamed libfD.h into fdproto-internal.h
- removed signals management (replaced by triggers in libfdcore)
* freeDiameter split into:
- libfdcore (most contents)
- renamed fD.h into fdcore-internal.h
- added core.c for framework init/shutdown.
- new triggers mechanism in events.c.
- freeDiameterd (main, command line parsing, signals management)
* tests:
- now in top-level directory tests.
* other changes:
- fd_dict_new now returns 0 on duplicate identical entries.
- fixes in dict_legacy_xml
- fixes in some dictionaries
- moved FD_DEFAULT_CONF_FILENAME definition to freeDiameter-host.h
author | Sebastien Decugis <sdecugis@nict.go.jp> |
---|---|
date | Fri, 14 Jan 2011 15:15:23 +0900 |
parents | freeDiameter/cnxctx.c@88a494357a9d |
children | 2e94ef0515d7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libfdcore/cnxctx.c Fri Jan 14 15:15:23 2011 +0900 @@ -0,0 +1,1583 @@ +/********************************************************************************************************* +* Software License Agreement (BSD License) * +* Author: Sebastien Decugis <sdecugis@nict.go.jp> * +* * +* Copyright (c) 2010, WIDE Project and NICT * +* All rights reserved. * +* * +* Redistribution and use of this software in source and binary forms, with or without modification, are * +* permitted provided that the following conditions are met: * +* * +* * Redistributions of source code must retain the above * +* copyright notice, this list of conditions and the * +* following disclaimer. * +* * +* * Redistributions in binary form must reproduce the above * +* copyright notice, this list of conditions and the * +* following disclaimer in the documentation and/or other * +* materials provided with the distribution. * +* * +* * Neither the name of the WIDE Project or NICT nor the * +* names of its contributors may be used to endorse or * +* promote products derived from this software without * +* specific prior written permission of WIDE Project and * +* NICT. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * +* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * +* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * +* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * +* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * +* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * +*********************************************************************************************************/ + +#include "fdcore-internal.h" +#include "cnxctx.h" + +#include <net/if.h> +#include <ifaddrs.h> /* for getifaddrs */ + +/* The maximum size of Diameter message we accept to receive (<= 2^24) to avoid too big mallocs in case of trashed headers */ +#ifndef DIAMETER_MSG_SIZE_MAX +#define DIAMETER_MSG_SIZE_MAX 65535 /* in bytes */ +#endif /* DIAMETER_MSG_SIZE_MAX */ + + +/* Connections contexts (cnxctx) in freeDiameter are wrappers around the sockets and TLS operations . + * They are used to hide the details of the processing to the higher layers of the daemon. + * They are always oriented on connections (TCP or SCTP), connectionless modes (UDP or SCTP) are not supported. + */ + +/* Note: this file could be moved to libfreeDiameter instead, but since it uses gnuTLS we prefer to keep it in the daemon */ + +/* Lifetime of a cnxctx object: + * 1) Creation + * a) a server socket: + * - create the object with fd_cnx_serv_tcp or fd_cnx_serv_sctp + * - start listening incoming connections: fd_cnx_serv_listen + * - accept new clients with fd_cnx_serv_accept. + * b) a client socket: + * - connect to a remote server with fd_cnx_cli_connect + * + * 2) Initialization + * - if TLS is started first, call fd_cnx_handshake + * - otherwise to receive clear messages, call fd_cnx_start_clear. fd_cnx_handshake can be called later. + * + * 3) Usage + * - fd_cnx_receive, fd_cnx_send : exchange messages on this connection (send is synchronous, receive is not, but blocking). + * - fd_cnx_recv_setaltfifo : when a message is received, the event is sent to an external fifo list. fd_cnx_receive does not work when the alt_fifo is set. + * - fd_cnx_getid : retrieve a descriptive string for the connection (for debug) + * - fd_cnx_getremoteid : identification of the remote peer (IP address or fqdn) + * - fd_cnx_getcred : get the remote peer TLS credentials, after handshake + * + * 4) End + * - fd_cnx_destroy + */ + +/*******************************************/ +/* Creation of a connection object */ +/*******************************************/ + +/* Initialize a context structure */ +static struct cnxctx * fd_cnx_init(int full) +{ + struct cnxctx * conn = NULL; + + TRACE_ENTRY("%d", full); + + CHECK_MALLOC_DO( conn = malloc(sizeof(struct cnxctx)), return NULL ); + memset(conn, 0, sizeof(struct cnxctx)); + + if (full) { + CHECK_FCT_DO( fd_fifo_new ( &conn->cc_incoming ), return NULL ); + } + + return conn; +} + +/* Create and bind a server socket to the given endpoint and port */ +struct cnxctx * fd_cnx_serv_tcp(uint16_t port, int family, struct fd_endpoint * ep) +{ + struct cnxctx * cnx = NULL; + sSS dummy; + sSA * sa = (sSA *) &dummy; + + TRACE_ENTRY("%hu %d %p", port, family, ep); + + CHECK_PARAMS_DO( port, return NULL ); + CHECK_PARAMS_DO( ep || family, return NULL ); + CHECK_PARAMS_DO( (! family) || (family == AF_INET) || (family == AF_INET6), return NULL ); + CHECK_PARAMS_DO( (! ep) || (!family) || (ep->ss.ss_family == family), return NULL ); + + /* The connection object */ + CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL ); + + /* Prepare the socket address information */ + if (ep) { + memcpy(sa, &ep->ss, sizeof(sSS)); + } else { + memset(&dummy, 0, sizeof(dummy)); + sa->sa_family = family; + } + if (sa->sa_family == AF_INET) { + ((sSA4 *)sa)->sin_port = htons(port); + cnx->cc_family = AF_INET; + } else { + ((sSA6 *)sa)->sin6_port = htons(port); + cnx->cc_family = AF_INET6; + } + + /* Create the socket */ + CHECK_FCT_DO( fd_tcp_create_bind_server( &cnx->cc_socket, sa, sSAlen(sa) ), goto error ); + + /* Generate the name for the connection object */ + { + char addrbuf[INET6_ADDRSTRLEN]; + int rc; + rc = getnameinfo(sa, sSAlen(sa), addrbuf, sizeof(addrbuf), NULL, 0, NI_NUMERICHOST); + if (rc) + snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc)); + snprintf(cnx->cc_id, sizeof(cnx->cc_id), "TCP srv [%s]:%hu (%d)", addrbuf, port, cnx->cc_socket); + } + + cnx->cc_proto = IPPROTO_TCP; + + return cnx; + +error: + fd_cnx_destroy(cnx); + return NULL; +} + +/* Same function for SCTP, with a list of local endpoints to bind to */ +struct cnxctx * fd_cnx_serv_sctp(uint16_t port, struct fd_list * ep_list) +{ +#ifdef DISABLE_SCTP + TRACE_DEBUG(INFO, "This function should never been called when SCTP is disabled..."); + ASSERT(0); + CHECK_FCT_DO( ENOTSUP, ); + return NULL; +#else /* DISABLE_SCTP */ + struct cnxctx * cnx = NULL; + + TRACE_ENTRY("%hu %p", port, ep_list); + + CHECK_PARAMS_DO( port, return NULL ); + + /* The connection object */ + CHECK_MALLOC_DO( cnx = fd_cnx_init(0), return NULL ); + + if (fd_g_config->cnf_flags.no_ip6) { + cnx->cc_family = AF_INET; + } else { + cnx->cc_family = AF_INET6; /* can create socket for both IP and IPv6 */ + } + + /* Create the socket */ + CHECK_FCT_DO( fd_sctp_create_bind_server( &cnx->cc_socket, cnx->cc_family, ep_list, port ), goto error ); + + /* Generate the name for the connection object */ + snprintf(cnx->cc_id, sizeof(cnx->cc_id), "SCTP srv :%hu (%d)", port, cnx->cc_socket); + + cnx->cc_proto = IPPROTO_SCTP; + + return cnx; + +error: + fd_cnx_destroy(cnx); + return NULL; +#endif /* DISABLE_SCTP */ +} + +/* Allow clients to connect on the server socket */ +int fd_cnx_serv_listen(struct cnxctx * conn) +{ + CHECK_PARAMS( conn ); + + switch (conn->cc_proto) { + case IPPROTO_TCP: + CHECK_FCT(fd_tcp_listen(conn->cc_socket)); + break; + +#ifndef DISABLE_SCTP + case IPPROTO_SCTP: + CHECK_FCT(fd_sctp_listen(conn->cc_socket)); + break; +#endif /* DISABLE_SCTP */ + + default: + CHECK_PARAMS(0); + } + + return 0; +} + +/* Accept a client (blocking until a new client connects) -- cancelable */ +struct cnxctx * fd_cnx_serv_accept(struct cnxctx * serv) +{ + struct cnxctx * cli = NULL; + sSS ss; + socklen_t ss_len = sizeof(ss); + int cli_sock = 0; + + TRACE_ENTRY("%p", serv); + CHECK_PARAMS_DO(serv, return NULL); + + /* Accept the new connection -- this is blocking until new client enters or until cancellation */ + CHECK_SYS_DO( cli_sock = accept(serv->cc_socket, (sSA *)&ss, &ss_len), return NULL ); + + if (TRACE_BOOL(INFO)) { + fd_log_debug("%s : accepted new client [", fd_cnx_getid(serv)); + sSA_DUMP_NODE( &ss, NI_NUMERICHOST ); + fd_log_debug("].\n"); + } + + CHECK_MALLOC_DO( cli = fd_cnx_init(1), { shutdown(cli_sock, SHUT_RDWR); close(cli_sock); return NULL; } ); + cli->cc_socket = cli_sock; + cli->cc_family = serv->cc_family; + cli->cc_proto = serv->cc_proto; + + /* Set the timeout */ + fd_cnx_s_setto(cli->cc_socket); + + /* Generate the name for the connection object */ + { + char addrbuf[INET6_ADDRSTRLEN]; + char portbuf[10]; + int rc; + + /* Numeric values for debug */ + rc = getnameinfo((sSA *)&ss, sSAlen(&ss), addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) { + snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc)); + portbuf[0] = '\0'; + } + + snprintf(cli->cc_id, sizeof(cli->cc_id), "{%s} (%d) <- [%s]:%s (%d)", + IPPROTO_NAME(cli->cc_proto), serv->cc_socket, + addrbuf, portbuf, cli->cc_socket); + + /* Name for log messages */ + rc = getnameinfo((sSA *)&ss, sSAlen(&ss), cli->cc_remid, sizeof(cli->cc_remid), NULL, 0, 0); + if (rc) + snprintf(cli->cc_remid, sizeof(cli->cc_remid), "[err:%s]", gai_strerror(rc)); + } + +#ifndef DISABLE_SCTP + /* SCTP-specific handlings */ + if (cli->cc_proto == IPPROTO_SCTP) { + /* Retrieve the number of streams */ + CHECK_FCT_DO( fd_sctp_get_str_info( cli->cc_socket, &cli->cc_sctp_para.str_in, &cli->cc_sctp_para.str_out, NULL ), {fd_cnx_destroy(cli); return NULL;} ); + if (cli->cc_sctp_para.str_out < cli->cc_sctp_para.str_in) + cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_out; + else + cli->cc_sctp_para.pairs = cli->cc_sctp_para.str_in; + + TRACE_DEBUG(FULL,"%s : client '%s' (SCTP:%d, %d/%d streams)", fd_cnx_getid(serv), fd_cnx_getid(cli), cli->cc_socket, cli->cc_sctp_para.str_in, cli->cc_sctp_para.str_out); + } +#endif /* DISABLE_SCTP */ + + return cli; +} + +/* Client side: connect to a remote server -- cancelable */ +struct cnxctx * fd_cnx_cli_connect_tcp(sSA * sa /* contains the port already */, socklen_t addrlen) +{ + int sock = 0; + struct cnxctx * cnx = NULL; + + TRACE_ENTRY("%p %d", sa, addrlen); + CHECK_PARAMS_DO( sa && addrlen, return NULL ); + + /* Create the socket and connect, which can take some time and/or fail */ + { + int ret = fd_tcp_client( &sock, sa, addrlen ); + if (ret != 0) { + int lvl; + switch (ret) { + case ECONNREFUSED: + + /* "Normal" errors */ + lvl = FULL; + break; + default: + lvl = INFO; + } + /* Some errors are expected, we log at different level */ + TRACE_DEBUG( lvl, "fd_tcp_client returned an error: %s", strerror(ret)); + return NULL; + } + } + + if (TRACE_BOOL(INFO)) { + fd_log_debug("Connection established to server '"); + sSA_DUMP_NODE_SERV( sa, NI_NUMERICSERV); + fd_log_debug("' (TCP:%d).\n", sock); + } + + /* Once the socket is created successfuly, prepare the remaining of the cnx */ + CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); close(sock); return NULL; } ); + + cnx->cc_socket = sock; + cnx->cc_family = sa->sa_family; + cnx->cc_proto = IPPROTO_TCP; + + /* Set the timeout */ + fd_cnx_s_setto(cnx->cc_socket); + + /* Generate the names for the object */ + { + char addrbuf[INET6_ADDRSTRLEN]; + char portbuf[10]; + int rc; + + /* Numeric values for debug */ + rc = getnameinfo(sa, addrlen, addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) { + snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc)); + portbuf[0] = '\0'; + } + + snprintf(cnx->cc_id, sizeof(cnx->cc_id), "{TCP} -> [%s]:%s (%d)", addrbuf, portbuf, cnx->cc_socket); + + /* Name for log messages */ + rc = getnameinfo(sa, addrlen, cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0); + if (rc) + snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc)); + } + + return cnx; +} + +/* Same for SCTP, accepts a list of remote addresses to connect to (see sctp_connectx for how they are used) */ +struct cnxctx * fd_cnx_cli_connect_sctp(int no_ip6, uint16_t port, struct fd_list * list) +{ +#ifdef DISABLE_SCTP + TRACE_DEBUG(INFO, "This function should never been called when SCTP is disabled..."); + ASSERT(0); + CHECK_FCT_DO( ENOTSUP, ); + return NULL; +#else /* DISABLE_SCTP */ + int sock = 0; + struct cnxctx * cnx = NULL; + sSS primary; + + TRACE_ENTRY("%p", list); + CHECK_PARAMS_DO( list && !FD_IS_LIST_EMPTY(list), return NULL ); + + { + int ret = fd_sctp_client( &sock, no_ip6, port, list ); + if (ret != 0) { + int lvl; + switch (ret) { + case ECONNREFUSED: + + /* "Normal" errors */ + lvl = FULL; + break; + default: + lvl = INFO; + } + /* Some errors are expected, we log at different level */ + TRACE_DEBUG( lvl, "fd_sctp_client returned an error: %s", strerror(ret)); + return NULL; + } + } + + /* Once the socket is created successfuly, prepare the remaining of the cnx */ + CHECK_MALLOC_DO( cnx = fd_cnx_init(1), { shutdown(sock, SHUT_RDWR); close(sock); return NULL; } ); + + cnx->cc_socket = sock; + cnx->cc_family = no_ip6 ? AF_INET : AF_INET6; + cnx->cc_proto = IPPROTO_SCTP; + + /* Set the timeout */ + fd_cnx_s_setto(cnx->cc_socket); + + /* Retrieve the number of streams and primary address */ + CHECK_FCT_DO( fd_sctp_get_str_info( sock, &cnx->cc_sctp_para.str_in, &cnx->cc_sctp_para.str_out, &primary ), goto error ); + if (cnx->cc_sctp_para.str_out < cnx->cc_sctp_para.str_in) + cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_out; + else + cnx->cc_sctp_para.pairs = cnx->cc_sctp_para.str_in; + + if (TRACE_BOOL(INFO)) { + fd_log_debug("Connection established to server '"); + sSA_DUMP_NODE_SERV( &primary, NI_NUMERICSERV); + fd_log_debug("' (SCTP:%d, %d/%d streams).\n", sock, cnx->cc_sctp_para.str_in, cnx->cc_sctp_para.str_out); + } + + /* Generate the names for the object */ + { + char addrbuf[INET6_ADDRSTRLEN]; + char portbuf[10]; + int rc; + + /* Numeric values for debug */ + rc = getnameinfo((sSA *)&primary, sSAlen(&primary), addrbuf, sizeof(addrbuf), portbuf, sizeof(portbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) { + snprintf(addrbuf, sizeof(addrbuf), "[err:%s]", gai_strerror(rc)); + portbuf[0] = '\0'; + } + + snprintf(cnx->cc_id, sizeof(cnx->cc_id), "{SCTP} -> [%s]:%s (%d)", addrbuf, portbuf, cnx->cc_socket); + + /* Name for log messages */ + rc = getnameinfo((sSA *)&primary, sSAlen(&primary), cnx->cc_remid, sizeof(cnx->cc_remid), NULL, 0, 0); + if (rc) + snprintf(cnx->cc_remid, sizeof(cnx->cc_remid), "[err:%s]", gai_strerror(rc)); + } + + return cnx; + +error: + fd_cnx_destroy(cnx); + return NULL; +#endif /* DISABLE_SCTP */ +} + +/* Return a string describing the connection, for debug */ +char * fd_cnx_getid(struct cnxctx * conn) +{ + CHECK_PARAMS_DO( conn, return "" ); + return conn->cc_id; +} + +/* Return the protocol of a connection */ +int fd_cnx_getproto(struct cnxctx * conn) +{ + CHECK_PARAMS_DO( conn, return 0 ); + return conn->cc_proto; +} + +/* Set the hostname to check during handshake */ +void fd_cnx_sethostname(struct cnxctx * conn, char * hn) +{ + CHECK_PARAMS_DO( conn, return ); + conn->cc_tls_para.cn = hn; +} + +/* Return the TLS state of a connection */ +int fd_cnx_getTLS(struct cnxctx * conn) +{ + CHECK_PARAMS_DO( conn, return 0 ); + fd_cpu_flush_cache(); + return conn->cc_status & CC_STATUS_TLS; +} + +/* Get the list of endpoints (IP addresses) of the local and remote peers on this connection */ +int fd_cnx_getremoteeps(struct cnxctx * conn, struct fd_list * eps) +{ + TRACE_ENTRY("%p %p %p", conn, eps); + CHECK_PARAMS(conn && eps); + + /* Check we have a full connection object, not a listening socket (with no remote) */ + CHECK_PARAMS( conn->cc_incoming ); + + /* Retrieve the peer endpoint(s) of the connection */ + switch (conn->cc_proto) { + case IPPROTO_TCP: { + sSS ss; + socklen_t sl; + CHECK_FCT(fd_tcp_get_remote_ep(conn->cc_socket, &ss, &sl)); + CHECK_FCT(fd_ep_add_merge( eps, (sSA *)&ss, sl, EP_FL_LL | EP_FL_PRIMARY )); + } + break; + + #ifndef DISABLE_SCTP + case IPPROTO_SCTP: { + CHECK_FCT(fd_sctp_get_remote_ep(conn->cc_socket, eps)); + } + break; + #endif /* DISABLE_SCTP */ + + default: + CHECK_PARAMS(0); + } + + return 0; +} + +/* Get a string describing the remote peer address (ip address or fqdn) */ +char * fd_cnx_getremoteid(struct cnxctx * conn) +{ + CHECK_PARAMS_DO( conn, return "" ); + return conn->cc_remid; +} + +/* Retrieve a list of all IP addresses of the local system from the kernel, using */ +int fd_cnx_get_local_eps(struct fd_list * list) +{ + struct ifaddrs *iflist, *cur; + CHECK_SYS(getifaddrs(&iflist)); + + for (cur = iflist; cur != NULL; cur = cur->ifa_next) { + if (cur->ifa_flags & IFF_LOOPBACK) + continue; + + if (fd_g_config->cnf_flags.no_ip4 && (cur->ifa_addr->sa_family == AF_INET)) + continue; + + if (fd_g_config->cnf_flags.no_ip6 && (cur->ifa_addr->sa_family == AF_INET6)) + continue; + + CHECK_FCT(fd_ep_add_merge( list, cur->ifa_addr, sSAlen(cur->ifa_addr), EP_FL_LL )); + } + + freeifaddrs(iflist); + + return 0; +} + + +/**************************************/ +/* Use of a connection object */ +/**************************************/ + +/* An error occurred on the socket */ +void fd_cnx_markerror(struct cnxctx * conn) +{ + TRACE_ENTRY("%p", conn); + CHECK_PARAMS_DO( conn, goto fatal ); + + TRACE_DEBUG(FULL, "Error flag set for socket %d (%s / %s)", conn->cc_socket, conn->cc_remid, conn->cc_id); + + /* Mark the error */ + fd_cpu_flush_cache(); + conn->cc_status |= CC_STATUS_ERROR; + + /* Report the error if not reported yet, and not closing */ + if ((!(conn->cc_status & CC_STATUS_CLOSING )) && (!(conn->cc_status & CC_STATUS_SIGNALED ))) { + TRACE_DEBUG(FULL, "Sending FDEVP_CNX_ERROR event"); + CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_ERROR, 0, NULL), goto fatal); + conn->cc_status |= CC_STATUS_SIGNALED; + } + fd_cpu_flush_cache(); + return; +fatal: + /* An unrecoverable error occurred, stop the daemon */ + CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); +} + +/* Set the timeout option on the socket */ +void fd_cnx_s_setto(int sock) +{ + struct timeval tv; + + /* Set a timeout on the socket so that in any case we are not stuck waiting for something */ + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 3; /* allow 3 seconds timeout for TLS session cleanup */ + CHECK_SYS_DO( setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), /* best effort only */ ); + CHECK_SYS_DO( setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)), /* Also timeout for sending, to avoid waiting forever */ ); +} + +/* A recv-like function, taking a cnxctx object instead of socket as entry. We use it to quickly react to timeouts without traversing GNUTLS wrapper each time */ +ssize_t fd_cnx_s_recv(struct cnxctx * conn, void *buffer, size_t length) +{ + ssize_t ret = 0; + int timedout = 0; +again: + ret = recv(conn->cc_socket, buffer, length, 0); + /* Handle special case of timeout */ + if ((ret < 0) && (errno == EAGAIN)) { + fd_cpu_flush_cache(); + if (! (conn->cc_status & CC_STATUS_CLOSING)) + goto again; /* don't care, just ignore */ + if (!timedout) { + timedout ++; /* allow for one timeout while closing */ + goto again; + } + } + + CHECK_SYS_DO(ret, /* continue */); + + /* Mark the error */ + if (ret <= 0) + fd_cnx_markerror(conn); + + return ret; +} + +/* Send */ +static ssize_t fd_cnx_s_send(struct cnxctx * conn, void *buffer, size_t length) +{ + ssize_t ret = 0; + int timedout = 0; +again: + ret = send(conn->cc_socket, buffer, length, 0); + /* Handle special case of timeout */ + if ((ret < 0) && (errno == EAGAIN)) { + fd_cpu_flush_cache(); + if (! (conn->cc_status & CC_STATUS_CLOSING)) + goto again; /* don't care, just ignore */ + if (!timedout) { + timedout ++; /* allow for one timeout while closing */ + goto again; + } + CHECK_SYS_DO(ret, /* continue */); + } + + /* Mark the error */ + if (ret <= 0) + fd_cnx_markerror(conn); + + return ret; +} + +/* Receiver thread (TCP & noTLS) : incoming message is directly saved into the target queue */ +static void * rcvthr_notls_tcp(void * arg) +{ + struct cnxctx * conn = arg; + + TRACE_ENTRY("%p", arg); + CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto out); + + /* Set the thread name */ + { + char buf[48]; + snprintf(buf, sizeof(buf), "Receiver (%d) TCP/noTLS)", conn->cc_socket); + fd_log_threadname ( buf ); + } + + ASSERT( conn->cc_proto == IPPROTO_TCP ); + ASSERT( ! (conn->cc_status & CC_STATUS_TLS) ); + ASSERT( Target_Queue(conn) ); + + /* Receive from a TCP connection: we have to rebuild the message boundaries */ + do { + uint8_t header[4]; + uint8_t * newmsg; + size_t length; + ssize_t ret = 0; + size_t received = 0; + + do { + ret = fd_cnx_s_recv(conn, &header[received], sizeof(header) - received); + if (ret <= 0) { + goto out; /* Stop the thread, the event was already sent */ + } + + received += ret; + } while (received < sizeof(header)); + + length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3]; + + /* Check the received word is a valid begining of a Diameter message */ + if ((header[0] != DIAMETER_VERSION) /* defined in <libfreeDiameter.h> */ + || (length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */ + /* The message is suspect */ + TRACE_DEBUG(INFO, "Received suspect header [ver: %d, size: %zd], assume disconnection", (int)header[0], length); + fd_cnx_markerror(conn); + goto out; /* Stop the thread, the recipient of the event will cleanup */ + } + + /* Ok, now we can really receive the data */ + CHECK_MALLOC_DO( newmsg = malloc( length ), goto fatal ); + memcpy(newmsg, header, sizeof(header)); + + while (received < length) { + pthread_cleanup_push(free, newmsg); /* In case we are canceled, clean the partialy built buffer */ + ret = fd_cnx_s_recv(conn, newmsg + received, length - received); + pthread_cleanup_pop(0); + + if (ret <= 0) { + free(newmsg); + goto out; + } + received += ret; + } + + /* We have received a complete message, pass it to the daemon */ + fd_cpu_flush_cache(); + CHECK_FCT_DO( fd_event_send( Target_Queue(conn), FDEVP_CNX_MSG_RECV, length, newmsg), /* continue or destroy everything? */); + + } while (conn->cc_loop); + +out: + TRACE_DEBUG(FULL, "Thread terminated"); + return NULL; + +fatal: + /* An unrecoverable error occurred, stop the daemon */ + CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); + goto out; +} + +#ifndef DISABLE_SCTP +/* Receiver thread (SCTP & noTLS) : incoming message is directly saved into cc_incoming, no need to care for the stream ID */ +static void * rcvthr_notls_sctp(void * arg) +{ + struct cnxctx * conn = arg; + uint8_t * buf; + size_t bufsz; + int event; + + TRACE_ENTRY("%p", arg); + CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), goto fatal); + + /* Set the thread name */ + { + char buf[48]; + snprintf(buf, sizeof(buf), "Receiver (%d) SCTP/noTLS)", conn->cc_socket); + fd_log_threadname ( buf ); + } + + ASSERT( conn->cc_proto == IPPROTO_SCTP ); + ASSERT( ! (conn->cc_status & CC_STATUS_TLS) ); + ASSERT( Target_Queue(conn) ); + + do { + fd_cpu_flush_cache(); + CHECK_FCT_DO( fd_sctp_recvmeta(conn->cc_socket, NULL, &buf, &bufsz, &event, &conn->cc_status), goto fatal ); + if (event == FDEVP_CNX_ERROR) { + fd_cnx_markerror(conn); + goto out; + } + + if (event == FDEVP_CNX_SHUTDOWN) { + /* Just ignore the notification for now, we will get another error later anyway */ + continue; + } + + fd_cpu_flush_cache(); + CHECK_FCT_DO( fd_event_send( Target_Queue(conn), event, bufsz, buf), goto fatal ); + + } while (conn->cc_loop || (event != FDEVP_CNX_MSG_RECV)); + +out: + TRACE_DEBUG(FULL, "Thread terminated"); + return NULL; + +fatal: + /* An unrecoverable error occurred, stop the daemon */ + CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); + goto out; +} +#endif /* DISABLE_SCTP */ + +/* Start receving messages in clear (no TLS) on the connection */ +int fd_cnx_start_clear(struct cnxctx * conn, int loop) +{ + TRACE_ENTRY("%p %i", conn, loop); + + CHECK_PARAMS( conn && Target_Queue(conn) && (!(conn->cc_status & CC_STATUS_TLS)) && (!conn->cc_loop)); + + /* Release resources in case of a previous call was already made */ + CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */); + + /* Save the loop request */ + conn->cc_loop = loop; + + switch (conn->cc_proto) { + case IPPROTO_TCP: + /* Start the tcp_notls thread */ + CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_tcp, conn ) ); + break; +#ifndef DISABLE_SCTP + case IPPROTO_SCTP: + /* Start the tcp_notls thread */ + CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_notls_sctp, conn ) ); + break; +#endif /* DISABLE_SCTP */ + default: + TRACE_DEBUG(INFO, "Unknown protocol: %d", conn->cc_proto); + ASSERT(0); + return ENOTSUP; + } + + return 0; +} + + + + +/* Returns 0 on error, received data size otherwise (always >= 0) */ +static ssize_t fd_tls_recv_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) +{ + ssize_t ret; +again: + CHECK_GNUTLS_DO( ret = gnutls_record_recv(session, data, sz), + { + switch (ret) { + case GNUTLS_E_REHANDSHAKE: + fd_cpu_flush_cache(); + if (!(conn->cc_status & CC_STATUS_CLOSING)) + CHECK_GNUTLS_DO( ret = gnutls_handshake(session), + { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret)); + } + goto end; + } ); + + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + fd_cpu_flush_cache(); + if (!(conn->cc_status & CC_STATUS_CLOSING)) + goto again; + TRACE_DEBUG(FULL, "Connection is closing, so abord gnutls_record_recv now."); + break; + + case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: + /* The connection is closed */ + TRACE_DEBUG(FULL, "Got 0 size while reading the socket, probably connection closed..."); + break; + + default: + TRACE_DEBUG(INFO, "This GNU TLS error is not handled, assume unrecoverable error"); + } + } ); + + if (ret == 0) + CHECK_GNUTLS_DO( gnutls_bye(session, GNUTLS_SHUT_RDWR), ); + +end: + if (ret <= 0) + fd_cnx_markerror(conn); + return ret; +} + +/* Wrapper around gnutls_record_send to handle some error codes */ +static ssize_t fd_tls_send_handle_error(struct cnxctx * conn, gnutls_session_t session, void * data, size_t sz) +{ + ssize_t ret; +again: + CHECK_GNUTLS_DO( ret = gnutls_record_send(session, data, sz), + { + switch (ret) { + case GNUTLS_E_REHANDSHAKE: + fd_cpu_flush_cache(); + if (!(conn->cc_status & CC_STATUS_CLOSING)) + CHECK_GNUTLS_DO( ret = gnutls_handshake(session), + { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS re-handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret)); + } + goto end; + } ); + + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + fd_cpu_flush_cache(); + if (!(conn->cc_status & CC_STATUS_CLOSING)) + goto again; + TRACE_DEBUG(INFO, "Connection is closing, so abord gnutls_record_send now."); + break; + + default: + TRACE_DEBUG(INFO, "This TLS error is not handled, assume unrecoverable error"); + } + } ); +end: + if (ret <= 0) + fd_cnx_markerror(conn); + + return ret; +} + + +/* The function that receives TLS data and re-builds a Diameter message -- it exits only on error or cancelation */ +int fd_tls_rcvthr_core(struct cnxctx * conn, gnutls_session_t session) +{ + /* No guarantee that GnuTLS preserves the message boundaries, so we re-build it as in TCP */ + do { + uint8_t header[4]; + uint8_t * newmsg; + size_t length; + ssize_t ret = 0; + size_t received = 0; + + do { + ret = fd_tls_recv_handle_error(conn, session, &header[received], sizeof(header) - received); + if (ret <= 0) { + /* The connection is closed */ + goto out; + } + received += ret; + } while (received < sizeof(header)); + + length = ((size_t)header[1] << 16) + ((size_t)header[2] << 8) + (size_t)header[3]; + + /* Check the received word is a valid beginning of a Diameter message */ + if ((header[0] != DIAMETER_VERSION) /* defined in <libfreeDiameter.h> */ + || (length > DIAMETER_MSG_SIZE_MAX)) { /* to avoid too big mallocs */ + /* The message is suspect */ + TRACE_DEBUG(INFO, "Received suspect header [ver: %d, size: %zd], assume disconnection", (int)header[0], length); + fd_cnx_markerror(conn); + goto out; + } + + /* Ok, now we can really receive the data */ + CHECK_MALLOC( newmsg = malloc( length ) ); + memcpy(newmsg, header, sizeof(header)); + + while (received < length) { + pthread_cleanup_push(free, newmsg); /* In case we are canceled, clean the partialy built buffer */ + ret = fd_tls_recv_handle_error(conn, session, newmsg + received, length - received); + pthread_cleanup_pop(0); + + if (ret <= 0) { + free(newmsg); + goto out; + } + received += ret; + } + + /* We have received a complete message, pass it to the daemon */ + fd_cpu_flush_cache(); + CHECK_FCT_DO( ret = fd_event_send( Target_Queue(conn), FDEVP_CNX_MSG_RECV, length, newmsg), + { + free(newmsg); + CHECK_FCT_DO(fd_event_send(fd_g_config->cnf_main_ev, FDEV_TERMINATE, 0, NULL), ); + return ret; + } ); + + } while (1); + +out: + return ENOTCONN; +} + +/* Receiver thread (TLS & 1 stream SCTP or TCP) */ +static void * rcvthr_tls_single(void * arg) +{ + struct cnxctx * conn = arg; + + TRACE_ENTRY("%p", arg); + CHECK_PARAMS_DO(conn && (conn->cc_socket > 0), return NULL ); + + /* Set the thread name */ + { + char buf[48]; + snprintf(buf, sizeof(buf), "Receiver (%d) TLS/single stream", conn->cc_socket); + fd_log_threadname ( buf ); + } + + ASSERT( conn->cc_status & CC_STATUS_TLS ); + ASSERT( Target_Queue(conn) ); + + /* The next function only returns when there is an error on the socket */ + CHECK_FCT_DO(fd_tls_rcvthr_core(conn, conn->cc_tls_para.session), /* continue */); + + TRACE_DEBUG(FULL, "Thread terminated"); + return NULL; +} + +/* Prepare a gnutls session object for handshake */ +int fd_tls_prepare(gnutls_session_t * session, int mode, char * priority, void * alt_creds) +{ + /* Create the session context */ + CHECK_GNUTLS_DO( gnutls_init (session, mode), return ENOMEM ); + + /* Set the algorithm suite */ + if (priority) { + const char * errorpos; + CHECK_GNUTLS_DO( gnutls_priority_set_direct( *session, priority, &errorpos ), + { TRACE_DEBUG(INFO, "Error in priority string '%s' at position: '%s'\n", priority, errorpos); return EINVAL; } ); + } else { + CHECK_GNUTLS_DO( gnutls_priority_set( *session, fd_g_config->cnf_sec_data.prio_cache ), return EINVAL ); + } + + /* Set the credentials of this side of the connection */ + CHECK_GNUTLS_DO( gnutls_credentials_set (*session, GNUTLS_CRD_CERTIFICATE, alt_creds ?: fd_g_config->cnf_sec_data.credentials), return EINVAL ); + + /* Request the remote credentials as well */ + if (mode == GNUTLS_SERVER) { + gnutls_certificate_server_set_request (*session, GNUTLS_CERT_REQUIRE); + } + + return 0; +} + +/* Verify remote credentials after successful handshake (return 0 if OK, EINVAL otherwise) */ +int fd_tls_verify_credentials(gnutls_session_t session, struct cnxctx * conn, int verbose) +{ + int i, ret = 0; + unsigned int gtret; + const gnutls_datum_t *cert_list; + unsigned int cert_list_size; + gnutls_x509_crt_t cert; + time_t now; + + TRACE_ENTRY("%p %d", conn, verbose); + CHECK_PARAMS(conn); + + /* Trace the session information -- http://www.gnu.org/software/gnutls/manual/gnutls.html#Obtaining-session-information */ + if (verbose && TRACE_BOOL(FULL)) { + const char *tmp; + gnutls_kx_algorithm_t kx; + gnutls_credentials_type_t cred; + + fd_log_debug("TLS Session information for connection '%s':\n", conn->cc_id); + + /* print the key exchange's algorithm name */ + GNUTLS_TRACE( kx = gnutls_kx_get (session) ); + GNUTLS_TRACE( tmp = gnutls_kx_get_name (kx) ); + fd_log_debug("\t - Key Exchange: %s\n", tmp); + + /* Check the authentication type used and switch + * to the appropriate. */ + GNUTLS_TRACE( cred = gnutls_auth_get_type (session) ); + switch (cred) + { + case GNUTLS_CRD_IA: + fd_log_debug("\t - TLS/IA session\n"); + break; + + case GNUTLS_CRD_PSK: + /* This returns NULL in server side. */ + if (gnutls_psk_client_get_hint (session) != NULL) + fd_log_debug("\t - PSK authentication. PSK hint '%s'\n", + gnutls_psk_client_get_hint (session)); + /* This returns NULL in client side. */ + if (gnutls_psk_server_get_username (session) != NULL) + fd_log_debug("\t - PSK authentication. Connected as '%s'\n", + gnutls_psk_server_get_username (session)); + break; + + case GNUTLS_CRD_ANON: /* anonymous authentication */ + fd_log_debug("\t - Anonymous DH using prime of %d bits\n", + gnutls_dh_get_prime_bits (session)); + break; + + case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */ + /* Check if we have been using ephemeral Diffie-Hellman. */ + if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) { + fd_log_debug("\t - Ephemeral DH using prime of %d bits\n", + gnutls_dh_get_prime_bits (session)); + } + break; +#ifdef ENABLE_SRP + case GNUTLS_CRD_SRP: + fd_log_debug("\t - SRP session with username %s\n", + gnutls_srp_server_get_username (session)); + break; +#endif /* ENABLE_SRP */ + + default: + fd_log_debug("\t - Different type of credentials for the session (%d).\n", cred); + break; + + } + + /* print the protocol's name (ie TLS 1.0) */ + tmp = gnutls_protocol_get_name (gnutls_protocol_get_version (session)); + fd_log_debug("\t - Protocol: %s\n", tmp); + + /* print the certificate type of the peer. ie X.509 */ + tmp = gnutls_certificate_type_get_name (gnutls_certificate_type_get (session)); + fd_log_debug("\t - Certificate Type: %s\n", tmp); + + /* print the compression algorithm (if any) */ + tmp = gnutls_compression_get_name (gnutls_compression_get (session)); + fd_log_debug("\t - Compression: %s\n", tmp); + + /* print the name of the cipher used. ie 3DES. */ + tmp = gnutls_cipher_get_name (gnutls_cipher_get (session)); + fd_log_debug("\t - Cipher: %s\n", tmp); + + /* Print the MAC algorithms name. ie SHA1 */ + tmp = gnutls_mac_get_name (gnutls_mac_get (session)); + fd_log_debug("\t - MAC: %s\n", tmp); + } + + /* First, use built-in verification */ + CHECK_GNUTLS_DO( gnutls_certificate_verify_peers2 (session, >ret), return EINVAL ); + if (gtret) { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :\n", conn->cc_socket, conn->cc_remid, conn->cc_id); + if (gtret & GNUTLS_CERT_INVALID) + fd_log_debug(" - The certificate is not trusted (unknown CA? expired?)\n"); + if (gtret & GNUTLS_CERT_REVOKED) + fd_log_debug(" - The certificate has been revoked.\n"); + if (gtret & GNUTLS_CERT_SIGNER_NOT_FOUND) + fd_log_debug(" - The certificate hasn't got a known issuer.\n"); + if (gtret & GNUTLS_CERT_SIGNER_NOT_CA) + fd_log_debug(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.\n"); + if (gtret & GNUTLS_CERT_INSECURE_ALGORITHM) + fd_log_debug(" - The certificate signature uses a weak algorithm.\n"); + } + return EINVAL; + } + + /* Code from http://www.gnu.org/software/gnutls/manual/gnutls.html#Verifying-peer_0027s-certificate */ + if (gnutls_certificate_type_get (session) != GNUTLS_CRT_X509) + return EINVAL; + + GNUTLS_TRACE( cert_list = gnutls_certificate_get_peers (session, &cert_list_size) ); + if (cert_list == NULL) + return EINVAL; + + now = time(NULL); + + if (verbose && TRACE_BOOL(FULL)) { + char serial[40]; + char dn[128]; + size_t size; + unsigned int algo, bits; + time_t expiration_time, activation_time; + + fd_log_debug("TLS Certificate information for connection '%s' (%d certs provided):\n", conn->cc_id, cert_list_size); + for (i = 0; i < cert_list_size; i++) + { + + CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL); + CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL); + + fd_log_debug(" Certificate %d info:\n", i); + + GNUTLS_TRACE( expiration_time = gnutls_x509_crt_get_expiration_time (cert) ); + GNUTLS_TRACE( activation_time = gnutls_x509_crt_get_activation_time (cert) ); + + fd_log_debug("\t - Certificate is valid since: %s", ctime (&activation_time)); + fd_log_debug("\t - Certificate expires: %s", ctime (&expiration_time)); + + /* Print the serial number of the certificate. */ + size = sizeof (serial); + gnutls_x509_crt_get_serial (cert, serial, &size); + + fd_log_debug("\t - Certificate serial number: "); + { + int j; + for (j = 0; j < size; j++) { + fd_log_debug("%02.2hhx", serial[j]); + } + } + fd_log_debug("\n"); + + /* Extract some of the public key algorithm's parameters */ + GNUTLS_TRACE( algo = gnutls_x509_crt_get_pk_algorithm (cert, &bits) ); + fd_log_debug("\t - Certificate public key: %s\n", + gnutls_pk_algorithm_get_name (algo)); + + /* Print the version of the X.509 certificate. */ + fd_log_debug("\t - Certificate version: #%d\n", + gnutls_x509_crt_get_version (cert)); + + size = sizeof (dn); + GNUTLS_TRACE( gnutls_x509_crt_get_dn (cert, dn, &size) ); + fd_log_debug("\t - DN: %s\n", dn); + + size = sizeof (dn); + GNUTLS_TRACE( gnutls_x509_crt_get_issuer_dn (cert, dn, &size) ); + fd_log_debug("\t - Issuer's DN: %s\n", dn); + + GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) ); + } + } + + /* Check validity of all the certificates */ + for (i = 0; i < cert_list_size; i++) + { + time_t deadline; + + CHECK_GNUTLS_DO( gnutls_x509_crt_init (&cert), return EINVAL); + CHECK_GNUTLS_DO( gnutls_x509_crt_import (cert, &cert_list[i], GNUTLS_X509_FMT_DER), return EINVAL); + + GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(cert) ); + if ((deadline != (time_t)-1) && (deadline < now)) { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :\n", conn->cc_socket, conn->cc_remid, conn->cc_id); + fd_log_debug(" - The certificate %d in the chain is expired\n", i); + } + ret = EINVAL; + } + + GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(cert) ); + if ((deadline != (time_t)-1) && (deadline > now)) { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :\n", conn->cc_socket, conn->cc_remid, conn->cc_id); + fd_log_debug(" - The certificate %d in the chain is not yet activated\n", i); + } + ret = EINVAL; + } + + if ((i == 0) && (conn->cc_tls_para.cn)) { + if (!gnutls_x509_crt_check_hostname (cert, conn->cc_tls_para.cn)) { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS: Remote certificate invalid on socket %d (Remote: '%s')(Connection: '%s') :\n", conn->cc_socket, conn->cc_remid, conn->cc_id); + fd_log_debug(" - The certificate hostname does not match '%s'\n", conn->cc_tls_para.cn); + } + ret = EINVAL; + } + } + + GNUTLS_TRACE( gnutls_x509_crt_deinit (cert) ); + } + + return ret; +} + +/* TLS handshake a connection; no need to have called start_clear before. Reception is active if handhsake is successful */ +int fd_cnx_handshake(struct cnxctx * conn, int mode, char * priority, void * alt_creds) +{ + TRACE_ENTRY( "%p %d %p %p", conn, mode, priority, alt_creds); + CHECK_PARAMS( conn && (!(conn->cc_status & CC_STATUS_TLS)) && ( (mode == GNUTLS_CLIENT) || (mode == GNUTLS_SERVER) ) && (!conn->cc_loop) ); + + /* Save the mode */ + conn->cc_tls_para.mode = mode; + + /* Cancel receiving thread if any -- it should already be terminated anyway, we just release the resources */ + CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */); + + /* Once TLS handshake is done, we don't stop after the first message */ + conn->cc_loop = 1; + + /* Prepare the master session credentials and priority */ + CHECK_FCT( fd_tls_prepare(&conn->cc_tls_para.session, mode, priority, alt_creds) ); + + /* Special case: multi-stream TLS is not natively managed in GNU TLS, we use a wrapper library */ + if (conn->cc_sctp_para.pairs > 1) { +#ifdef DISABLE_SCTP + ASSERT(0); + CHECK_FCT( ENOTSUP ); +#else /* DISABLE_SCTP */ + /* Initialize the wrapper, start the demux thread */ + CHECK_FCT( fd_sctps_init(conn) ); +#endif /* DISABLE_SCTP */ + } else { + /* Set the transport pointer passed to push & pull callbacks */ + GNUTLS_TRACE( gnutls_transport_set_ptr( conn->cc_tls_para.session, (gnutls_transport_ptr_t) conn ) ); + + /* Set the push and pull callbacks */ + GNUTLS_TRACE( gnutls_transport_set_pull_function(conn->cc_tls_para.session, (void *)fd_cnx_s_recv) ); + GNUTLS_TRACE( gnutls_transport_set_push_function(conn->cc_tls_para.session, (void *)fd_cnx_s_send) ); + } + + /* Mark the connection as protected from here, so that the gnutls credentials will be freed */ + fd_cpu_flush_cache(); + conn->cc_status |= CC_STATUS_TLS; + + /* Handshake master session */ + { + int ret; + + /* When gnutls 2.10.1 is around, we should use gnutls_certificate_set_verify_function and fd_tls_verify_credentials, so that handshake fails directly. */ + + CHECK_GNUTLS_DO( ret = gnutls_handshake(conn->cc_tls_para.session), + { + if (TRACE_BOOL(INFO)) { + fd_log_debug("TLS Handshake failed on socket %d (%s) : %s\n", conn->cc_socket, conn->cc_id, gnutls_strerror(ret)); + } + fd_cnx_markerror(conn); + return EINVAL; + } ); + + /* Now verify the remote credentials are valid -- only simple tests here */ + CHECK_FCT_DO( fd_tls_verify_credentials(conn->cc_tls_para.session, conn, 1), + { + CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_RDWR), ); + fd_cnx_markerror(conn); + return EINVAL; + }); + } + + /* Multi-stream TLS: handshake other streams as well */ + if (conn->cc_sctp_para.pairs > 1) { +#ifndef DISABLE_SCTP + /* Start reading the messages from the master session. That way, if the remote peer closed, we are not stuck inside handshake */ + CHECK_FCT(fd_sctps_startthreads(conn, 0)); + + /* Resume all additional sessions from the master one. */ + CHECK_FCT(fd_sctps_handshake_others(conn, priority, alt_creds)); + + /* Start decrypting the messages from all threads and queuing them in target queue */ + CHECK_FCT(fd_sctps_startthreads(conn, 1)); +#endif /* DISABLE_SCTP */ + } else { + /* Start decrypting the data */ + CHECK_POSIX( pthread_create( &conn->cc_rcvthr, NULL, rcvthr_tls_single, conn ) ); + } + + return 0; +} + +/* Retrieve TLS credentials of the remote peer, after handshake */ +int fd_cnx_getcred(struct cnxctx * conn, const gnutls_datum_t **cert_list, unsigned int *cert_list_size) +{ + TRACE_ENTRY("%p %p %p", conn, cert_list, cert_list_size); + CHECK_PARAMS( conn && (conn->cc_status & CC_STATUS_TLS) && cert_list && cert_list_size ); + + /* This function only works for X.509 certificates. */ + CHECK_PARAMS( gnutls_certificate_type_get (conn->cc_tls_para.session) == GNUTLS_CRT_X509 ); + + GNUTLS_TRACE( *cert_list = gnutls_certificate_get_peers (conn->cc_tls_para.session, cert_list_size) ); + if (*cert_list == NULL) { + TRACE_DEBUG(INFO, "No certificate was provided by remote peer / an error occurred."); + return EINVAL; + } + + TRACE_DEBUG( FULL, "Saved certificate chain (%d certificates) in peer structure.", *cert_list_size); + + return 0; +} + +/* Receive next message. if timeout is not NULL, wait only until timeout. This function only pulls from a queue, mgr thread is filling that queue aynchrounously. */ +/* if the altfifo has been set on this conn object, this function must not be called */ +int fd_cnx_receive(struct cnxctx * conn, struct timespec * timeout, unsigned char **buf, size_t * len) +{ + int ev; + size_t ev_sz; + void * ev_data; + + TRACE_ENTRY("%p %p %p %p", conn, timeout, buf, len); + CHECK_PARAMS(conn && (conn->cc_socket > 0) && buf && len); + CHECK_PARAMS(conn->cc_rcvthr != (pthread_t)NULL); + CHECK_PARAMS(conn->cc_alt == NULL); + + /* Now, pull the first event */ +get_next: + if (timeout) { + CHECK_FCT( fd_event_timedget(conn->cc_incoming, timeout, FDEVP_PSM_TIMEOUT, &ev, &ev_sz, &ev_data) ); + } else { + CHECK_FCT( fd_event_get(conn->cc_incoming, &ev, &ev_sz, &ev_data) ); + } + + switch (ev) { + case FDEVP_CNX_MSG_RECV: + /* We got one */ + *len = ev_sz; + *buf = ev_data; + return 0; + + case FDEVP_PSM_TIMEOUT: + TRACE_DEBUG(FULL, "Timeout event received"); + return ETIMEDOUT; + + case FDEVP_CNX_EP_CHANGE: + /* We ignore this event */ + goto get_next; + + case FDEVP_CNX_ERROR: + TRACE_DEBUG(FULL, "Received ERROR event on the connection"); + return ENOTCONN; + } + + TRACE_DEBUG(INFO, "Received unexpected event %d (%s)", ev, fd_pev_str(ev)); + return EINVAL; +} + +/* Set an alternate FIFO list to send FDEVP_CNX_* events to */ +int fd_cnx_recv_setaltfifo(struct cnxctx * conn, struct fifo * alt_fifo) +{ + TRACE_ENTRY( "%p %p", conn, alt_fifo ); + CHECK_PARAMS( conn && alt_fifo && conn->cc_incoming ); + + /* The magic function does it all */ + CHECK_FCT( fd_fifo_move( conn->cc_incoming, alt_fifo, &conn->cc_alt ) ); + + return 0; +} + +/* Send function when no multi-stream is involved, or sending on stream #0 (send() always use stream 0)*/ +static int send_simple(struct cnxctx * conn, unsigned char * buf, size_t len) +{ + ssize_t ret; + size_t sent = 0; + TRACE_ENTRY("%p %p %zd", conn, buf, len); + do { + fd_cpu_flush_cache(); + if (conn->cc_status & CC_STATUS_TLS) { + CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_tls_para.session, buf + sent, len - sent), ); + } else { + /* Maybe better to replace this call with sendmsg for atomic sending? */ + CHECK_SYS_DO( ret = fd_cnx_s_send(conn, buf + sent, len - sent), ); + } + if (ret <= 0) + return ENOTCONN; + + sent += ret; + } while ( sent < len ); + return 0; +} + +/* Send a message -- this is synchronous -- and we assume it's never called by several threads at the same time, so we don't protect. */ +int fd_cnx_send(struct cnxctx * conn, unsigned char * buf, size_t len, uint32_t flags) +{ + TRACE_ENTRY("%p %p %zd %x", conn, buf, len, flags); + + CHECK_PARAMS(conn && (conn->cc_socket > 0) && (! (conn->cc_status & CC_STATUS_ERROR)) && buf && len); + + TRACE_DEBUG(FULL, "Sending %zdb %sdata on connection %s", len, (conn->cc_status & CC_STATUS_TLS) ? "TLS-protected ":"", conn->cc_id); + + switch (conn->cc_proto) { + case IPPROTO_TCP: + CHECK_FCT( send_simple(conn, buf, len) ); + break; + +#ifndef DISABLE_SCTP + case IPPROTO_SCTP: { + if (flags & FD_CNX_BROADCAST) { + /* Send the buffer over all other streams */ + uint16_t str; + fd_cpu_flush_cache(); + if (conn->cc_status & CC_STATUS_TLS) { + for ( str=1; str < conn->cc_sctp_para.pairs; str++) { + ssize_t ret; + size_t sent = 0; + do { + CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctps_data.array[str].session, buf + sent, len - sent), ); + if (ret <= 0) + return ENOTCONN; + + sent += ret; + } while ( sent < len ); + } + } else { + for ( str=1; str < conn->cc_sctp_para.str_out; str++) { + CHECK_FCT_DO( fd_sctp_sendstr(conn->cc_socket, str, buf, len, &conn->cc_status), { fd_cnx_markerror(conn); return ENOTCONN; } ); + } + } + + /* Set the ORDERED flag also so that it is sent over stream 0 as well */ + flags &= FD_CNX_ORDERED; + } + + if (flags & FD_CNX_ORDERED) { + /* We send over stream #0 */ + CHECK_FCT( send_simple(conn, buf, len) ); + } else { + /* Default case : no flag specified */ + + int another_str = 0; /* do we send over stream #0 ? */ + + if ((conn->cc_sctp_para.str_out > 1) && ((! (conn->cc_status & CC_STATUS_TLS)) || (conn->cc_sctp_para.pairs > 1))) { + /* Update the id of the stream we will send this message over */ + conn->cc_sctp_para.next += 1; + conn->cc_sctp_para.next %= ((conn->cc_status & CC_STATUS_TLS) ? conn->cc_sctp_para.pairs : conn->cc_sctp_para.str_out); + another_str = (conn->cc_sctp_para.next ? 1 : 0); + } + + if ( ! another_str ) { + CHECK_FCT( send_simple(conn, buf, len) ); + } else { + if (!(conn->cc_status & CC_STATUS_TLS)) { + CHECK_FCT_DO( fd_sctp_sendstr(conn->cc_socket, conn->cc_sctp_para.next, buf, len, &conn->cc_status), { fd_cnx_markerror(conn); return ENOTCONN; } ); + } else { + /* push the record to the appropriate session */ + ssize_t ret; + size_t sent = 0; + ASSERT(conn->cc_sctps_data.array != NULL); + do { + CHECK_GNUTLS_DO( ret = fd_tls_send_handle_error(conn, conn->cc_sctps_data.array[conn->cc_sctp_para.next].session, buf + sent, len - sent), ); + if (ret <= 0) + return ENOTCONN; + + sent += ret; + } while ( sent < len ); + } + } + } + } + break; +#endif /* DISABLE_SCTP */ + + default: + TRACE_DEBUG(INFO, "Unknwon protocol: %d", conn->cc_proto); + ASSERT(0); + return ENOTSUP; /* or EINVAL... */ + } + + return 0; +} + + +/**************************************/ +/* Destruction of connection */ +/**************************************/ + +/* Destroy a conn structure, and shutdown the socket */ +void fd_cnx_destroy(struct cnxctx * conn) +{ + TRACE_ENTRY("%p", conn); + + CHECK_PARAMS_DO(conn, return); + + fd_cpu_flush_cache(); + conn->cc_status |= CC_STATUS_CLOSING; + + /* Initiate shutdown of the TLS session(s): call gnutls_bye(WR), then read until error */ + if (conn->cc_status & CC_STATUS_TLS) { +#ifndef DISABLE_SCTP + if (conn->cc_sctp_para.pairs > 1) { + if (! (conn->cc_status & CC_STATUS_ERROR )) { + /* Bye on master session */ + CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) ); + } + + if (! (conn->cc_status & CC_STATUS_ERROR ) ) { + /* and other stream pairs */ + fd_sctps_bye(conn); + } + + if (! (conn->cc_status & CC_STATUS_ERROR ) ) { + /* Now wait for all decipher threads to terminate */ + fd_sctps_waitthreadsterm(conn); + } else { + /* Abord the threads, the connection is dead already */ + fd_sctps_stopthreads(conn); + } + + /* Deinit gnutls resources */ + fd_sctps_gnutls_deinit_others(conn); + if (conn->cc_tls_para.session) { + GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) ); + conn->cc_tls_para.session = NULL; + } + + /* Destroy the wrapper (also stops the demux thread) */ + fd_sctps_destroy(conn); + + } else { +#endif /* DISABLE_SCTP */ + /* We are not using the sctps wrapper layer */ + if (! (conn->cc_status & CC_STATUS_ERROR ) ) { + /* Master session */ + CHECK_GNUTLS_DO( gnutls_bye(conn->cc_tls_para.session, GNUTLS_SHUT_WR), fd_cnx_markerror(conn) ); + } + + if (! (conn->cc_status & CC_STATUS_ERROR ) ) { + /* In this case, just wait for thread rcvthr_tls_single to terminate */ + if (conn->cc_rcvthr != (pthread_t)NULL) { + CHECK_POSIX_DO( pthread_join(conn->cc_rcvthr, NULL), /* continue */ ); + conn->cc_rcvthr = (pthread_t)NULL; + } + } else { + /* Cancel the receiver thread in case it did not already terminate */ + CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ ); + } + + /* Free the resources of the TLS session */ + if (conn->cc_tls_para.session) { + GNUTLS_TRACE( gnutls_deinit(conn->cc_tls_para.session) ); + conn->cc_tls_para.session = NULL; + } + +#ifndef DISABLE_SCTP + } +#endif /* DISABLE_SCTP */ + } + + /* Terminate the thread in case it is not done yet -- is there any such case left ?*/ + CHECK_FCT_DO( fd_thr_term(&conn->cc_rcvthr), /* continue */ ); + + /* Shut the connection down */ + if (conn->cc_socket > 0) { + shutdown(conn->cc_socket, SHUT_RDWR); + close(conn->cc_socket); + conn->cc_socket = -1; + } + + /* Empty and destroy FIFO list */ + if (conn->cc_incoming) { + fd_event_destroy( &conn->cc_incoming, free ); + } + + /* Free the object */ + free(conn); + + /* Done! */ + return; +}