-
-/*
- * netsec.c -- Network security routines for handling protocols that
+/* netsec.c -- Network security routines for handling protocols that
* require SASL and/or TLS.
*
* This code is Copyright (c) 2016, by the authors of nmh. See the
#include <h/oauth.h>
#include <stdarg.h>
#include <sys/select.h>
+#include "base64.h"
#ifdef CYRUS_SASL
#include <sasl/sasl.h>
struct _netsec_context {
int ns_readfd; /* Read descriptor for network connection */
int ns_writefd; /* Write descriptor for network connection */
+ int ns_noclose; /* Do not close file descriptors if set */
int ns_snoop; /* If true, display network data */
int ns_snoop_noend; /* If true, didn't get a CR/LF on last line */
netsec_snoop_callback *ns_snoop_cb; /* Snoop output callback */
void *ns_snoop_context; /* Context data for snoop function */
int ns_timeout; /* Network read timeout, in seconds */
char *ns_userid; /* Userid for authentication */
+ char *ns_hostname; /* Hostname we've connected to */
unsigned char *ns_inbuffer; /* Our read input buffer */
unsigned char *ns_inptr; /* Our read buffer input pointer */
unsigned int ns_inbuflen; /* Length of data in input buffer */
char *oauth_service; /* OAuth2 service name */
#endif /* OAUTH_SUPPORT */
#ifdef CYRUS_SASL
- char *sasl_hostname; /* Hostname we've connected to */
sasl_conn_t *sasl_conn; /* SASL connection context */
sasl_ssf_t sasl_ssf; /* SASL Security Strength Factor */
sasl_callback_t *sasl_cbs; /* Callbacks used by SASL */
/*
* How this code works, in general.
*
- * _If_ we are using no encryption or SASL encryption, then we buffer the
- * network data through ns_inbuffer and ns_outbuffer. That should be
- * relatively self-explanatory.
+ * _If_ we are using no encryption then we buffer the network data
+ * through ns_inbuffer and ns_outbuffer. That should be relatively
+ * self-explanatory.
+ *
+ * If we use encryption, then ns_inbuffer and ns_outbuffer contain the
+ * cleartext data. When it comes time to send the encrypted data on the
+ * (either from a flush or the buffer is full) we either use BIO_write()
+ * for TLS or sasl_encode() (followed by a write() for Cyrus-SASL. For
+ * reads we either use BIO_read() (TLS) or do a network read into a
+ * temporary buffer and use sasl_decode() (Cyrus-SASL). Note that if
+ * negotiate TLS then we disable SASL encryption.
*
- * If we are using SSL for encryption, then use a buffering BIO for output
- * (that just easier). Still do buffering for reads; when we need more
- * data we call the BIO_read() function to fill our local buffer.
+ * We used to use a buffering BIO for the reads/writes for TLS, but it
+ * ended up being complicated to special-case the buffering for everything
+ * except TLS, so the buffering is now unified, no matter which encryption
+ * method is being used (even none).
*
- * For SASL, we make use of (for now) the Cyrus-SASL library. For some
- * mechanisms, we implement those mechanisms directly since the Cyrus SASL
- * library doesn't support them (like OAuth).
+ * For SASL authentication, we make use of (for now) the Cyrus-SASL
+ * library. For some mechanisms, we implement those mechanisms directly
+ * since the Cyrus SASL library doesn't support them (like OAuth).
*/
/*
netsec_context *
netsec_init(void)
{
- netsec_context *nsc = mh_xmalloc(sizeof(*nsc));
+ netsec_context *nsc;
+ NEW(nsc);
nsc->ns_readfd = -1;
nsc->ns_writefd = -1;
+ nsc->ns_noclose = 0;
nsc->ns_snoop = 0;
nsc->ns_snoop_noend = 0;
nsc->ns_snoop_cb = NULL;
nsc->ns_snoop_context = NULL;
nsc->ns_userid = NULL;
+ nsc->ns_hostname = NULL;
nsc->ns_timeout = 60; /* Our default */
nsc->ns_inbufsize = NETSEC_BUFSIZE;
nsc->ns_inbuffer = mh_xmalloc(nsc->ns_inbufsize);
#endif /* OAUTH_SUPPORT */
#ifdef CYRUS_SASL
nsc->sasl_conn = NULL;
- nsc->sasl_hostname = NULL;
nsc->sasl_cbs = NULL;
nsc->sasl_creds = NULL;
nsc->sasl_secret = NULL;
/*
* Shutdown the connection completely and free all resources.
- * The connection is only closed if the flag is given.
*/
void
-netsec_shutdown(netsec_context *nsc, int closeflag)
+netsec_shutdown(netsec_context *nsc)
{
- if (nsc->ns_userid)
- free(nsc->ns_userid);
- if (nsc->ns_inbuffer)
- free(nsc->ns_inbuffer);
- if (nsc->ns_outbuffer)
- free(nsc->ns_outbuffer);
- if (nsc->sasl_mech)
- free(nsc->sasl_mech);
- if (nsc->sasl_chosen_mech)
- free(nsc->sasl_chosen_mech);
+ free(nsc->ns_userid);
+ free(nsc->ns_hostname);
+ free(nsc->ns_inbuffer);
+ free(nsc->ns_outbuffer);
+ free(nsc->sasl_mech);
+ free(nsc->sasl_chosen_mech);
#ifdef OAUTH_SERVICE
- if (nsc->oauth_service)
- free(nsc->oauth_service);
+ free(nsc->oauth_service);
#endif /* OAUTH_SERVICE */
#ifdef CYRUS_SASL
if (nsc->sasl_conn)
sasl_dispose(&nsc->sasl_conn);
- if (nsc->sasl_hostname)
- free(nsc->sasl_hostname);
- if (nsc->sasl_cbs)
- free(nsc->sasl_cbs);
- if (nsc->sasl_creds) {
- if (nsc->sasl_creds->password)
- memset(nsc->sasl_creds->password, 0,
- strlen(nsc->sasl_creds->password));
- free(nsc->sasl_creds);
- }
+ free(nsc->sasl_cbs);
+ if (nsc->sasl_creds)
+ nmh_credentials_free(nsc->sasl_creds);
if (nsc->sasl_secret) {
if (nsc->sasl_secret->len > 0) {
memset(nsc->sasl_secret->data, 0, nsc->sasl_secret->len);
}
free(nsc->sasl_secret);
}
- if (nsc->sasl_tmpbuf)
- free(nsc->sasl_tmpbuf);
+ free(nsc->sasl_tmpbuf);
#endif /* CYRUS_SASL */
#ifdef TLS_SUPPORT
if (nsc->ssl_io)
BIO_free_all(nsc->ssl_io);
#endif /* TLS_SUPPORT */
- if (closeflag) {
+ if (! nsc->ns_noclose) {
if (nsc->ns_readfd != -1)
close(nsc->ns_readfd);
if (nsc->ns_writefd != -1 && nsc->ns_writefd != nsc->ns_readfd)
nsc->ns_userid = getcpy(userid);
}
+/*
+ * Set the hostname of the remote host we're connecting to.
+ */
+
+void
+netsec_set_hostname(netsec_context *nsc, const char *hostname)
+{
+ nsc->ns_hostname = mh_xstrdup(hostname);
+}
+
/*
* Get the snoop flag for this connection
*/
if (decodeBase64(string, &decoded, &decodedlen, 1, NULL) == OK) {
/*
- * Some mechanisms preoduce large binary tokens, which aren't really
+ * Some mechanisms produce large binary tokens, which aren't really
* readable. So let's do a simple heuristic. If the token is greater
* than 100 characters _and_ the first 100 bytes are more than 50%
* non-ASCII, then don't print the decoded buffer, just the
* assume here that this has something in it.
*/
- retlen = size > nsc->ns_inbuflen ? nsc->ns_inbuflen : size;
+ retlen = min(size, nsc->ns_inbuflen);
memcpy(buffer, nsc->ns_inptr, retlen);
*
* - Unlike every other function, we return a pointer to the
* existing buffer. This pointer is valid until you call another
- * read functiona again.
- * - We NUL-terminated the buffer right at the end, before the terminator.
+ * read function again.
+ * - We NUL-terminate the buffer right at the end, before the CR-LF terminator.
* - Technically we look for a LF; if we find a CR right before it, then
* we back up one.
- * - If your data may contain embedded NULs, this won't work.
+ * - If your data may contain embedded NULs, this won't work. You should
+ * be using netsec_read() in that case.
*/
char *
if (nsc->ns_snoop_cb)
nsc->ns_snoop_cb(nsc, sptr, strlen(sptr),
nsc->ns_snoop_context);
- else
- fprintf(stderr, "%s\n", sptr);
+ else {
+ fputs(sptr, stderr);
+ putc('\n', stderr);
+ }
}
return sptr;
}
*/
if (count >= nsc->ns_inbufsize / 2) {
- netsec_err(errstr, "Unable to find a line terminator after %d bytes",
+ netsec_err(errstr, "Unable to find a line terminator after %zu bytes",
count);
return NULL;
}
nsc->ns_inptr = nsc->ns_inbuffer;
}
-#ifdef CYRUS_SASL
+#if defined(CYRUS_SASL) || defined(TLS_SUPPORT)
retry:
-#endif /* CYRUS_SASL */
+#endif /* CYRUS_SASL || TLS_SUPPORT */
/*
* If we are using TLS and there's anything pending, then skip the
* select call
*/
}
+ /*
+ * Some explanation:
+ *
+ * startoffset is the offset from the beginning of the input
+ * buffer to data that is in our input buffer, but has not yet
+ * been consumed. This can be non-zero if functions like
+ * netsec_readline() leave leftover data.
+ *
+ * remaining is the remaining amount of unconsumed data in the input
+ * buffer.
+ *
+ * end is a pointer to the end of the valid data + 1; it's where
+ * the next read should go.
+ */
+
startoffset = nsc->ns_inptr - nsc->ns_inbuffer;
remaining = nsc->ns_inbufsize - (startoffset + nsc->ns_inbuflen);
end = nsc->ns_inptr + nsc->ns_inbuflen;
+ /*
+ * If we're past the halfway point in our read buffers, shuffle everything
+ * back to the beginning.
+ */
+
+ if (startoffset > nsc->ns_inbufsize / 2) {
+ memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen);
+ nsc->ns_inptr = nsc->ns_inbuffer;
+ startoffset = 0;
+ remaining = nsc->ns_inbufsize - nsc->ns_inbuflen;
+ end = nsc->ns_inptr + nsc->ns_inbuflen;
+ }
+
/*
* If we are using TLS, then just read via the BIO. But we still
* use our local buffer.
if (nsc->tls_active) {
rc = BIO_read(nsc->ssl_io, end, remaining);
if (rc == 0) {
+ SSL *ssl;
+ int errcode;
+
+ /*
+ * Check to see if we're supposed to retry; if so,
+ * then go back and read again.
+ */
+
+ if (BIO_should_retry(nsc->ssl_io))
+ goto retry;
+
/*
- * Either EOF, or possibly an error. Either way, it was probably
- * unexpected, so treat as error.
+ * Okay, fine. Get the real error out of the SSL context.
*/
- netsec_err(errstr, "TLS peer aborted connection");
+
+ if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) {
+ netsec_err(errstr, "SSL_read() returned 0, but cannot "
+ "retrieve SSL context");
+ return NOTOK;
+ }
+
+ errcode = SSL_get_error(ssl, rc);
+ if (errcode == SSL_ERROR_ZERO_RETURN) {
+ netsec_err(errstr, "TLS peer closed remote connection");
+ } else {
+ netsec_err(errstr, "TLS network read failed: %s",
+ ERR_error_string(ERR_peek_last_error(), NULL));
+ }
+ if (nsc->ns_snoop)
+ ERR_print_errors_fp(stderr);
return NOTOK;
- } else if (rc < 0) {
+ }
+ if (rc < 0) {
/* Definitely an error */
netsec_err(errstr, "Read on TLS connection failed: %s",
ERR_error_string(ERR_get_error(), NULL));
#endif /* CYRUS_SASL */
nsc->ns_inbuflen += rc;
- /*
- * If we're past the halfway point in our read buffers, shuffle everything
- * back to the beginning.
- */
-
- if (startoffset > nsc->ns_inbufsize / 2) {
- memmove(nsc->ns_inbuffer, nsc->ns_inptr, nsc->ns_inbuflen);
- nsc->ns_inptr = nsc->ns_inbuffer;
- }
-
return OK;
}
"%d bytes, but our buffer size was only %d bytes",
rc, nsc->ns_outbufsize);
return NOTOK;
- } else {
- /*
- * Generate a flush (which may be inefficient, but hopefully
- * it isn't) and then try again.
- */
- if (netsec_flush(nsc, errstr) != OK)
- return NOTOK;
- /*
- * After this, outbuffer should == outptr, so we shouldn't
- * hit this next time around.
- */
- goto retry;
}
+ /*
+ * Generate a flush (which may be inefficient, but hopefully
+ * it isn't) and then try again.
+ */
+ if (netsec_flush(nsc, errstr) != OK)
+ return NOTOK;
+ /*
+ * After this, outbuffer should == outptr, so we shouldn't
+ * hit this next time around.
+ */
+ goto retry;
}
if (nsc->ns_snoop) {
*/
int
-netsec_set_sasl_params(netsec_context *nsc, const char *hostname,
- const char *service, const char *mechanism,
- netsec_sasl_callback callback, char **errstr)
+netsec_set_sasl_params(netsec_context *nsc, const char *service,
+ const char *mechanism, netsec_sasl_callback callback,
+ char **errstr)
{
#ifdef CYRUS_SASL
sasl_callback_t *sasl_cbs;
int retval;
+ if (!nsc->ns_hostname) {
+ netsec_err(errstr, "Internal error: ns_hostname is NULL");
+ return NOTOK;
+ }
+
if (! sasl_initialized) {
retval = sasl_client_init(NULL);
if (retval != SASL_OK) {
nsc->sasl_cbs = sasl_cbs;
- retval = sasl_client_new(service, hostname, NULL, NULL, nsc->sasl_cbs, 0,
- &nsc->sasl_conn);
+ retval = sasl_client_new(service, nsc->ns_hostname, NULL, NULL,
+ nsc->sasl_cbs, 0, &nsc->sasl_conn);
if (retval) {
netsec_err(errstr, "SASL new client allocation failed: %s",
return NOTOK;
}
+ /*
+ * Set up our credentials
+ */
+
+ nsc->sasl_creds = nmh_get_credentials(nsc->ns_hostname, nsc->ns_userid);
+
+#else /* CYRUS_SASL */
+ NMH_UNUSED(service);
+ NMH_UNUSED(errstr);
+#endif /* CYRUS_SASL */
+
/*
* According to the RFC, mechanisms can only be uppercase letter, numbers,
- * and a hypen or underscore. So make sure we uppercase any letters
+ * and a hyphen or underscore. So make sure we uppercase any letters
* in case the user passed in lowercase.
*/
if (mechanism) {
char *p;
- nsc->sasl_mech = getcpy(mechanism);
+ nsc->sasl_mech = mh_xstrdup(mechanism);
for (p = nsc->sasl_mech; *p; p++)
- if (isascii((unsigned char) *p)) /* Just in case */
+ if (isascii((unsigned char) *p)) /* Leave non-ASCII lower alone. */
*p = toupper((unsigned char) *p);
}
nsc->sasl_proto_cb = callback;
- nsc->sasl_hostname = getcpy(hostname);
return OK;
-#else /* CYRUS_SASL */
- NMH_UNUSED(nsc);
- NMH_UNUSED(hostname);
- NMH_UNUSED(service);
- NMH_UNUSED(mechanism);
- NMH_UNUSED(callback);
-
- netsec_err(errstr, "SASL is not supported");
-
- return NOTOK;
-#endif /* CYRUS_SASL */
}
#ifdef CYRUS_SASL
if (! result || (id != SASL_CB_USER && id != SASL_CB_AUTHNAME))
return SASL_BADPARAM;
- if (nsc->ns_userid == NULL) {
- /*
- * Pass the 1 third argument to nmh_get_credentials() so that
- * a defauly user if the -user switch wasn't supplied, and so
- * that a default password will be supplied. That's used when
- * those values really don't matter, and only with legacy/.netrc,
- * i.e., with a credentials profile entry.
- */
-
- if (nsc->sasl_creds == NULL) {
- nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
- nsc->sasl_creds->user = NULL;
- nsc->sasl_creds->password = NULL;
- }
+ *result = nmh_cred_get_user(nsc->sasl_creds);
- if (nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid, 1,
- nsc->sasl_creds) != OK)
- return SASL_BADPARAM;
-
- if (nsc->ns_userid != nsc->sasl_creds->user) {
- if (nsc->ns_userid)
- free(nsc->ns_userid);
- nsc->ns_userid = getcpy(nsc->sasl_creds->user);
- }
- }
-
- *result = nsc->ns_userid;
if (len)
- *len = strlen(nsc->ns_userid);
+ *len = strlen(*result);
return SASL_OK;
}
sasl_secret_t **psecret)
{
netsec_context *nsc = (netsec_context *) context;
+ const char *password;
int len;
NMH_UNUSED(conn);
if (! psecret || id != SASL_CB_PASS)
return SASL_BADPARAM;
- if (nsc->sasl_creds == NULL) {
- nsc->sasl_creds = mh_xmalloc(sizeof(*nsc->sasl_creds));
- nsc->sasl_creds->user = NULL;
- nsc->sasl_creds->password = NULL;
- }
-
- if (nsc->sasl_creds->password == NULL) {
- /*
- * Pass the 0 third argument to nmh_get_credentials() so
- * that the default password isn't used. With legacy/.netrc
- * credentials support, we'll only get here if the -user
- * switch to send(1)/post(8) wasn't used.
- */
-
- if (nmh_get_credentials(nsc->sasl_hostname, nsc->ns_userid, 0,
- nsc->sasl_creds) != OK) {
- return SASL_BADPARAM;
- }
- }
+ password = nmh_cred_get_password(nsc->sasl_creds);
- len = strlen(nsc->sasl_creds->password);
+ len = strlen(password);
/*
* sasl_secret_t includes 1 bytes for "data" already, so that leaves
return SASL_NOMEM;
(*psecret)->len = len;
- strcpy((char *) (*psecret)->data, nsc->sasl_creds->password);
+ strcpy((char *) (*psecret)->data, password);
nsc->sasl_secret = *psecret;
unsigned char *xoauth_client_res;
size_t xoauth_client_res_len;
#endif /* OAUTH_SUPPORT */
+#if defined CYRUS_SASL || defined OAUTH_SUPPORT
int rc;
+#endif /* CYRUS_SASL || OAUTH_SUPPORT */
/*
* If we've been passed a requested mechanism, check our mechanism
return NOTOK;
}
- nsc->sasl_chosen_mech = getcpy(nsc->sasl_mech);
+ nsc->sasl_chosen_mech = mh_xstrdup(nsc->sasl_mech);
if (mh_oauth_do_xoauth(nsc->ns_userid, nsc->oauth_service,
&xoauth_client_res, &xoauth_client_res_len,
/*
* We're going to assume the error here is a JSON response;
* we ignore it and send a blank message in response. We should
- * then get either an +OK or -ERR
+ * then get a failure messages with a useful error. We should
+ * NOT get a success message at this point.
*/
free(*errstr);
nsc->sasl_proto_cb(NETSEC_SASL_WRITE, NULL, 0, NULL, 0, NULL);
* messages.
*/
- memset(&secprops, 0, sizeof(secprops));
+ ZERO(&secprops);
secprops.maxbufsize = SASL_MAXRECVBUF;
/*
rc = sasl_client_step(nsc->sasl_conn, (char *) outbuf, outbuflen, NULL,
(const char **) &saslbuf, &saslbuflen);
- if (outbuf)
- free(outbuf);
+ free(outbuf);
if (rc != SASL_OK && rc != SASL_CONTINUE) {
netsec_err(errstr, "SASL client negotiation failed: %s",
return OK;
#else
- NMH_UNUSED(nsc);
+ /*
+ * If we're at this point, then either we have NEITHER OAuth2 or
+ * Cyrus-SASL compiled in, or have OAuth2 but didn't give the XOAUTH2
+ * mechanism on the command line.
+ */
- netsec_err(errstr, "SASL not supported");
+ if (! nsc->sasl_mech)
+ netsec_err(errstr, "SASL library support not available; please "
+ "specify a SASL mechanism to use");
+ else
+ netsec_err(errstr, "No support for the %s SASL mechanism",
+ nsc->sasl_mech);
return NOTOK;
#endif /* CYRUS_SASL */
char *
netsec_get_sasl_mechanism(netsec_context *nsc)
{
-#ifdef CYRUS_SASL
return nsc->sasl_chosen_mech;
-#else /* CYRUS_SASL */
- return NULL;
-#endif /* CYRUS_SASL */
}
/*
nsc->oauth_service = getcpy(service);
return OK;
#else /* OAUTH_SUPPORT */
+ NMH_UNUSED(nsc);
+ NMH_UNUSED(service);
return NOTOK;
#endif /* OAUTH_SUPPORT */
}
*/
int
-netsec_set_tls(netsec_context *nsc, int tls, char **errstr)
+netsec_set_tls(netsec_context *nsc, int tls, int noverify, char **errstr)
{
- if (tls) {
#ifdef TLS_SUPPORT
+ if (tls) {
SSL *ssl;
- BIO *rbio, *wbio, *ssl_bio;;
+ BIO *rbio, *wbio, *ssl_bio;
if (! tls_initialized) {
SSL_library_init();
SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 |
SSL_OP_NO_TLSv1);
+ if (!SSL_CTX_set_default_verify_paths(sslctx)) {
+ netsec_err(errstr, "Unable to set default certificate "
+ "verification paths: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ return NOTOK;
+ }
+
tls_initialized++;
}
* supplied socket.
*
* Then we create an SSL BIO, and assign our current SSL connection
- * to it. We then create a buffer BIO and push it in front of our
- * SSL BIO. So the chain looks like:
- *
- * buffer BIO -> SSL BIO -> socket BIO.
+ * to it. This is done so our code stays simple if we want to use
+ * any buffering BIOs (right now we do our own buffering).
+ * So the chain looks like:
*
- * So writes and reads are buffered (we mostly care about writes).
+ * SSL BIO -> socket BIO.
*/
- rbio = BIO_new_socket(nsc->ns_readfd, BIO_NOCLOSE);
+ rbio = BIO_new_socket(nsc->ns_readfd, BIO_CLOSE);
if (! rbio) {
netsec_err(errstr, "Unable to create a read socket BIO: %s",
return NOTOK;
}
- wbio = BIO_new_socket(nsc->ns_writefd, BIO_NOCLOSE);
+ wbio = BIO_new_socket(nsc->ns_writefd, BIO_CLOSE);
if (! wbio) {
netsec_err(errstr, "Unable to create a write socket BIO: %s",
SSL_set_bio(ssl, rbio, wbio);
SSL_set_connect_state(ssl);
+ /*
+ * If noverify is NOT set, then do certificate validation.
+ * Turning on SSL_VERIFY_PEER will verify the certificate chain
+ * against locally stored root certificates (the locations are
+ * set using SSL_CTX_set_default_verify_paths()), and we put
+ * the hostname in the X509 verification parameters so the OpenSSL
+ * code will verify that the hostname appears in the server
+ * certificate.
+ */
+
+ if (! noverify) {
+#ifdef HAVE_X509_VERIFY_PARAM_SET1_HOST
+ X509_VERIFY_PARAM *param;
+#endif /* HAVE_X509_VERIFY_PARAM_SET1_HOST */
+
+ SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL);
+ if (! nsc->ns_hostname) {
+ netsec_err(errstr, "Internal error: hostname not set and "
+ "certification verification enabled");
+ SSL_free(ssl);
+ return NOTOK;
+ }
+
+#ifdef HAVE_X509_VERIFY_PARAM_SET1_HOST
+ param = SSL_get0_param(ssl);
+
+ if (! X509_VERIFY_PARAM_set1_host(param, nsc->ns_hostname, 0)) {
+ netsec_err(errstr, "Unable to add hostname %s to cert "
+ "verification parameters: %s", nsc->ns_hostname,
+ ERR_error_string(ERR_get_error(), NULL));
+ SSL_free(ssl);
+ return NOTOK;
+ }
+#endif /* HAVE_X509_VERIFY_PARAM_SET1_HOST */
+ }
+
ssl_bio = BIO_new(BIO_f_ssl());
if (! ssl_bio) {
BIO_set_ssl(ssl_bio, ssl, BIO_CLOSE);
nsc->ssl_io = ssl_bio;
- return OK;
- } else {
- BIO_free_all(nsc->ssl_io);
- nsc->ssl_io = NULL;
+ /*
+ * Since SSL now owns these file descriptors, have it handle the
+ * closing of them instead of netsec_shutdown().
+ */
+
+ nsc->ns_noclose = 1;
return OK;
}
+ BIO_free_all(nsc->ssl_io);
+ nsc->ssl_io = NULL;
+
#else /* TLS_SUPPORT */
- netsec_err(errstr, "TLS is not supported");
+ NMH_UNUSED(nsc);
+ NMH_UNUSED(noverify);
+ if (tls) {
+ netsec_err(errstr, "TLS is not supported");
return NOTOK;
}
#endif /* TLS_SUPPORT */
+
+ return OK;
}
/*
}
if (BIO_do_handshake(nsc->ssl_io) < 1) {
- netsec_err(errstr, "TLS negotiation failed: %s",
- ERR_error_string(ERR_get_error(), NULL));
+ unsigned long errcode = ERR_get_error();
+
+ /*
+ * Print a more detailed message if it was certificate verification
+ * failure.
+ */
+
+ if (ERR_GET_LIB(errcode) == ERR_LIB_SSL &&
+ ERR_GET_REASON(errcode) == SSL_R_CERTIFICATE_VERIFY_FAILED) {
+ SSL *ssl;
+
+ if (BIO_get_ssl(nsc->ssl_io, &ssl) < 1) {
+ netsec_err(errstr, "Certificate verification failed, but "
+ "cannot retrieve SSL handle: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ } else {
+ netsec_err(errstr, "Server certificate verification failed: %s",
+ X509_verify_cert_error_string(
+ SSL_get_verify_result(ssl)));
+ }
+ } else {
+ netsec_err(errstr, "TLS negotiation failed: %s",
+ ERR_error_string(errcode, NULL));
+ }
+
+ /*
+ * Because negotiation failed, shut down TLS so we don't get any
+ * garbage on the connection. Because of weirdness with SSL_shutdown,
+ * we end up calling it twice: once explicitly, once as part of
+ * BIO_free_all().
+ */
+
+ BIO_ssl_shutdown(nsc->ssl_io);
+ BIO_free_all(nsc->ssl_io);
+ nsc->ssl_io = NULL;
+
return NOTOK;
}
SSL_CIPHER_get_name(cipher),
SSL_CIPHER_get_bits(cipher, NULL),
SSL_CIPHER_get_version(cipher));
+ SSL_SESSION_print_fp(stderr, SSL_get_session(ssl));
}
}
return OK;
#else /* TLS_SUPPORT */
+ NMH_UNUSED(nsc);
netsec_err(errstr, "TLS not supported");
return NOTOK;