summaryrefslogtreecommitdiff
path: root/snmplib/transports/snmpDTLSUDPDomain.c
diff options
context:
space:
mode:
Diffstat (limited to 'snmplib/transports/snmpDTLSUDPDomain.c')
-rw-r--r--snmplib/transports/snmpDTLSUDPDomain.c1788
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 */