diff options
Diffstat (limited to 'snmplib/transports/snmpDTLSUDPDomain.c')
-rw-r--r-- | snmplib/transports/snmpDTLSUDPDomain.c | 1788 |
1 files changed, 1788 insertions, 0 deletions
diff --git a/snmplib/transports/snmpDTLSUDPDomain.c b/snmplib/transports/snmpDTLSUDPDomain.c new file mode 100644 index 0000000..9c56d19 --- /dev/null +++ b/snmplib/transports/snmpDTLSUDPDomain.c @@ -0,0 +1,1788 @@ +/* Portions of this file are subject to the following copyright(s). See + * the Net-SNMP's COPYING file for more details and other copyrights + * that may apply: + */ +/* + * Portions of this file are copyrighted by: + * Copyright Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms specified in the COPYING file + * distributed with the Net-SNMP package. + */ +/* + * See the following web pages for useful documentation on this transport: + * http://www.net-snmp.org/wiki/index.php/TUT:Using_TLS + * http://www.net-snmp.org/wiki/index.php/Using_DTLS + */ + +#include <net-snmp/net-snmp-config.h> + +#ifdef HAVE_LIBSSL_DTLS + +#include <net-snmp/net-snmp-features.h> + +netsnmp_feature_require(cert_util) +netsnmp_feature_require(sockaddr_size) + +#include <net-snmp/library/snmpDTLSUDPDomain.h> +#include <net-snmp/library/snmpUDPIPv6Domain.h> + +#include <stdio.h> +#include <sys/types.h> +#include <ctype.h> +#include <errno.h> + +#if HAVE_STRING_H +#include <string.h> +#else +#include <strings.h> +#endif +#if HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#if HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#if HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#if HAVE_NETDB_H +#include <netdb.h> +#endif +#if HAVE_SYS_UIO_H +#include <sys/uio.h> +#endif + +#if HAVE_DMALLOC_H +#include <dmalloc.h> +#endif + +#include <net-snmp/types.h> +#include <net-snmp/output_api.h> +#include <net-snmp/config_api.h> + +#include <net-snmp/library/snmp_transport.h> +#include <net-snmp/library/system.h> +#include <net-snmp/library/tools.h> +#include <net-snmp/library/callback.h> + +#include "openssl/bio.h" +#include "openssl/ssl.h" +#include "openssl/err.h" +#include "openssl/rand.h" + +#include <net-snmp/library/snmpSocketBaseDomain.h> +#include <net-snmp/library/snmpTLSBaseDomain.h> +#include <net-snmp/library/snmpUDPDomain.h> +#include <net-snmp/library/cert_util.h> +#include <net-snmp/library/snmp_openssl.h> + +#ifndef INADDR_NONE +#define INADDR_NONE -1 +#endif + +#define WE_ARE_SERVER 0 +#define WE_ARE_CLIENT 1 + +oid netsnmpDTLSUDPDomain[] = { TRANSPORT_DOMAIN_DTLS_UDP_IP }; +size_t netsnmpDTLSUDPDomain_len = OID_LENGTH(netsnmpDTLSUDPDomain); + +static netsnmp_tdomain dtlsudpDomain; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN +static int openssl_addr_index6 = 0; +#endif + +/* this stores openssl credentials for each connection since openssl + can't do it for us at the moment; hopefully future versions will + change */ +typedef struct bio_cache_s { + BIO *read_bio; /* OpenSSL will read its incoming SSL packets from here */ + BIO *write_bio; /* OpenSSL will write its outgoing SSL packets to here */ + netsnmp_sockaddr_storage sas; + u_int flags; + struct bio_cache_s *next; + int msgnum; + char *write_cache; + size_t write_cache_len; + _netsnmpTLSBaseData *tlsdata; +} bio_cache; + +/** bio_cache flags */ +#define NETSNMP_BIO_HAVE_COOKIE 0x0001 /* verified cookie */ +#define NETSNMP_BIO_CONNECTED 0x0002 /* recieved decoded data */ +#define NETSNMP_BIO_DISCONNECTED 0x0004 /* peer shutdown */ + +static bio_cache *biocache = NULL; + +static int openssl_addr_index = 0; + +static int netsnmp_dtls_verify_cookie(SSL *ssl, unsigned char *cookie, + unsigned int cookie_len); +static int netsnmp_dtls_gen_cookie(SSL *ssl, unsigned char *cookie, + unsigned int *cookie_len); + +/* this stores remote connections in a list to search through */ +/* XXX: optimize for searching */ +/* XXX: handle state issues for new connections to reduce DOS issues */ +/* (TLS should do this, but openssl can't do more than one ctx per sock */ +/* XXX: put a timer on the cache for expirary purposes */ +static bio_cache *find_bio_cache(netsnmp_sockaddr_storage *from_addr) { + bio_cache *cachep = NULL; + + for(cachep = biocache; cachep; cachep = cachep->next) { + + if (cachep->sas.sa.sa_family != from_addr->sa.sa_family) + continue; + + if ((from_addr->sa.sa_family == AF_INET) && + ((cachep->sas.sin.sin_addr.s_addr != + from_addr->sin.sin_addr.s_addr) || + (cachep->sas.sin.sin_port != from_addr->sin.sin_port))) + continue; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + else if ((from_addr->sa.sa_family == AF_INET6) && + ((cachep->sas.sin6.sin6_port != from_addr->sin6.sin6_port) || + (cachep->sas.sin6.sin6_scope_id != + from_addr->sin6.sin6_scope_id) || + (memcmp(cachep->sas.sin6.sin6_addr.s6_addr, + from_addr->sin6.sin6_addr.s6_addr, + sizeof(from_addr->sin6.sin6_addr.s6_addr)) != 0))) + continue; +#endif + /* found an existing connection */ + break; + } + return cachep; +} + +/* removes a single cache entry and returns SUCCESS on finding and + removing it. */ +static int remove_bio_cache(bio_cache *thiscache) { + bio_cache *cachep = NULL, *prevcache = NULL; + cachep = biocache; + while(cachep) { + if (cachep == thiscache) { + + /* remove it from the list */ + if (NULL == prevcache) { + /* at the first cache in the list */ + biocache = thiscache->next; + } else { + prevcache->next = thiscache->next; + } + + return SNMPERR_SUCCESS; + } + prevcache = cachep; + cachep = cachep->next; + } + return SNMPERR_GENERR; +} + +/* frees the contents of a bio_cache */ +static void free_bio_cache(bio_cache *cachep) { +/* These are freed by the SSL_free() call */ +/* + BIO_free(cachep->read_bio); + BIO_free(cachep->write_bio); +*/ + DEBUGMSGTL(("9:dtlsudp:bio_cache", "releasing %p\n", cachep)); + SNMP_FREE(cachep->write_cache); + netsnmp_tlsbase_free_tlsdata(cachep->tlsdata); +} + +static void remove_and_free_bio_cache(bio_cache *cachep) { + /** no debug, remove_bio_cache does it */ + remove_bio_cache(cachep); + free_bio_cache(cachep); +} + + +/* XXX: lots of malloc/state cleanup needed */ +#define DIEHERE(msg) do { snmp_log(LOG_ERR, "%s\n", msg); return NULL; } while(0) + +static bio_cache * +start_new_cached_connection(netsnmp_transport *t, + netsnmp_sockaddr_storage *remote_addr, + int we_are_client) { + bio_cache *cachep = NULL; + _netsnmpTLSBaseData *tlsdata; + + DEBUGTRACETOK("9:dtlsudp"); + + /* RFC5953: section 5.3.1, step 1: + 1) The snmpTlstmSessionOpens counter is incremented. + */ + if (we_are_client) + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENS); + + if (!t->sock) + DIEHERE("no socket passed in to start_new_cached_connection\n"); + if (!remote_addr) + DIEHERE("no remote_addr passed in to start_new_cached_connection\n"); + + cachep = SNMP_MALLOC_TYPEDEF(bio_cache); + if (!cachep) + return NULL; + + /* allocate our TLS specific data */ + if (NULL == (tlsdata = netsnmp_tlsbase_allocate_tlsdata(t, !we_are_client))) { + SNMP_FREE(cachep); + return NULL; + } + cachep->tlsdata = tlsdata; + + /* RFC5953: section 5.3.1, step 1: + 2) The client selects the appropriate certificate and cipher_suites + for the key agreement based on the tmSecurityName and the + tmRequestedSecurityLevel for the session. For sessions being + established as a result of a SNMP-TARGET-MIB based operation, the + certificate will potentially have been identified via the + snmpTlstmParamsTable mapping and the cipher_suites will have to + be taken from system-wide or implementation-specific + configuration. If no row in the snmpTlstmParamsTable exists then + implementations MAY choose to establish the connection using a + default client certificate available to the application. + Otherwise, the certificate and appropriate cipher_suites will + need to be passed to the openSession() ASI as supplemental + information or configured through an implementation-dependent + mechanism. It is also implementation-dependent and possibly + policy-dependent how tmRequestedSecurityLevel will be used to + influence the security capabilities provided by the (D)TLS + connection. However this is done, the security capabilities + provided by (D)TLS MUST be at least as high as the level of + security indicated by the tmRequestedSecurityLevel parameter. + The actual security level of the session is reported in the + tmStateReference cache as tmSecurityLevel. For (D)TLS to provide + strong authentication, each principal acting as a command + generator SHOULD have its own certificate. + */ + /* Implementation notes: + + This Information is passed in via the transport and default + paremeters + */ + /* see if we have base configuration to copy in to this new one */ + if (NULL != t->data && t->data_length == sizeof(_netsnmpTLSBaseData)) { + _netsnmpTLSBaseData *parentdata = t->data; + if (parentdata->our_identity) + tlsdata->our_identity = strdup(parentdata->our_identity); + if (parentdata->their_identity) + tlsdata->their_identity = strdup(parentdata->their_identity); + if (parentdata->their_fingerprint) + tlsdata->their_fingerprint = strdup(parentdata->their_fingerprint); + if (parentdata->trust_cert) + tlsdata->trust_cert = strdup(parentdata->trust_cert); + if (parentdata->their_hostname) + tlsdata->their_hostname = strdup(parentdata->their_hostname); + } + + DEBUGMSGTL(("dtlsudp", "starting a new connection\n")); + cachep->next = biocache; + biocache = cachep; + + if (remote_addr->sa.sa_family == AF_INET) + memcpy(&cachep->sas.sin, &remote_addr->sin, sizeof(remote_addr->sin)); +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + else if (remote_addr->sa.sa_family == AF_INET6) + memcpy(&cachep->sas.sin6, &remote_addr->sin6, sizeof(remote_addr->sin6)); +#endif + else + DIEHERE("unknown address family"); + + /* create caching memory bios for OpenSSL to read and write to */ + + cachep->read_bio = BIO_new(BIO_s_mem()); /* openssl reads from */ + if (!cachep->read_bio) + DIEHERE("failed to create the openssl read_bio"); + + cachep->write_bio = BIO_new(BIO_s_mem()); /* openssl writes to */ + if (!cachep->write_bio) { + BIO_free(cachep->read_bio); + cachep->read_bio = NULL; + DIEHERE("failed to create the openssl write_bio"); + } + + BIO_set_mem_eof_return(cachep->read_bio, -1); + BIO_set_mem_eof_return(cachep->write_bio, -1); + + if (we_are_client) { + /* we're the client */ + DEBUGMSGTL(("dtlsudp", + "starting a new connection as a client to sock: %d\n", + t->sock)); + tlsdata->ssl = SSL_new(sslctx_client_setup(DTLSv1_method(), tlsdata)); + + /* XXX: session setting 735 */ + } else { + /* we're the server */ + SSL_CTX *ctx = sslctx_server_setup(DTLSv1_method()); + if (!ctx) { + BIO_free(cachep->read_bio); + BIO_free(cachep->write_bio); + cachep->read_bio = NULL; + cachep->write_bio = NULL; + DIEHERE("failed to create the SSL Context"); + } + + /* turn on cookie exchange */ + /* Set DTLS cookie generation and verification callbacks */ + SSL_CTX_set_cookie_generate_cb(ctx, netsnmp_dtls_gen_cookie); + SSL_CTX_set_cookie_verify_cb(ctx, netsnmp_dtls_verify_cookie); + + tlsdata->ssl = SSL_new(ctx); + } + + if (!tlsdata->ssl) { + BIO_free(cachep->read_bio); + BIO_free(cachep->write_bio); + cachep->read_bio = NULL; + cachep->write_bio = NULL; + DIEHERE("failed to create the SSL session structure"); + } + + SSL_set_mode(tlsdata->ssl, SSL_MODE_AUTO_RETRY); + + /* set the bios that openssl should read from and write to */ + /* (and we'll do the opposite) */ + SSL_set_bio(tlsdata->ssl, cachep->read_bio, cachep->write_bio); + + /* RFC5953: section 5.3.1, step 1: + 3) Using the destTransportDomain and destTransportAddress values, + the client will initiate the (D)TLS handshake protocol to + establish session keys for message integrity and encryption. + + If the attempt to establish a session is unsuccessful, then + snmpTlstmSessionOpenErrors is incremented, an error indication is + returned, and processing stops. If the session failed to open + because the presented server certificate was unknown or invalid + then the snmpTlstmSessionUnknownServerCertificate or + snmpTlstmSessionInvalidServerCertificates MUST be incremented and + a snmpTlstmServerCertificateUnknown or + snmpTlstmServerInvalidCertificate notification SHOULD be sent as + appropriate. Reasons for server certificate invalidation + includes, but is not limited to, cryptographic validation + failures and an unexpected presented certificate identity. + */ + /* Implementation notes: + + Because we're working asyncronously the real "end" point of + opening a connection doesn't occur here as certificate + verification and other things needs to happen first in the + verify callback, etc. See the netsnmp_dtlsudp_recv() + function for the final processing. + */ + /* set the SSL notion of we_are_client/server */ + if (we_are_client) + SSL_set_connect_state(tlsdata->ssl); + else { + /* XXX: we need to only create cache entries when cookies succeed */ + + SSL_set_options(tlsdata->ssl, SSL_OP_COOKIE_EXCHANGE); + + SSL_set_ex_data(tlsdata->ssl, openssl_addr_index, cachep); + + SSL_set_accept_state(tlsdata->ssl); + } + + /* RFC5953: section 5.3.1, step 1: + 6) The TLSTM-specific session identifier (tlstmSessionID) is set in + the tmSessionID of the tmStateReference passed to the TLS + Transport Model to indicate that the session has been established + successfully and to point to a specific (D)TLS connection for + future use. The tlstmSessionID is also stored in the LCD for + later lookup during processing of incoming messages + (Section 5.1.2). + */ + /* Implementation notes: + + our sessionID is stored as the transport's data pointer member + */ + DEBUGMSGT(("9:dtlsudp:bio_cache:created", "%p\n", cachep)); + + return cachep; +} + +static bio_cache * +find_or_create_bio_cache(netsnmp_transport *t, + netsnmp_sockaddr_storage *from_addr, + int we_are_client) { + bio_cache *cachep = find_bio_cache(from_addr); + if (NULL == cachep) { + /* none found; need to start a new context */ + cachep = start_new_cached_connection(t, from_addr, we_are_client); + if (NULL == cachep) { + snmp_log(LOG_ERR, "failed to open a new dtls connection\n"); + } + } else { + DEBUGMSGT(("9:dtlsudp:bio_cache:found", "%p\n", cachep)); + } + return cachep; +} + +static netsnmp_indexed_addr_pair * +_extract_addr_pair(netsnmp_transport *t, void *opaque, int olen) +{ + netsnmp_indexed_addr_pair *addr_pair = NULL; + + if (opaque && olen == sizeof(netsnmp_tmStateReference)) { + netsnmp_tmStateReference *tmStateRef = + (netsnmp_tmStateReference *) opaque; + + if (tmStateRef->have_addresses) + addr_pair = &(tmStateRef->addresses); + } + if ((NULL == addr_pair) && (NULL != t)) { + if (t->data != NULL && + t->data_length == sizeof(netsnmp_indexed_addr_pair)) + addr_pair = (netsnmp_indexed_addr_pair *) (t->data); + else if (t->data != NULL && + t->data_length == sizeof(_netsnmpTLSBaseData)) { + _netsnmpTLSBaseData *tlsdata = (_netsnmpTLSBaseData *) t->data; + addr_pair = (netsnmp_indexed_addr_pair *) (tlsdata->addr); + } + } + + return addr_pair; +} + +static struct sockaddr * +_find_remote_sockaddr(netsnmp_transport *t, void *opaque, int olen, int *socklen) +{ + netsnmp_indexed_addr_pair *addr_pair = _extract_addr_pair(t, opaque, olen); + struct sockaddr *sa = NULL; + + if (NULL == addr_pair) + return NULL; + + sa = &addr_pair->remote_addr.sa; + *socklen = netsnmp_sockaddr_size(sa); + return sa; +} + + +/* + * Reads data from our internal openssl outgoing BIO and sends any + * queued packets out the UDP port + */ +static int +_netsnmp_send_queued_dtls_pkts(netsnmp_transport *t, bio_cache *cachep) { + int outsize, rc2; + u_char outbuf[65535]; + + DEBUGTRACETOK("9:dtlsudp"); + + /* for memory bios, we now read from openssl's write + buffer (ie, the packet to go out) and send it out + the udp port manually */ + + outsize = BIO_read(cachep->write_bio, outbuf, sizeof(outbuf)); + if (outsize > 0) { + DEBUGMSGTL(("dtlsudp", "have %d bytes to send\n", outsize)); + + /* should always be true. */ + int socksize; + struct sockaddr *sa; + sa = _find_remote_sockaddr(t, NULL, 0, &socksize); + if (NULL == sa) + sa = &cachep->sas.sa; + socksize = netsnmp_sockaddr_size(sa); + rc2 = t->base_transport->f_send(t, outbuf, outsize, (void**)&sa, + &socksize); + if (rc2 == -1) { + snmp_log(LOG_ERR, "failed to send a DTLS specific packet\n"); + } + } else + DEBUGMSGTL(("9:dtlsudp", "have 0 bytes to send\n")); + + return outsize; +} + +/* + * If we have any outgoing SNMP data queued that OpenSSL/DTLS couldn't send + * (likely due to DTLS control packets needing to go out first) + * then this function attempts to send them. + */ +/* returns SNMPERR_SUCCESS if we succeeded in getting the data out */ +/* returns SNMPERR_GENERR if we still need more time */ +static int +_netsnmp_bio_try_and_write_buffered(netsnmp_transport *t, bio_cache *cachep) { + int rc; + _netsnmpTLSBaseData *tlsdata; + + DEBUGTRACETOK("9:dtlsudp"); + + tlsdata = cachep->tlsdata; + + /* make sure we have something to write */ + if (!cachep->write_cache || cachep->write_cache_len == 0) + return SNMPERR_SUCCESS; + + DEBUGMSGTL(("dtlsudp", "Trying to write %" NETSNMP_PRIz "d of buffered data\n", + cachep->write_cache_len)); + + /* try and write out the cached data */ + rc = SSL_write(tlsdata->ssl, cachep->write_cache, cachep->write_cache_len); + + while (rc == -1) { + int errnum = SSL_get_error(tlsdata->ssl, rc); + int bytesout; + + /* don't treat want_read/write errors as real errors */ + if (errnum != SSL_ERROR_WANT_READ && + errnum != SSL_ERROR_WANT_WRITE) { + DEBUGMSGTL(("dtlsudp", "ssl_write error (of buffered data)\n")); + _openssl_log_error(rc, tlsdata->ssl, "SSL_write"); + return SNMPERR_GENERR; + } + + /* check to see if we have outgoing DTLS packets to send */ + /* (SSL_write could have created DTLS control packets) */ + bytesout = _netsnmp_send_queued_dtls_pkts(t, cachep); + + /* If want_read/write but failed to actually send anything + then we need to wait for the other side, so quit */ + if ((errnum == SSL_ERROR_WANT_READ || + errnum == SSL_ERROR_WANT_WRITE) && + bytesout <= 0) { + /* we've failed; must need to wait longer */ + return SNMPERR_GENERR; + } + + /* retry writing */ + DEBUGMSGTL(("9:dtlsudp", "recalling ssl_write\n")); + rc = SSL_write(tlsdata->ssl, cachep->write_cache, + cachep->write_cache_len); + } + + if (rc > 0) + cachep->msgnum++; + + if (_netsnmp_send_queued_dtls_pkts(t, cachep) > 0) { + SNMP_FREE(cachep->write_cache); + cachep->write_cache_len = 0; + DEBUGMSGTL(("dtlsudp", " Write was successful\n")); + return SNMPERR_SUCCESS; + } + DEBUGMSGTL(("dtlsudp", " failed to send over UDP socket\n")); + return SNMPERR_GENERR; +} + +static int +_netsnmp_add_buffered_data(bio_cache *cachep, char *buf, size_t size) { + if (cachep->write_cache && cachep->write_cache_len > 0) { + size_t newsize = cachep->write_cache_len + size; + + char *newbuf = realloc(cachep->write_cache, newsize); + if (NULL == newbuf) { + /* ack! malloc failure */ + /* XXX: free and close */ + return SNMPERR_GENERR; + } + cachep->write_cache = newbuf; + + /* write the new packet to the end */ + memcpy(cachep->write_cache + cachep->write_cache_len, + buf, size); + cachep->write_cache_len = newsize; + } else { + if (SNMPERR_SUCCESS != + memdup((u_char **) &cachep->write_cache, buf, size)) { + /* ack! malloc failure */ + /* XXX: free and close */ + return SNMPERR_GENERR; + } + cachep->write_cache_len = size; + } + return SNMPERR_SUCCESS; +} + +static int +netsnmp_dtlsudp_recv(netsnmp_transport *t, void *buf, int size, + void **opaque, int *olength) +{ + int rc = -1; + netsnmp_indexed_addr_pair *addr_pair = NULL; + struct sockaddr *from; + netsnmp_tmStateReference *tmStateRef = NULL; + _netsnmpTLSBaseData *tlsdata; + bio_cache *cachep; + + DEBUGTRACETOK("9:dtlsudp"); + + if (NULL == t || t->sock == 0) + return -1; + + /* create a tmStateRef cache for slow fill-in */ + tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference); + + if (tmStateRef == NULL) { + *opaque = NULL; + *olength = 0; + return -1; + } + + /* Set the transportDomain */ + memcpy(tmStateRef->transportDomain, + netsnmpDTLSUDPDomain, sizeof(netsnmpDTLSUDPDomain[0]) * + netsnmpDTLSUDPDomain_len); + tmStateRef->transportDomainLen = netsnmpDTLSUDPDomain_len; + + addr_pair = &tmStateRef->addresses; + tmStateRef->have_addresses = 1; + from = (struct sockaddr *) &(addr_pair->remote_addr); + + while (rc < 0) { + char *opaque = NULL; + int olen; + rc = t->base_transport->f_recv(t, buf, size, (void**)&opaque, &olen); + if (rc > 0) + memcpy(from, opaque, olen); + SNMP_FREE(opaque); + if (rc < 0 && errno != EINTR) { + break; + } + } + + DEBUGMSGTL(("dtlsudp", "received %d raw bytes on way to dtls\n", rc)); + if (rc < 0) { + DEBUGMSGTL(("dtlsudp", "recvfrom fd %d err %d (\"%s\")\n", + t->sock, errno, strerror(errno))); + SNMP_FREE(tmStateRef); + return -1; + } + + /* now that we have the from address filled in, we can look up + the openssl context and have openssl read and process + appropriately */ + + /* RFC5953: section 5.1, step 1: + 1) Determine the tlstmSessionID for the incoming message. The + tlstmSessionID MUST be a unique session identifier for this + (D)TLS connection. The contents and format of this identifier + are implementation-dependent as long as it is unique to the + session. A session identifier MUST NOT be reused until all + references to it are no longer in use. The tmSessionID is equal + to the tlstmSessionID discussed in Section 5.1.1. tmSessionID + refers to the session identifier when stored in the + tmStateReference and tlstmSessionID refers to the session + identifier when stored in the LCD. They MUST always be equal + when processing a given session's traffic. + + If this is the first message received through this session and + the session does not have an assigned tlstmSessionID yet then the + snmpTlstmSessionAccepts counter is incremented and a + tlstmSessionID for the session is created. This will only happen + on the server side of a connection because a client would have + already assigned a tlstmSessionID during the openSession() + invocation. Implementations may have performed the procedures + described in Section 5.3.2 prior to this point or they may + perform them now, but the procedures described in Section 5.3.2 + MUST be performed before continuing beyond this point. + */ + + /* RFC5953: section 5.1, step 2: + 2) Create a tmStateReference cache for the subsequent reference and + assign the following values within it: + + tmTransportDomain = snmpTLSTCPDomain or snmpDTLSUDPDomain as + appropriate. + + tmTransportAddress = The address the message originated from. + + tmSecurityLevel = The derived tmSecurityLevel for the session, + as discussed in Section 3.1.2 and Section 5.3. + + tmSecurityName = The derived tmSecurityName for the session as + discussed in Section 5.3. This value MUST be constant during + the lifetime of the session. + + tmSessionID = The tlstmSessionID described in step 1 above. + */ + + /* if we don't have a cachep for this connection then + we're receiving something new and are the server + side */ + cachep = + find_or_create_bio_cache(t, &addr_pair->remote_addr, WE_ARE_SERVER); + if (NULL == cachep) { + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONACCEPTS); + SNMP_FREE(tmStateRef); + return -1; + } + tlsdata = cachep->tlsdata; + if (NULL == tlsdata->ssl) { + /* + * this happens when the server starts but doesn't have an + * identity and a client connects... + */ + snmp_log(LOG_ERR, + "DTLSUDP: missing tlsdata!\n"); + /*snmp_increment_statistic( XXX-rks ??? );*/ + SNMP_FREE(tmStateRef); + return -1; + } + + /* Implementation notes: + - we use the t->data memory pointer as the session ID + - the transport domain is already the correct type if we got here + - if we don't have a session yet (eg, no tmSessionID from the + specs) then we create one automatically here. + */ + + /* write the received buffer to the memory-based input bio */ + BIO_write(cachep->read_bio, buf, rc); + + /* RFC5953: section 5.1, step 3: + 3) The incomingMessage and incomingMessageLength are assigned values + from the (D)TLS processing. + */ + /* Implementation notes: + + rc = incomingMessageLength + + buf = IncomingMessage + */ + + /* XXX: in Wes' other example we do a SSL_pending() call + too to ensure we're ready to read... it's possible + that buffered stuff in openssl won't be caught by the + net-snmp select loop because it's already been pulled + out; need to deal with this) */ + rc = SSL_read(tlsdata->ssl, buf, size); + + /* + * moved netsnmp_openssl_null_checks to netsnmp_tlsbase_wrapup_recv. + * currently netsnmp_tlsbase_wrapup_recv is where we check for + * algorithm compliance, but we (sometimes) know the algorithms + * at this point, so we could bail earlier (here)... + */ + + while (rc == -1) { + int errnum = SSL_get_error(tlsdata->ssl, rc); + int bytesout; + + /* don't treat want_read/write errors as real errors */ + if (errnum != SSL_ERROR_WANT_READ && + errnum != SSL_ERROR_WANT_WRITE) { + _openssl_log_error(rc, tlsdata->ssl, "SSL_read"); + break; + } + + /* check to see if we have outgoing DTLS packets to send */ + /* (SSL_read could have created DTLS control packets) */ + bytesout = _netsnmp_send_queued_dtls_pkts(t, cachep); + + /* If want_read/write but failed to actually send + anything then we need to wait for the other side, + so quit */ + if ((errnum == SSL_ERROR_WANT_READ || + errnum == SSL_ERROR_WANT_WRITE) && + bytesout <= 0) + break; + + /* retry reading */ + DEBUGMSGTL(("9:dtlsudp", "recalling ssl_read\n")); + rc = SSL_read(tlsdata->ssl, buf, size); + } + + if (rc == -1) { + SNMP_FREE(tmStateRef); + + DEBUGMSGTL(("9:dtlsudp", "no decoded data from dtls\n")); + + if (SSL_get_error(tlsdata->ssl, rc) == SSL_ERROR_WANT_READ) { + DEBUGMSGTL(("9dtlsudp","here: want read!\n")); + + /* see if we have buffered write date to send out first */ + if (cachep->write_cache) { + _netsnmp_bio_try_and_write_buffered(t, cachep); + /* XXX: check error or not here? */ + /* (what would we do differently?) */ + } + + rc = -1; /* XXX: it's ok, but what's the right return? */ + } + else + _openssl_log_error(rc, tlsdata->ssl, "SSL_read"); + +#if 0 /* to dump cache if we don't have a cookie, this is where to do it */ + if (!(cachep->flags & NETSNMP_BIO_HAVE_COOKIE)) + remove_and_free_bio_cache(cachep); +#endif + return rc; + } + + DEBUGMSGTL(("dtlsudp", "received %d decoded bytes from dtls\n", rc)); + + if ((0 == rc) && (SSL_get_shutdown(tlsdata->ssl) & SSL_RECEIVED_SHUTDOWN)) { + DEBUGMSGTL(("dtlsudp", "peer disconnected\n")); + cachep->flags |= NETSNMP_BIO_DISCONNECTED; + remove_and_free_bio_cache(cachep); + SNMP_FREE(tmStateRef); + return rc; + } + cachep->flags |= NETSNMP_BIO_CONNECTED; + + /* Until we've locally assured ourselves that all is well in + certificate-verification-land we need to be prepared to stop + here and ensure all our required checks have been done. */ + if (0 == (tlsdata->flags & NETSNMP_TLSBASE_CERT_FP_VERIFIED)) { + int verifyresult; + + if (tlsdata->flags & NETSNMP_TLSBASE_IS_CLIENT) { + + /* verify that the server's certificate is the correct one */ + + /* RFC5953: section 5.3.1, step 1: + 3) Using the destTransportDomain and + destTransportAddress values, the client will + initiate the (D)TLS handshake protocol to establish + session keys for message integrity and encryption. + + If the attempt to establish a session is + unsuccessful, then snmpTlstmSessionOpenErrors is + incremented, an error indication is returned, and + processing stops. If the session failed to open + because the presented server certificate was + unknown or invalid then the + snmpTlstmSessionUnknownServerCertificate or + snmpTlstmSessionInvalidServerCertificates MUST be + incremented and a snmpTlstmServerCertificateUnknown + or snmpTlstmServerInvalidCertificate notification + SHOULD be sent as appropriate. Reasons for server + certificate invalidation includes, but is not + limited to, cryptographic validation failures and + an unexpected presented certificate identity. + */ + /* RFC5953: section 5.3.1, step 1: + 4) The (D)TLS client MUST then verify that the (D)TLS + server's presented certificate is the expected + certificate. The (D)TLS client MUST NOT transmit + SNMP messages until the server certificate has been + authenticated, the client certificate has been + transmitted and the TLS connection has been fully + established. + + If the connection is being established from + configuration based on SNMP-TARGET-MIB + configuration, then the snmpTlstmAddrTable + DESCRIPTION clause describes how the verification + is done (using either a certificate fingerprint, or + an identity authenticated via certification path + validation). + + If the connection is being established for reasons + other than configuration found in the + SNMP-TARGET-MIB then configuration and procedures + outside the scope of this document should be + followed. Configuration mechanisms SHOULD be + similar in nature to those defined in the + snmpTlstmAddrTable to ensure consistency across + management configuration systems. For example, a + command-line tool for generating SNMP GETs might + support specifying either the server's certificate + fingerprint or the expected host name as a command + line argument. + */ + /* RFC5953: section 5.3.1, step 1: + 5) (D)TLS provides assurance that the authenticated + identity has been signed by a trusted configured + certification authority. If verification of the + server's certificate fails in any way (for example + because of failures in cryptographic verification + or the presented identity did not match the + expected named entity) then the session + establishment MUST fail, the + snmpTlstmSessionInvalidServerCertificates object is + incremented. If the session can not be opened for + any reason at all, including cryptographic + verification failures and snmpTlstmCertToTSNTable + lookup failures, then the + snmpTlstmSessionOpenErrors counter is incremented + and processing stops. + */ + + /* Implementation notes: + + in the following function the server's certificate and + presented commonname or subjectAltName is checked + according to the rules in the snmpTlstmAddrTable. + */ + if ((verifyresult = netsnmp_tlsbase_verify_server_cert(tlsdata->ssl, tlsdata)) + != SNMPERR_SUCCESS) { + if (verifyresult == SNMPERR_TLS_NO_CERTIFICATE) { + /* assume we simply haven't received it yet and there + is more data to wait-for or send */ + /* XXX: probably need to check for whether we should + send stuff from our end to continue the transaction + */ + SNMP_FREE(tmStateRef); + return -1; + } else { + /* XXX: free needed memory */ + snmp_log(LOG_ERR, + "DTLSUDP: failed to verify ssl certificate (of the server)\n"); + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONUNKNOWNSERVERCERTIFICATE); + /* Step 5 says these are always incremented */ + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDSERVERCERTIFICATES); + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); + SNMP_FREE(tmStateRef); + return -1; + } + } + tlsdata->flags |= NETSNMP_TLSBASE_CERT_FP_VERIFIED; + DEBUGMSGTL(("dtlsudp", "Verified the server's certificate\n")); + } else { +#ifndef NETSNMP_NO_LISTEN_SUPPORT + /* verify that the client's certificate is the correct one */ + + if ((verifyresult = netsnmp_tlsbase_verify_client_cert(tlsdata->ssl, tlsdata)) + != SNMPERR_SUCCESS) { + if (verifyresult == SNMPERR_TLS_NO_CERTIFICATE) { + /* assume we simply haven't received it yet and there + is more data to wait-for or send */ + /* XXX: probably need to check for whether we should + send stuff from our end to continue the transaction + */ + SNMP_FREE(tmStateRef); + return -1; + } else { + /* XXX: free needed memory */ + snmp_log(LOG_ERR, + "DTLSUDP: failed to verify ssl certificate (of the client)\n"); + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCLIENTCERTIFICATES); + SNMP_FREE(tmStateRef); + return -1; + } + } + tlsdata->flags |= NETSNMP_TLSBASE_CERT_FP_VERIFIED; + DEBUGMSGTL(("dtlsudp", "Verified the client's certificate\n")); +#else /* NETSNMP_NO_LISTEN_SUPPORT */ + return NULL; +#endif /* NETSNMP_NO_LISTEN_SUPPORT */ + } + } + + if (rc > 0) + cachep->msgnum++; + + if (BIO_ctrl_pending(cachep->write_bio) > 0) { + _netsnmp_send_queued_dtls_pkts(t, cachep); + } + + DEBUGIF ("9:dtlsudp") { + char *str = + t->base_transport->f_fmtaddr(t, addr_pair, + sizeof(netsnmp_indexed_addr_pair)); + DEBUGMSGTL(("9:dtlsudp", + "recvfrom fd %d got %d bytes (from %s)\n", + t->sock, rc, str)); + free(str); + } + + /* see if we have buffered write date to send out first */ + if (cachep->write_cache) { + if (SNMPERR_GENERR == + _netsnmp_bio_try_and_write_buffered(t, cachep)) { + /* we still have data that can't get out in the buffer */ + /* XXX: nothing to do here? */ + } + } + + if (netsnmp_tlsbase_wrapup_recv(tmStateRef, tlsdata, opaque, olength) != + SNMPERR_SUCCESS) + return SNMPERR_GENERR; + + /* RFC5953: section 5.1, step 4: + 4) The TLS Transport Model passes the transportDomain, + transportAddress, incomingMessage, and incomingMessageLength to + the Dispatcher using the receiveMessage ASI: + + statusInformation = + receiveMessage( + IN transportDomain -- snmpTLSTCPDomain or snmpDTLSUDPDomain, + IN transportAddress -- address for the received message + IN incomingMessage -- the whole SNMP message from (D)TLS + IN incomingMessageLength -- the length of the SNMP message + IN tmStateReference -- transport info + ) + */ + /* Implementation notes: those pamateres are all passed outward + using the functions arguments and the return code below (the length) */ + + return rc; +} + + + +static int +netsnmp_dtlsudp_send(netsnmp_transport *t, void *buf, int size, + void **opaque, int *olength) +{ + int rc = -1; + netsnmp_indexed_addr_pair *addr_pair = NULL; + bio_cache *cachep = NULL; + netsnmp_tmStateReference *tmStateRef = NULL; + u_char outbuf[65535]; + _netsnmpTLSBaseData *tlsdata = NULL; + int socksize; + struct sockaddr *sa; + + DEBUGTRACETOK("9:dtlsudp"); + DEBUGMSGTL(("dtlsudp", "sending %d bytes\n", size)); + + if (NULL == t || t->sock <= 0) { + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCACHES); + snmp_log(LOG_ERR, "invalid netsnmp_dtlsudp_send usage\n"); + return -1; + } + + /* determine remote addresses */ + addr_pair = _extract_addr_pair(t, opaque ? *opaque : NULL, + olength ? *olength : 0); + if (NULL == addr_pair) { + /* RFC5953: section 5.2, step 1: + 1) If tmStateReference does not refer to a cache containing values + for tmTransportDomain, tmTransportAddress, tmSecurityName, + tmRequestedSecurityLevel, and tmSameSecurity, then increment the + snmpTlstmSessionInvalidCaches counter, discard the message, and + return the error indication in the statusInformation. Processing + of this message stops. + */ + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONINVALIDCACHES); + snmp_log(LOG_ERR, "dtlsudp_send: can't get address to send to\n"); + return -1; + } + + /* RFC5953: section 5.2, step 2: + 2) Extract the tmSessionID, tmTransportDomain, tmTransportAddress, + tmSecurityName, tmRequestedSecurityLevel, and tmSameSecurity + values from the tmStateReference. Note: The tmSessionID value + may be undefined if no session exists yet over which the message + can be sent. + */ + /* Implementation notes: + - we use the t->data memory pointer as the session ID + - the transport domain is already the correct type if we got here + - if we don't have a session yet (eg, no tmSessionID from the + specs) then we create one automatically here. + */ + if (opaque != NULL && *opaque != NULL && + olength != NULL && *olength == sizeof(netsnmp_tmStateReference)) + tmStateRef = (netsnmp_tmStateReference *) *opaque; + + + /* RFC5953: section 5.2, step 3: + 3) If tmSameSecurity is true and either tmSessionID is undefined or + refers to a session that is no longer open then increment the + snmpTlstmSessionNoSessions counter, discard the message and + return the error indication in the statusInformation. Processing + of this message stops. + */ + /* RFC5953: section 5.2, step 4: + 4) If tmSameSecurity is false and tmSessionID refers to a session + that is no longer available then an implementation SHOULD open a + new session using the openSession() ASI (described in greater + detail in step 5b). Instead of opening a new session an + implementation MAY return a snmpTlstmSessionNoSessions error to + the calling module and stop processing of the message. + */ + /* Implementation Notes: + - We would never get here if the sessionID was different. We + tie packets directly to the transport object and it could + never be sent back over a different transport, which is what + the above text is trying to prevent. + - Auto-connections are handled higher in the Net-SNMP library stack + */ + + /* RFC5953: section 5.2, step 5: + 5) If tmSessionID is undefined, then use tmTransportDomain, + tmTransportAddress, tmSecurityName and tmRequestedSecurityLevel + to see if there is a corresponding entry in the LCD suitable to + send the message over. + + 5a) If there is a corresponding LCD entry, then this session + will be used to send the message. + + 5b) If there is no corresponding LCD entry, then open a session + using the openSession() ASI (discussed further in + Section 5.3.1). Implementations MAY wish to offer message + buffering to prevent redundant openSession() calls for the + same cache entry. If an error is returned from + openSession(), then discard the message, discard the + tmStateReference, increment the snmpTlstmSessionOpenErrors, + return an error indication to the calling module and stop + processing of the message. + */ + + /* we're always a client if we're sending to something unknown yet */ + if (NULL == + (cachep = find_or_create_bio_cache(t, &addr_pair->remote_addr, + WE_ARE_CLIENT))) { + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONOPENERRORS); + return -1; + } + + tlsdata = cachep->tlsdata; + if (NULL == tlsdata || NULL == tlsdata->ssl) { + /** xxx mem lean? free created bio cache? */ + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONNOSESSIONS); + snmp_log(LOG_ERR, "bad tls data or ssl ptr in netsnmp_dtlsudp_send\n"); + return -1; + } + + if (!tlsdata->securityName && tmStateRef && + tmStateRef->securityNameLen > 0) { + tlsdata->securityName = strdup(tmStateRef->securityName); + } + + /* see if we have previous outgoing data to send */ + if (cachep->write_cache) { + if (SNMPERR_GENERR == _netsnmp_bio_try_and_write_buffered(t, cachep)) { + /* we still have data that can't get out in the buffer */ + + /* add the new data to the end of the existing cache */ + if (_netsnmp_add_buffered_data(cachep, buf, size) != + SNMPERR_SUCCESS) { + /* XXX: free and close */ + } + return -1; + } + } + + DEBUGIF ("9:dtlsudp") { + char *str = t->base_transport->f_fmtaddr(t, (void *) addr_pair, + sizeof(netsnmp_indexed_addr_pair)); + DEBUGMSGTL(("9:dtlsudp", "send %d bytes from %p to %s on fd %d\n", + size, buf, str, t->sock)); + free(str); + } + + /* RFC5953: section 5.2, step 6: + 6) Using either the session indicated by the tmSessionID if there + was one or the session resulting from a previous step (4 or 5), + pass the outgoingMessage to (D)TLS for encapsulation and + transmission. + */ + rc = SSL_write(tlsdata->ssl, buf, size); + + while (rc == -1) { + int bytesout; + int errnum = SSL_get_error(tlsdata->ssl, rc); + + /* don't treat want_read/write errors as real errors */ + if (errnum != SSL_ERROR_WANT_READ && + errnum != SSL_ERROR_WANT_WRITE) { + DEBUGMSGTL(("dtlsudp", "ssl_write error\n")); + _openssl_log_error(rc, tlsdata->ssl, "SSL_write"); + break; + } + + /* check to see if we have outgoing DTLS packets to send */ + /* (SSL_read could have created DTLS control packets) */ + bytesout = _netsnmp_send_queued_dtls_pkts(t, cachep); + + /* If want_read/write but failed to actually send + anything then we need to wait for the other side, + so quit */ + if ((errnum == SSL_ERROR_WANT_READ || + errnum == SSL_ERROR_WANT_WRITE) && + bytesout <= 0) { + /* We need more data written to or read from the socket + but we're failing to do so and need to wait till the + socket is ready again; unfortunately this means we need + to buffer the SNMP data temporarily in the mean time */ + + /* remember the packet */ + if (_netsnmp_add_buffered_data(cachep, buf, size) != + SNMPERR_SUCCESS) { + + /* XXX: free and close */ + return -1; + } + + /* exit out of the loop until we get caled again from + socket data */ + break; + } + DEBUGMSGTL(("9:dtlsudp", "recalling ssl_write\n")); + rc = SSL_write(tlsdata->ssl, buf, size); + } + + if (rc > 0) + cachep->msgnum++; + + /* for memory bios, we now read from openssl's write buffer (ie, + the packet to go out) and send it out the udp port manually */ + rc = BIO_read(cachep->write_bio, outbuf, sizeof(outbuf)); + if (rc <= 0) { + /* in theory an ok thing */ + return 0; + } + socksize = netsnmp_sockaddr_size(&cachep->sas.sa); + sa = &cachep->sas.sa; + rc = t->base_transport->f_send(t, outbuf, rc, (void**)&sa, &socksize); + + return rc; +} + + + +static int +netsnmp_dtlsudp_close(netsnmp_transport *t) +{ + /* XXX: issue a proper dtls closure notification(s) */ + + bio_cache *cachep = NULL; + _netsnmpTLSBaseData *tlsbase = NULL; + + DEBUGTRACETOK("9:dtlsudp"); + + DEBUGMSGTL(("dtlsudp:close", "closing dtlsudp transport %p\n", t)); + + /* RFC5953: section 5.4, step 1: + 1) Increment either the snmpTlstmSessionClientCloses or the + snmpTlstmSessionServerCloses counter as appropriate. + */ + snmp_increment_statistic(STAT_TLSTM_SNMPTLSTMSESSIONCLIENTCLOSES); + + /* RFC5953: section 5.4, step 2: + 2) Look up the session using the tmSessionID. + */ + /* Implementation notes: + + Our session id is stored as the t->data pointer + */ + if (NULL != t->data && t->data_length == sizeof(_netsnmpTLSBaseData)) { + tlsbase = (_netsnmpTLSBaseData *) t->data; + + if (tlsbase->addr) + cachep = find_bio_cache(&tlsbase->addr->remote_addr); + } + + /* RFC5953: section 5.4, step 3: + 3) If there is no open session associated with the tmSessionID, then + closeSession processing is completed. + */ + if (NULL == cachep) + return netsnmp_socketbase_close(t); + + /* if we have any remaining packtes to send, try to send them */ + if (cachep->write_cache_len > 0) { + int i = 0; + char buf[8192]; + int rc; + void *opaque = NULL; + int opaque_len = 0; + fd_set readfs; + struct timeval tv; + + DEBUGMSGTL(("dtlsudp:close", + "%" NETSNMP_PRIz "d bytes remain in write_cache\n", + cachep->write_cache_len)); + + /* + * if negotiations have completed and we've received data, try and + * send any queued packets. + */ + if (cachep->flags & NETSNMP_BIO_CONNECTED) { + /* make configurable: + - do this at all? + - retries + - timeout + */ + for (i = 0; i < 6 && cachep->write_cache_len != 0; ++i) { + + /* first see if we can send out what we have */ + _netsnmp_bio_try_and_write_buffered(t, cachep); + if (cachep->write_cache_len == 0) + break; + + /* if we've failed that, we probably need to wait for packets */ + FD_ZERO(&readfs); + FD_SET(t->sock, &readfs); + tv.tv_sec = 0; + tv.tv_usec = 50000; + rc = select(t->sock+1, &readfs, NULL, NULL, &tv); + if (rc > 0) { + /* junk recv for catching negotations still in play */ + opaque_len = 0; + netsnmp_dtlsudp_recv(t, buf, sizeof(buf), + &opaque, &opaque_len); + SNMP_FREE(opaque); + } + } /* for loop */ + } + + /** dump anything that wasn't sent */ + if (cachep->write_cache_len > 0) { + DEBUGMSGTL(("dtlsudp:close", + "dumping %" NETSNMP_PRIz "d bytes from write_cache\n", + cachep->write_cache_len)); + SNMP_FREE(cachep->write_cache); + cachep->write_cache_len = 0; + } + } + + /* RFC5953: section 5.4, step 4: + 4) Have (D)TLS close the specified connection. This MUST include + sending a close_notify TLS Alert to inform the other side that + session cleanup may be performed. + */ + if (NULL != cachep->tlsdata && NULL != cachep->tlsdata->ssl) { + + DEBUGMSGTL(("dtlsudp:close", "closing SSL socket\n")); + SSL_shutdown(cachep->tlsdata->ssl); + + /* send the close_notify we maybe generated in step 4 */ + if (BIO_ctrl_pending(cachep->write_bio) > 0) + _netsnmp_send_queued_dtls_pkts(t, cachep); + } + + remove_and_free_bio_cache(cachep); + + return netsnmp_socketbase_close(t); +} + +char * +netsnmp_dtlsudp_fmtaddr(netsnmp_transport *t, void *data, int len) +{ + int sa_len; + struct sockaddr *sa = _find_remote_sockaddr(t, data, len, &sa_len); + if (sa) { + data = sa; + len = sa_len; + } + + return netsnmp_ipv4_fmtaddr("DTLSUDP", t, data, len); +} + +/* + * Open a DTLS-based transport for SNMP. Local is TRUE if addr is the local + * address to bind to (i.e. this is a server-type session); otherwise addr is + * the remote address to send things to. + */ + +static netsnmp_transport * +_transport_common(netsnmp_transport *t, int local) +{ + char *tmp = NULL; + int tmp_len; + + DEBUGTRACETOK("9:dtlsudp"); + + if (NULL == t) + return NULL; + + /** save base transport for clients; need in send/recv functions later */ + if (t->data) { /* don't copy data */ + tmp = t->data; + tmp_len = t->data_length; + t->data = NULL; + } + t->base_transport = netsnmp_transport_copy(t); + + if (tmp) { + t->data = tmp; + t->data_length = tmp_len; + } + if (NULL != t->data && + t->data_length == sizeof(netsnmp_indexed_addr_pair)) { + _netsnmpTLSBaseData *tlsdata = + netsnmp_tlsbase_allocate_tlsdata(t, local); + tlsdata->addr = t->data; + t->data = tlsdata; + t->data_length = sizeof(_netsnmpTLSBaseData); + } + + /* + * Set Domain + */ + t->domain = netsnmpDTLSUDPDomain; + t->domain_length = netsnmpDTLSUDPDomain_len; + + t->f_recv = netsnmp_dtlsudp_recv; + t->f_send = netsnmp_dtlsudp_send; + t->f_close = netsnmp_dtlsudp_close; + t->f_config = netsnmp_tlsbase_config; + t->f_setup_session = netsnmp_tlsbase_session_init; + t->f_accept = NULL; + t->f_fmtaddr = netsnmp_dtlsudp_fmtaddr; + + t->flags = NETSNMP_TRANSPORT_FLAG_TUNNELED; + + return t; +} + +netsnmp_transport * +netsnmp_dtlsudp_transport(struct sockaddr_in *addr, int local) +{ + netsnmp_transport *t = NULL; + + DEBUGTRACETOK("dtlsudp"); + + t = netsnmp_udp_transport(addr, local); + if (NULL == t) + return NULL; + + _transport_common(t, local); + + if (!local) { + /* dtls needs to bind the socket for SSL_write to work */ + if (connect(t->sock, (struct sockaddr *) addr, sizeof(*addr)) == -1) + snmp_log(LOG_ERR, "dtls: failed to connect\n"); + } + + return t; +} + + +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + +char * +netsnmp_dtlsudp6_fmtaddr(netsnmp_transport *t, void *data, int len) +{ + int sa_len; + struct sockaddr *sa = _find_remote_sockaddr(t, data, len, &sa_len); + if (sa) { + data = sa; + len = sa_len; + } + + return netsnmp_ipv6_fmtaddr("DTLSUDP6", t, data, len); +} + +/* + * Open a DTLS-based transport for SNMP. Local is TRUE if addr is the local + * address to bind to (i.e. this is a server-type session); otherwise addr is + * the remote address to send things to. + */ + +netsnmp_transport * +netsnmp_dtlsudp6_transport(struct sockaddr_in6 *addr, int local) +{ + netsnmp_transport *t = NULL; + + DEBUGTRACETOK("dtlsudp"); + + t = netsnmp_udp6_transport(addr, local); + if (NULL == t) + return NULL; + + _transport_common(t, local); + + if (!local) { + /* dtls needs to bind the socket for SSL_write to work */ + if (connect(t->sock, (struct sockaddr *) addr, sizeof(*addr)) == -1) + snmp_log(LOG_ERR, "dtls: failed to connect\n"); + } + + /* XXX: Potentially set sock opts here (SO_SNDBUF/SO_RCV_BUF) */ + /* XXX: and buf size */ + + t->f_fmtaddr = netsnmp_dtlsudp6_fmtaddr; + + return t; +} +#endif + + +netsnmp_transport * +netsnmp_dtlsudp_create_tstring(const char *str, int isserver, + const char *default_target) +{ +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + struct sockaddr_in6 addr6; +#endif + struct sockaddr_in addr; + netsnmp_transport *t; + _netsnmpTLSBaseData *tlsdata; + char buf[SPRINT_MAX_LEN], *cp; + + if (netsnmp_sockaddr_in2(&addr, str, default_target)) + t = netsnmp_dtlsudp_transport(&addr, isserver); +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + else if (netsnmp_sockaddr_in6_2(&addr6, str, default_target)) + t = netsnmp_dtlsudp6_transport(&addr6, isserver); +#endif + else + return NULL; + + + /* see if we can extract the remote hostname */ + if (!isserver && t && t->data && str) { + tlsdata = (_netsnmpTLSBaseData *) t->data; + /* search for a : */ + if (NULL != (cp = strrchr(str, ':'))) { + sprintf(buf, "%.*s", (int) SNMP_MIN(cp - str, sizeof(buf) - 1), + str); + } else { + /* else the entire spec is a host name only */ + strlcpy(buf, str, sizeof(buf)); + } + tlsdata->their_hostname = strdup(buf); + } + return t; +} + + +netsnmp_transport * +netsnmp_dtlsudp_create_ostring(const u_char * o, size_t o_len, int local) +{ + struct sockaddr_in addr; + + if (o_len == 6) { + unsigned short porttmp = (o[4] << 8) + o[5]; + addr.sin_family = AF_INET; + memcpy((u_char *) & (addr.sin_addr.s_addr), o, 4); + addr.sin_port = htons(porttmp); + return netsnmp_dtlsudp_transport(&addr, local); + } +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + else if (o_len == 18) { + struct sockaddr_in6 addr6; + unsigned short porttmp = (o[16] << 8) + o[17]; + addr6.sin6_family = AF_INET6; + memcpy((u_char *) & (addr6.sin6_addr.s6_addr), o, 4); + addr6.sin6_port = htons(porttmp); + return netsnmp_dtlsudp6_transport(&addr6, local); + } +#endif + return NULL; +} + +void +netsnmp_dtlsudp_ctor(void) +{ + char indexname[] = "_netsnmp_addr_info"; + static const char *prefixes[] = { "dtlsudp", "dtls" +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + , "dtlsudp6", "dtls6" +#endif + }; + int i, num_prefixes = sizeof(prefixes) / sizeof(char *); +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + char indexname6[] = "_netsnmp_addr_info6"; +#endif + + DEBUGMSGTL(("dtlsudp", "registering DTLS constructor\n")); + + /* config settings */ + +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + if (!openssl_addr_index6) + openssl_addr_index6 = + SSL_get_ex_new_index(0, indexname6, NULL, NULL, NULL); +#endif + + dtlsudpDomain.name = netsnmpDTLSUDPDomain; + dtlsudpDomain.name_length = netsnmpDTLSUDPDomain_len; + dtlsudpDomain.prefix = (const char**)calloc(num_prefixes + 1, + sizeof(char *)); + for (i = 0; i < num_prefixes; ++ i) + dtlsudpDomain.prefix[i] = prefixes[i]; + + dtlsudpDomain.f_create_from_tstring = NULL; + dtlsudpDomain.f_create_from_tstring_new = netsnmp_dtlsudp_create_tstring; + dtlsudpDomain.f_create_from_ostring = netsnmp_dtlsudp_create_ostring; + + if (!openssl_addr_index) + openssl_addr_index = + SSL_get_ex_new_index(0, indexname, NULL, NULL, NULL); + + netsnmp_tdomain_register(&dtlsudpDomain); +} + +/* + * Much of the code below was taken from the OpenSSL example code + * and is subject to the OpenSSL copyright. + */ +#define NETSNMP_COOKIE_SECRET_LENGTH 16 +int cookie_initialized=0; +unsigned char cookie_secret[NETSNMP_COOKIE_SECRET_LENGTH]; + +typedef union { + struct sockaddr sa; + struct sockaddr_in s4; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + struct sockaddr_in6 s6; +#endif +} _peer_union; + +int netsnmp_dtls_gen_cookie(SSL *ssl, unsigned char *cookie, + unsigned int *cookie_len) +{ + unsigned char *buffer, result[EVP_MAX_MD_SIZE]; + unsigned int length, resultlength; + bio_cache *cachep = NULL; + _peer_union *peer; + + /* Initialize a random secret */ + if (!cookie_initialized) { + if (!RAND_bytes(cookie_secret, NETSNMP_COOKIE_SECRET_LENGTH)) { + snmp_log(LOG_ERR, "dtls: error setting random cookie secret\n"); + return 0; + } + cookie_initialized = 1; + } + + DEBUGMSGT(("dtlsudp:cookie", "generating cookie...\n")); + + /* Read peer information */ + cachep = SSL_get_ex_data(ssl, openssl_addr_index); + if (!cachep) { + snmp_log(LOG_ERR, "dtls: failed to get the peer address\n"); + return 0; + } + peer = (_peer_union *)&cachep->sas; + + /* Create buffer with peer's address and port */ + length = 0; + switch (peer->sa.sa_family) { + case AF_INET: + length += sizeof(struct in_addr); + length += sizeof(peer->s4.sin_port); + break; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + case AF_INET6: + length += sizeof(struct in6_addr); + length += sizeof(peer->s6.sin6_port); + break; +#endif + default: + snmp_log(LOG_ERR, "dtls generating cookie: unknown family: %d\n", + peer->sa.sa_family); + return 0; + } + buffer = malloc(length); + if (buffer == NULL) { + snmp_log(LOG_ERR,"dtls: out of memory\n"); + return 0; + } + + switch (peer->sa.sa_family) { + case AF_INET: + memcpy(buffer, + &peer->s4.sin_port, + sizeof(peer->s4.sin_port)); + memcpy(buffer + sizeof(peer->s4.sin_port), + &peer->s4.sin_addr, + sizeof(struct in_addr)); + break; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + case AF_INET6: + memcpy(buffer, + &peer->s6.sin6_port, + sizeof(peer->s6.sin6_port)); + memcpy(buffer + sizeof(peer->s6.sin6_port), + &peer->s6.sin6_addr, + sizeof(struct in6_addr)); + break; +#endif + default: + snmp_log(LOG_ERR, "dtls: unknown address family generating a cookie\n"); + return 0; + } + + /* Calculate HMAC of buffer using the secret */ + HMAC(EVP_sha1(), cookie_secret, NETSNMP_COOKIE_SECRET_LENGTH, + buffer, length, result, &resultlength); + OPENSSL_free(buffer); + + memcpy(cookie, result, resultlength); + *cookie_len = resultlength; + + DEBUGMSGT(("9:dtlsudp:cookie", "generated %d byte cookie\n", *cookie_len)); + + return 1; +} + +int netsnmp_dtls_verify_cookie(SSL *ssl, unsigned char *cookie, + unsigned int cookie_len) +{ + unsigned char *buffer, result[EVP_MAX_MD_SIZE]; + unsigned int length, resultlength, rc; + bio_cache *cachep = NULL; + _peer_union *peer; + + /* If secret isn't initialized yet, the cookie can't be valid */ + if (!cookie_initialized) + return 0; + + DEBUGMSGT(("9:dtlsudp:cookie", "verifying %d byte cookie\n", cookie_len)); + + cachep = SSL_get_ex_data(ssl, openssl_addr_index); + if (!cachep) { + snmp_log(LOG_ERR, "dtls: failed to get the peer address\n"); + return 0; + } + peer = (_peer_union *)&cachep->sas; + + /* Create buffer with peer's address and port */ + length = 0; + switch (peer->sa.sa_family) { + case AF_INET: + length += sizeof(struct in_addr); + length += sizeof(peer->s4.sin_port); + break; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + case AF_INET6: + length += sizeof(struct in6_addr); + length += sizeof(peer->s6.sin6_port); + break; +#endif + default: + snmp_log(LOG_ERR, + "dtls: unknown address family %d generating a cookie\n", + peer->sa.sa_family); + return 0; + } + buffer = malloc(length); + if (buffer == NULL) { + snmp_log(LOG_ERR, "dtls: unknown address family generating a cookie\n"); + return 0; + } + + switch (peer->sa.sa_family) { + case AF_INET: + memcpy(buffer, + &peer->s4.sin_port, + sizeof(peer->s4.sin_port)); + memcpy(buffer + sizeof(peer->s4.sin_port), + &peer->s4.sin_addr, + sizeof(struct in_addr)); + break; +#ifdef NETSNMP_TRANSPORT_UDPIPV6_DOMAIN + case AF_INET6: + memcpy(buffer, + &peer->s6.sin6_port, + sizeof(peer->s6.sin6_port)); + memcpy(buffer + sizeof(peer->s6.sin6_port), + &peer->s6.sin6_addr, + sizeof(struct in6_addr)); + break; +#endif + default: + snmp_log(LOG_ERR, + "dtls: unknown address family %d generating a cookie\n", + peer->sa.sa_family); + return 0; + } + + /* Calculate HMAC of buffer using the secret */ + HMAC(EVP_sha1(), cookie_secret, NETSNMP_COOKIE_SECRET_LENGTH, + buffer, length, result, &resultlength); + OPENSSL_free(buffer); + + if (cookie_len != resultlength || memcmp(result, cookie, resultlength) != 0) + rc = 0; + else { + rc = 1; + cachep->flags |= NETSNMP_BIO_HAVE_COOKIE; + } + + DEBUGMSGT(("dtlsudp:cookie", "verify cookie: %d\n", rc)); + + return rc; +} + +#endif /* HAVE_LIBSSL_DTLS */ |