blob: 33e91a60a8d19e2684b9dbdbf15ade5d519026a6 [file] [log] [blame]
/* 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.
*/
/*
* NOTE: THIS IS AN EXPERIMENTAL IMPLEMENTATION AND NOT YET SUITABLE
* FOR PRODUCTION USE
*
* THERE are KNOWN security ISSUES with THIS code!
* (if nothing else, you can't tie certificates to certain hosts/users)
*/
/*
* ---------- Creating Certificates ----------
*
* Example pub/priv key creation steps using openssl (replace for-user
* with the appropriate name, etc (e.g. for a user you might use their
* first.last name and for a server, use it's hostname or something)
*
* 1) create the CSR file first:
*
* openssl req -days 365 -new -out for-user.csr -keyout for-user.priv
*
* 2) Optionally remove the passphrase if you hate that sort of thing
* (obviously not recommended; useful on servers without password prompts)
*
* openssl rsa -in for-user.priv -out for-user.insecure.priv
*
* 3) Create a self-signed key from the CSR:
*
* openssl x509 -set_serial `date +%Y%m%d` -in for-user.csr -out for-user.cert -req -signkey for-user.insecure.priv -days 365
*
*
* These can then be used by the config tokens for both the client and
* the server:
*
* ---------- Creating a CA for issuing certs ----------
*
* TBD
*
* ---------- Configuration ----------
*
* In the snmp.conf file, you should specify the following
* types of configuration lines:
*
* To tell the client which keys *it* should use to authenticate with:
*
* defX509ClientPriv /path/to/for-user.insecure.priv
* defX509ClientPub /path/to/for-user.insecure.cert
*
* To tell the client to only a list of servers:
*
* defX509ServerCerts /path/to/server-certs.certs
*
* (server-certs.certs can be created by simply cat'ing multiple
* server cert files into ones big file)
*
* To tell the server it's certs to offer:
*
* defX509ServerPub /path/to/server1.insecure.cert
* defX509ServerPriv /path/to/server1.insecure.priv
*
* To tell the server which keys it should accept from clients:
*
* defX509ClientCerts /path/to/client-certs.certs
*
* To authorize for R/W a particular CommonName from those certs:
*
* rwuser "John Doe"
*
*/
#include <net-snmp/net-snmp-config.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_WINSOCK_H
#include <winsock2.h>
#include <ws2tcpip.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/snmpDTLSUDPDomain.h>
#include <net-snmp/library/snmpUDPDomain.h>
#include <net-snmp/library/system.h>
#include <net-snmp/library/tools.h>
#include <net-snmp/library/snmp_openssl.h>
#include <net-snmp/library/callback.h>
#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif
#ifdef MSG_DONTWAIT
#define NETSNMP_DONTWAIT MSG_DONTWAIT
#else
#define NETSNMP_DONTWAIT 0
#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;
/* 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 *bio;
BIO *write_bio;
struct sockaddr_in sockaddr;
uint32_t ipv4addr;
u_short portnum;
SSL *con;
SSL_CTX *ctx;
struct bio_cache_s *next;
int msgnum;
int sock;
char *securityName;
} bio_cache;
bio_cache *biocache = NULL;
/*
* cached SSL context information
* (in theory we may want more than one per client/server but it's
* unlikely and a CPU and memory waste unless we do need more than one)
*/
SSL_CTX *client_ctx, *server_ctx;
/* 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(struct sockaddr_in *from_addr) {
bio_cache *cachep = NULL;
cachep = biocache;
while(cachep) {
if (cachep->ipv4addr == from_addr->sin_addr.s_addr &&
cachep->portnum == from_addr->sin_port) {
/* found an existing connection */
break;
}
cachep = cachep->next;
}
return cachep;
}
static const char * _x509_get_error(int x509failvalue, const char *location) {
static const char *reason = NULL;
/* XXX: use this instead: X509_verify_cert_error_string(err) */
switch (x509failvalue) {
case X509_V_OK:
reason = "X509_V_OK";
break;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
reason = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT";
break;
case X509_V_ERR_UNABLE_TO_GET_CRL:
reason = "X509_V_ERR_UNABLE_TO_GET_CRL";
break;
case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
reason = "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE";
break;
case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
reason = "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE";
break;
case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
reason = "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY";
break;
case X509_V_ERR_CERT_SIGNATURE_FAILURE:
reason = "X509_V_ERR_CERT_SIGNATURE_FAILURE";
break;
case X509_V_ERR_CRL_SIGNATURE_FAILURE:
reason = "X509_V_ERR_CRL_SIGNATURE_FAILURE";
break;
case X509_V_ERR_CERT_NOT_YET_VALID:
reason = "X509_V_ERR_CERT_NOT_YET_VALID";
break;
case X509_V_ERR_CERT_HAS_EXPIRED:
reason = "X509_V_ERR_CERT_HAS_EXPIRED";
break;
case X509_V_ERR_CRL_NOT_YET_VALID:
reason = "X509_V_ERR_CRL_NOT_YET_VALID";
break;
case X509_V_ERR_CRL_HAS_EXPIRED:
reason = "X509_V_ERR_CRL_HAS_EXPIRED";
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
reason = "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD";
break;
case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
reason = "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD";
break;
case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
reason = "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD";
break;
case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
reason = "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD";
break;
case X509_V_ERR_OUT_OF_MEM:
reason = "X509_V_ERR_OUT_OF_MEM";
break;
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
reason = "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT";
break;
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
reason = "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN";
break;
case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
reason = "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY";
break;
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
reason = "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE";
break;
case X509_V_ERR_CERT_CHAIN_TOO_LONG:
reason = "X509_V_ERR_CERT_CHAIN_TOO_LONG";
break;
case X509_V_ERR_CERT_REVOKED:
reason = "X509_V_ERR_CERT_REVOKED";
break;
case X509_V_ERR_INVALID_CA:
reason = "X509_V_ERR_INVALID_CA";
break;
case X509_V_ERR_PATH_LENGTH_EXCEEDED:
reason = "X509_V_ERR_PATH_LENGTH_EXCEEDED";
break;
case X509_V_ERR_INVALID_PURPOSE:
reason = "X509_V_ERR_INVALID_PURPOSE";
break;
case X509_V_ERR_CERT_UNTRUSTED:
reason = "X509_V_ERR_CERT_UNTRUSTED";
break;
case X509_V_ERR_CERT_REJECTED:
reason = "X509_V_ERR_CERT_REJECTED";
break;
case X509_V_ERR_SUBJECT_ISSUER_MISMATCH:
reason = "X509_V_ERR_SUBJECT_ISSUER_MISMATCH";
break;
case X509_V_ERR_AKID_SKID_MISMATCH:
reason = "X509_V_ERR_AKID_SKID_MISMATCH";
break;
case X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH:
reason = "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH";
break;
case X509_V_ERR_KEYUSAGE_NO_CERTSIGN:
reason = "X509_V_ERR_KEYUSAGE_NO_CERTSIGN";
break;
case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
reason = "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER";
break;
case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION:
reason = "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION";
break;
case X509_V_ERR_KEYUSAGE_NO_CRL_SIGN:
reason = "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN";
break;
case X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION:
reason = "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION";
break;
case X509_V_ERR_INVALID_NON_CA:
reason = "X509_V_ERR_INVALID_NON_CA";
break;
case X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED:
reason = "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED";
break;
case X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE:
reason = "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE";
break;
case X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED:
reason = "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED";
break;
case X509_V_ERR_INVALID_EXTENSION:
reason = "X509_V_ERR_INVALID_EXTENSION";
break;
case X509_V_ERR_INVALID_POLICY_EXTENSION:
reason = "X509_V_ERR_INVALID_POLICY_EXTENSION";
break;
case X509_V_ERR_NO_EXPLICIT_POLICY:
reason = "X509_V_ERR_NO_EXPLICIT_POLICY";
break;
case X509_V_ERR_UNNESTED_RESOURCE:
reason = "X509_V_ERR_UNNESTED_RESOURCE";
break;
case X509_V_ERR_APPLICATION_VERIFICATION:
reason = "X509_V_ERR_APPLICATION_VERIFICATION";
default:
reason = "unknown failure code";
}
return reason;
}
int verify_callback(int ok, X509_STORE_CTX *ctx) {
int err, depth;
char buf[1024];
X509 *thecert;
thecert = X509_STORE_CTX_get_current_cert(ctx);
err = X509_STORE_CTX_get_error(ctx);
depth = X509_STORE_CTX_get_error_depth(ctx);
/* things to do: */
X509_NAME_oneline(X509_get_subject_name(thecert), buf, sizeof(buf));
DEBUGMSGTL(("dtlsudp_x509",
"Cert: %s\n", buf));
DEBUGMSGTL(("dtlsudp_x509",
" verify value: %d, depth=%d, error code=%d, error string=%s\n",
ok, depth, err, _x509_get_error(err, "verify callback")));
/* check if we allow self-signed certs */
if (netsnmp_ds_get_boolean(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_ALLOW_SELF_SIGNED) &&
(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT == err ||
X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN == err)) {
DEBUGMSGTL(("dtlsudp_x509", " accepting a self-signed certificate\n"));
return 1;
}
DEBUGMSGTL(("dtlsudp_x509", " returing the passed in value of %d\n", ok));
return(ok);
}
static void _openssl_log_error(int rc, SSL *con, const char *location) {
const char *reason;
if (rc == -1) {
int sslnum = SSL_get_error(con, rc);
switch(sslnum) {
case SSL_ERROR_NONE:
reason = "SSL_ERROR_NONE";
break;
case SSL_ERROR_SSL:
reason = "SSL_ERROR_SSL";
break;
case SSL_ERROR_WANT_READ:
reason = "SSL_ERROR_WANT_READ";
break;
case SSL_ERROR_WANT_WRITE:
reason = "SSL_ERROR_WANT_WRITE";
break;
case SSL_ERROR_WANT_X509_LOOKUP:
reason = "SSL_ERROR_WANT_X509_LOOKUP";
break;
case SSL_ERROR_SYSCALL:
reason = "SSL_ERROR_SYSCALL";
snmp_log(LOG_ERR, "DTLS error: %s: rc=%d, sslerror = %d (%s): system_error=%d (%s)\n",
location, rc, sslnum, reason, errno, strerror(errno));
return;
case SSL_ERROR_ZERO_RETURN:
reason = "SSL_ERROR_ZERO_RETURN";
break;
case SSL_ERROR_WANT_CONNECT:
reason = "SSL_ERROR_WANT_CONNECT";
break;
case SSL_ERROR_WANT_ACCEPT:
reason = "SSL_ERROR_WANT_ACCEPT";
break;
default:
reason = "unknown";
}
snmp_log(LOG_ERR, "DTLS error: %s: rc=%d, sslerror = %d (%s)\n",
location, rc, sslnum, reason);
}
}
/* XXX: lots of malloc/state cleanup needed */
#define DIEHERE(msg) { snmp_log(LOG_ERR, "%s\n", msg); return NULL; }
static bio_cache *
start_new_cached_connection(int sock, struct sockaddr_in *remote_addr,
int we_are_client) {
bio_cache *cachep = NULL;
if (!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;
DEBUGMSGTL(("dtlsudp", "starting a new connection\n"));
cachep->next = biocache;
biocache = cachep;
cachep->ipv4addr = remote_addr->sin_addr.s_addr;
cachep->portnum = remote_addr->sin_port;
cachep->sock = sock;
memcpy(&cachep->sockaddr, remote_addr, sizeof(*remote_addr));
if (we_are_client) {
DEBUGMSGTL(("dtlsudp", "starting a new connection as a client to sock: %d\n", sock));
cachep->con = SSL_new(client_ctx);
/* XXX: session setting 735 */
/* create a bio */
cachep->bio = BIO_new(BIO_s_mem()); /* The one openssl reads from */
cachep->write_bio = BIO_new(BIO_s_mem()); /* openssl writes to */
BIO_set_mem_eof_return(cachep->bio, -1);
BIO_set_mem_eof_return(cachep->write_bio, -1);
SSL_set_bio(cachep->con, cachep->bio, cachep->write_bio);
SSL_set_connect_state(cachep->con);
} else {
/* we're the server */
cachep->bio = BIO_new(BIO_s_mem()); /* The one openssl reads from */
if (!cachep->bio)
DIEHERE("failed to create the read bio");
cachep->write_bio = BIO_new(BIO_s_mem()); /* openssl writes to */
if (!cachep->write_bio) {
DIEHERE("failed to create the write bio");
BIO_free(cachep->bio);
}
BIO_set_mem_eof_return(cachep->bio, -1);
BIO_set_mem_eof_return(cachep->write_bio, -1);
cachep->con = SSL_new(server_ctx);
if (!cachep->con) {
BIO_free(cachep->bio);
BIO_free(cachep->write_bio);
DIEHERE("failed to create the write bio");
}
/* turn on cookie exchange */
/* XXX: we need to only create cache entries when cookies succeed */
SSL_set_options(cachep->con, SSL_OP_COOKIE_EXCHANGE);
/* set the bios that openssl should read from and write to */
/* (and we'll do the opposite) */
SSL_set_bio(cachep->con, cachep->bio, cachep->write_bio);
SSL_set_accept_state(cachep->con);
}
return cachep;
}
static bio_cache *
find_or_create_bio_cache(int sock, struct sockaddr_in *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(sock, from_addr, we_are_client);
if (NULL == cachep) {
snmp_log(LOG_ERR, "failed to open a new dtls connection\n");
}
}
return cachep;
}
/*
* You can write something into opaque that will subsequently get passed back
* to your send function if you like. For instance, you might want to
* remember where a PDU came from, so that you can send a reply there...
*/
static int
netsnmp_dtlsudp_recv(netsnmp_transport *t, void *buf, int size,
void **opaque, int *olength)
{
int rc = -1;
socklen_t fromlen = sizeof(struct sockaddr);
netsnmp_addr_pair *addr_pair = NULL;
struct sockaddr *from;
netsnmp_tmStateReference *tmStateRef = NULL;
X509 *peer;
if (t != NULL && t->sock >= 0) {
/* create a tmStateRef cache for slow fill-in */
tmStateRef = SNMP_MALLOC_TYPEDEF(netsnmp_tmStateReference);
if (tmStateRef == NULL) {
*opaque = NULL;
*olength = 0;
return -1;
}
addr_pair = &tmStateRef->addresses;
tmStateRef->have_addresses = 1;
from = (struct sockaddr *) &(addr_pair->remote_addr);
while (rc < 0) {
#if defined(linux) && defined(IP_PKTINFO)
rc = netsnmp_udp_recvfrom(t->sock, buf, size, from, &fromlen, &(addr_pair->local_addr));
#else
rc = recvfrom(t->sock, buf, size, NETSNMP_DONTWAIT, from, &fromlen);
#endif /* linux && IP_PKTINFO */
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;
}
if (rc >= 0) {
/* now that we have the from address filled in, we can look up
the openssl context and have openssl read and process
appropriately */
/* if we don't have a cachep for this connection then
we're receiving something new and are the server
side */
/* XXX: allow for a SNMP client to never accept new conns? */
bio_cache *cachep =
find_or_create_bio_cache(t->sock, &addr_pair->remote_addr,
WE_ARE_SERVER);
if (NULL == cachep) {
SNMP_FREE(tmStateRef);
return -1;
}
/* write the received buffer to the memory-based input bio */
BIO_write(cachep->bio, buf, rc);
/* 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(cachep->con, buf, size);
DEBUGMSGTL(("dtlsudp", "received %d decoded bytes from dtls\n", rc));
if (BIO_ctrl_pending(cachep->write_bio) > 0) {
/* we have outgoing data to send; probably DTLS negotation */
u_char outbuf[65535];
int outsize;
int rc2;
/* 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) {
/* should always be true. */
#if defined(XXXFIXME) && defined(linux) && defined(IP_PKTINFO)
/* XXX: before this can work, we need to remember address we
received it from (addr_pair) */
rc2 = netsnmp_udp_sendto(cachep->sock, addr_pair->local_addr, addr_pair->remote_addr, outbuf, outsize);
#else
rc2 = sendto(t->sock, outbuf, outsize, 0, &cachep->sockaddr, sizeof(struct sockaddr));
#endif /* linux && IP_PKTINFO */
if (rc2 == -1) {
snmp_log(LOG_ERR, "failed to send a DTLS specific packet\n");
}
}
}
if (SSL_pending(cachep->con)) {
fprintf(stderr, "ack: got here... pending\n");
exit(1);
}
if (rc == -1) {
_openssl_log_error(rc, cachep->con, "SSL_read");
SNMP_FREE(tmStateRef);
if (SSL_get_error(cachep->con, rc) == SSL_ERROR_WANT_READ)
return -1; /* XXX: it's ok, but what's the right return? */
return rc;
}
{
char *str = netsnmp_udp_fmtaddr(NULL, addr_pair, sizeof(netsnmp_addr_pair));
DEBUGMSGTL(("dtlsudp",
"recvfrom fd %d got %d bytes (from %s)\n",
t->sock, rc, str));
free(str);
}
/* XXX: disallow NULL auth/encr algs in our implementations */
tmStateRef->transportSecurityLevel = SNMP_SEC_LEVEL_AUTHPRIV;
/* use x509 cert to do lookup to secname if DNE in cachep yet */
if (!cachep->securityName) {
if (NULL != (peer = SSL_get_peer_certificate(cachep->con))) {
X509_NAME *subname;
char namebuf[1024];
/* we have one */
subname = X509_get_subject_name(peer);
X509_NAME_get_text_by_NID(subname, NID_commonName,
namebuf, sizeof(namebuf));
DEBUGMSGTL(("dtlsudp", "got commonname: %s\n",
namebuf));
cachep->securityName = strdup(namebuf);
DEBUGMSGTL(("dtlsudp", "set SecName to: %s\n",
cachep->securityName));
} else {
SNMP_FREE(tmStateRef);
return -1;
}
}
/* XXX: detect and throw out overflow secname sizes rather
than truncating. */
strlcpy(tmStateRef->securityName, cachep->securityName,
sizeof(tmStateRef->securityName));
tmStateRef->securityName[sizeof(tmStateRef->securityName)-1] = '\0';
tmStateRef->securityNameLen = strlen(tmStateRef->securityName);
*opaque = tmStateRef;
*olength = sizeof(netsnmp_tmStateReference);
} else {
DEBUGMSGTL(("dtlsudp", "recvfrom fd %d err %d (\"%s\")\n",
t->sock, errno, strerror(errno)));
}
}
return rc;
}
static int
netsnmp_dtlsudp_send(netsnmp_transport *t, void *buf, int size,
void **opaque, int *olength)
{
int rc = -1;
netsnmp_addr_pair *addr_pair = NULL;
struct sockaddr *to = NULL;
bio_cache *cachep = NULL;
netsnmp_tmStateReference *tmStateRef = NULL;
u_char outbuf[65535];
if (opaque != NULL && *opaque != NULL &&
*olength == sizeof(netsnmp_tmStateReference)) {
tmStateRef = (netsnmp_tmStateReference *) *opaque;
if (tmStateRef->have_addresses)
addr_pair = &(tmStateRef->addresses);
else if (t != NULL && t->data != NULL &&
t->data_length == sizeof(netsnmp_addr_pair))
addr_pair = (netsnmp_addr_pair *) (t->data);
} else if (t != NULL && t->data != NULL &&
t->data_length == sizeof(netsnmp_addr_pair)) {
addr_pair = (netsnmp_addr_pair *) (t->data);
}
if (NULL == addr_pair) {
snmp_log(LOG_ERR, "dtlsudp_send: can't get address to send to\n");
return -1;
}
to = (struct sockaddr *) &(addr_pair->remote_addr);
if (NULL == to || NULL == t || t->sock <= 0) {
snmp_log(LOG_ERR, "invalid netsnmp_dtlsudp_send usage\n");
return -1;
}
/* we're always a client if we're sending to something unknown yet */
if (NULL ==
(cachep = find_or_create_bio_cache(t->sock, &addr_pair->remote_addr,
WE_ARE_CLIENT)))
return -1;
if (!cachep->securityName && tmStateRef && tmStateRef->securityNameLen > 0)
cachep->securityName = strdup(tmStateRef->securityName);
{
char *str = netsnmp_udp_fmtaddr(NULL, (void *) addr_pair,
sizeof(netsnmp_addr_pair));
DEBUGMSGTL(("dtlsudp", "send %d bytes from %p to %s on fd %d\n",
size, buf, str, t->sock));
free(str);
}
rc = SSL_write(cachep->con, buf, size);
if (rc < 0) {
_openssl_log_error(rc, cachep->con, "SSL_write");
}
/* 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;
}
#if defined(FIXME) && defined(linux) && defined(IP_PKTINFO)
/* XXX: before this can work, we need to remember address we
received it from (addr_pair) */
rc = netsnmp_udp_sendto(cachep->sock, &cachep->sockaddr remote addr_pair ? &(addr_pair->local_addr) : NULL, to, outbuf, rc);
#else
rc = sendto(t->sock, outbuf, rc, 0, &cachep->sockaddr, sizeof(struct sockaddr));
#endif /* linux && IP_PKTINFO */
return rc;
}
static int
netsnmp_dtlsudp_close(netsnmp_transport *t)
{
int rc = -1;
/* XXX: issue a proper dtls closure notification(s) */
if (t->sock >= 0) {
#ifndef HAVE_CLOSESOCKET
rc = close(t->sock);
#else
rc = closesocket(t->sock);
#endif
t->sock = -1;
}
return rc;
}
/*
* 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_dtlsudp_transport(struct sockaddr_in *addr, int local)
{
netsnmp_transport *t = NULL;
int rc = 0;
char *str = NULL;
char *client_socket = NULL;
netsnmp_addr_pair addr_pair;
if (addr == NULL || addr->sin_family != AF_INET) {
return NULL;
}
memset(&addr_pair, 0, sizeof(netsnmp_addr_pair));
memcpy(&(addr_pair.remote_addr), addr, sizeof(struct sockaddr_in));
t = SNMP_MALLOC_TYPEDEF(netsnmp_transport);
if (t == NULL) {
return NULL;
}
str = netsnmp_udp_fmtaddr(NULL, (void *)&addr_pair,
sizeof(netsnmp_addr_pair));
DEBUGMSGTL(("dtlsudp", "open %s %s\n", local ? "local" : "remote",
str));
free(str);
t->domain = netsnmpDTLSUDPDomain;
t->domain_length = netsnmpDTLSUDPDomain_len;
t->sock = socket(PF_INET, SOCK_DGRAM, 0);
DEBUGMSGTL(("dtlsudp", "openned socket %d as local=%d\n", t->sock,
local));
if (t->sock < 0) {
netsnmp_transport_free(t);
return NULL;
}
/* XXX: Potentially set sock opts here (SO_SNDBUF/SO_RCV_BUF) */
/* XXX: and buf size */
_netsnmp_udp_sockopt_set(t->sock, local);
if (local) {
/*
* This session is inteneded as a server, so we must bind on to the
* given IP address, which may include an interface address, or could
* be INADDR_ANY, but certainly includes a port number.
*/
t->local = (u_char *) malloc(6);
if (t->local == NULL) {
netsnmp_transport_free(t);
return NULL;
}
memcpy(t->local, (u_char *) & (addr->sin_addr.s_addr), 4);
t->local[4] = (htons(addr->sin_port) & 0xff00) >> 8;
t->local[5] = (htons(addr->sin_port) & 0x00ff) >> 0;
t->local_length = 6;
#if defined(linux) && defined(IP_PKTINFO)
{
int sockopt = 1;
if (setsockopt(t->sock, SOL_IP, IP_PKTINFO, &sockopt, sizeof sockopt) == -1) {
DEBUGMSGTL(("dtlsudp", "couldn't set IP_PKTINFO: %s\n",
strerror(errno)));
netsnmp_transport_free(t);
return NULL;
}
DEBUGMSGTL(("dtlsudp", "set IP_PKTINFO\n"));
}
#endif
rc = bind(t->sock, (struct sockaddr *) addr,
sizeof(struct sockaddr));
if (rc != 0) {
netsnmp_dtlsudp_close(t);
netsnmp_transport_free(t);
return NULL;
}
t->data = NULL;
t->data_length = 0;
} else {
/*
* This is a client session. If we've been given a
* client address to send from, then bind to that.
* Otherwise the send will use "something sensible".
*/
client_socket = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_CLIENT_ADDR);
if (client_socket) {
struct sockaddr_in client_addr;
netsnmp_sockaddr_in2(&client_addr, client_socket, NULL);
addr_pair.local_addr = client_addr.sin_addr;
client_addr.sin_port = 0;
DEBUGMSGTL(("dtlsudp", "binding socket: %d\n", t->sock));
rc = bind(t->sock, (struct sockaddr *)&client_addr,
sizeof(struct sockaddr));
if ( rc != 0 ) {
DEBUGMSGTL(("dtlsudp", "failed to bind for clientaddr: %d %s\n",
errno, strerror(errno)));
netsnmp_dtlsudp_close(t);
netsnmp_transport_free(t);
return NULL;
}
}
str = netsnmp_udp_fmtaddr(NULL, (void *)&addr_pair,
sizeof(netsnmp_addr_pair));
DEBUGMSGTL(("dtlsudp", "client open %s\n", str));
free(str);
/*
* Save the (remote) address in the
* transport-specific data pointer for later use by netsnmp_dtlsudp_send.
*/
t->data = SNMP_MALLOC_TYPEDEF(netsnmp_addr_pair);
t->remote = (u_char *)malloc(6);
if (t->data == NULL || t->remote == NULL) {
netsnmp_transport_free(t);
return NULL;
}
memcpy(t->remote, (u_char *) & (addr->sin_addr.s_addr), 4);
t->remote[4] = (htons(addr->sin_port) & 0xff00) >> 8;
t->remote[5] = (htons(addr->sin_port) & 0x00ff) >> 0;
t->remote_length = 6;
memcpy(t->data, &addr_pair, sizeof(netsnmp_addr_pair));
t->data_length = sizeof(netsnmp_addr_pair);
/* 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");
}
/*
* 16-bit length field, 8 byte DTLS header, 20 byte IPv4 header
*/
t->msgMaxSize = 0xffff - 8 - 20;
t->f_recv = netsnmp_dtlsudp_recv;
t->f_send = netsnmp_dtlsudp_send;
t->f_close = netsnmp_dtlsudp_close;
t->f_accept = NULL;
t->f_fmtaddr = netsnmp_udp_fmtaddr;
t->flags = NETSNMP_TRANSPORT_FLAG_TUNNELED;
return t;
}
void
netsnmp_dtlsudp_agent_config_tokens_register(void)
{
}
netsnmp_transport *
netsnmp_dtlsudp_create_tstring(const char *str, int local,
const char *default_target)
{
struct sockaddr_in addr;
if (netsnmp_sockaddr_in2(&addr, str, default_target)) {
return netsnmp_dtlsudp_transport(&addr, local);
} else {
return NULL;
}
}
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);
}
return NULL;
}
#define LOGANDDIE(msg) { snmp_log(LOG_ERR, "%s\n", msg); return 0; }
static int have_done_init = 0;
static int
dtlsudp_bootstrap(int majorid, int minorid, void *serverarg, void *clientarg) {
const char *certfile;
EVP_PKEY *key = NULL;
X509 *cert = NULL;
BIO *keybio = NULL;
/* don't do this more than once */
if (have_done_init)
return 0;
have_done_init = 1;
/***********************************************************************
* Set up the client context
*/
client_ctx = SSL_CTX_new(DTLSv1_client_method());
if (!client_ctx) {
LOGANDDIE("can't create a new context");
}
SSL_CTX_set_read_ahead (client_ctx, 1); /* Required for DTLS */
SSL_CTX_set_verify(client_ctx,
SSL_VERIFY_PEER|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT|
SSL_VERIFY_CLIENT_ONCE,
&verify_callback);
keybio = BIO_new(BIO_s_file());
if (!keybio)
LOGANDDIE ("error creating bio for reading public key");
certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_CLIENT_PUB);
DEBUGMSGTL(("dtlsudp", "using public key: %s\n", certfile));
if (BIO_read_filename(keybio, certfile) <=0)
LOGANDDIE ("error reading public key");
cert = PEM_read_bio_X509_AUX(keybio, NULL, NULL, NULL);
if (!cert)
LOGANDDIE("failed to load public key");
/* XXX: mem leak on previous keybio? */
certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_CLIENT_PRIV);
keybio = BIO_new(BIO_s_file());
if (!keybio)
LOGANDDIE ("error creating bio for reading private key");
DEBUGMSGTL(("dtlsudp", "using private key: %s\n", certfile));
if (!keybio ||
BIO_read_filename(keybio, certfile) <= 0)
LOGANDDIE ("error reading private key");
key = PEM_read_bio_PrivateKey(keybio, NULL, NULL, NULL);
if (!key)
LOGANDDIE("failed to load private key");
if (SSL_CTX_use_certificate(client_ctx, cert) <= 0)
LOGANDDIE("failed to set the certificate to use");
if (SSL_CTX_use_PrivateKey(client_ctx, key) <= 0)
LOGANDDIE("failed to set the private key to use");
if (!SSL_CTX_check_private_key(client_ctx))
LOGANDDIE("public and private keys incompatible");
certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_SERVER_CERTS);
/* XXX: also need to match individual cert to indiv. host */
if(! SSL_CTX_load_verify_locations(client_ctx, certfile, NULL)) {
LOGANDDIE("failed to load truststore");
/* Handle failed load here */
}
if (!SSL_CTX_set_default_verify_paths(client_ctx)) {
LOGANDDIE ("failed to set default verify path");
}
/***********************************************************************
* Set up the server context
*/
/* setting up for ssl */
server_ctx = SSL_CTX_new(DTLSv1_server_method());
if (!server_ctx) {
LOGANDDIE("can't create a new context");
}
certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_SERVER_PUB);
if (SSL_CTX_use_certificate_file(server_ctx, certfile,
SSL_FILETYPE_PEM) < 1) {
LOGANDDIE("faild to load cert");
}
certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_SERVER_PRIV);
if (SSL_CTX_use_PrivateKey_file(server_ctx, certfile, SSL_FILETYPE_PEM) < 1) {
LOGANDDIE("faild to load key");
}
SSL_CTX_set_read_ahead(server_ctx, 1);
certfile = netsnmp_ds_get_string(NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_CLIENT_CERTS);
if(! SSL_CTX_load_verify_locations(server_ctx, certfile, NULL)) {
LOGANDDIE("failed to load truststore");
/* Handle failed load here */
}
SSL_CTX_set_verify(server_ctx,
SSL_VERIFY_PEER|
SSL_VERIFY_FAIL_IF_NO_PEER_CERT|
SSL_VERIFY_CLIENT_ONCE,
&verify_callback);
return 0;
}
void
netsnmp_dtlsudp_ctor(void)
{
DEBUGMSGTL(("dtlsudp", "registering DTLS constructor\n"));
/* config settings */
/* bootstrap ssl since we'll need it */
netsnmp_init_openssl();
/*
* for the client
*/
/* pem file of valid server CERT CAs */
netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ServerCerts",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_SERVER_CERTS);
/* the public client cert to authenticate with */
netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ClientPub",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_CLIENT_PUB);
/* the private client cert to authenticate with */
netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ClientPriv",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_CLIENT_PRIV);
/*
* for the server
*/
/* The list of valid client keys to accept (or CAs I think) */
netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ClientCerts",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_CLIENT_CERTS);
/* The X509 server key to use */
netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ServerPub",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_SERVER_PUB);
netsnmp_ds_register_config(ASN_OCTET_STR, "snmp", "defX509ServerPriv",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_X509_SERVER_PRIV);
netsnmp_ds_register_config(ASN_BOOLEAN, "snmp", "AllowSelfSignedX509",
NETSNMP_DS_LIBRARY_ID,
NETSNMP_DS_LIB_ALLOW_SELF_SIGNED);
/*
* register our boot-strapping needs
*/
snmp_register_callback(SNMP_CALLBACK_LIBRARY,
SNMP_CALLBACK_POST_READ_CONFIG,
dtlsudp_bootstrap, NULL);
dtlsudpDomain.name = netsnmpDTLSUDPDomain;
dtlsudpDomain.name_length = netsnmpDTLSUDPDomain_len;
dtlsudpDomain.prefix = (const char**)calloc(2, sizeof(char *));
dtlsudpDomain.prefix[0] = "dtlsudp";
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;
netsnmp_tdomain_register(&dtlsudpDomain);
}