From 48d50840d53eb62842c0d9b54eab9cd7c9a3a46d Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Mon, 18 Aug 2014 13:04:47 +0300
Subject: [PATCH] Reorganize functions in be-secure-openssl.c

Move the functions within the file so that public interface functions come
first, followed by internal functions. Previously, be_tls_write was first,
then internal stuff, and finally the rest of the public interface, which
clearly didn't make much sense.

Per Andres Freund's complaint.
---
 src/backend/libpq/be-secure-openssl.c | 949 +++++++++++++-------------
 1 file changed, 480 insertions(+), 469 deletions(-)

diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index f7bdcb7f10e..8d8f12952a4 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -75,12 +75,17 @@
 #include "utils/memutils.h"
 
 
+static int my_sock_read(BIO *h, char *buf, int size);
+static int my_sock_write(BIO *h, const char *buf, int size);
+static BIO_METHOD *my_BIO_s_socket(void);
+static int my_SSL_set_fd(Port *port, int fd);
 
 static DH  *load_dh_file(int keylength);
 static DH  *load_dh_buffer(const char *, size_t);
 static DH  *tmp_dh_cb(SSL *s, int is_export, int keylength);
 static int	verify_cb(int, X509_STORE_CTX *);
 static void info_cb(const SSL *ssl, int type, int args);
+static void initialize_ecdh(void);
 static const char *SSLerrmessage(void);
 
 /* are we in the middle of a renegotiation? */
@@ -153,479 +158,11 @@ AaqLulO7R8Ifa1SwF2DteSGVtgWEN8gDpN3RBmmPTDngyF2DHb5qmpnznwtFKdTL\n\
 KWbuHn491xNO25CQWMtem80uKw+pTnisBRF/454n1Jnhub144YRBoN8CAQI=\n\
 -----END DH PARAMETERS-----\n";
 
-/*
- *	Write data to a secure connection.
- */
-ssize_t
-be_tls_write(Port *port, void *ptr, size_t len)
-{
-	ssize_t		n;
-	int			err;
-
-	/*
-	 * If SSL renegotiations are enabled and we're getting close to the
-	 * limit, start one now; but avoid it if there's one already in
-	 * progress.  Request the renegotiation 1kB before the limit has
-	 * actually expired.
-	 */
-	if (ssl_renegotiation_limit && !in_ssl_renegotiation &&
-		port->count > (ssl_renegotiation_limit - 1) * 1024L)
-	{
-		in_ssl_renegotiation = true;
-
-		/*
-		 * The way we determine that a renegotiation has completed is by
-		 * observing OpenSSL's internal renegotiation counter.  Make sure
-		 * we start out at zero, and assume that the renegotiation is
-		 * complete when the counter advances.
-		 *
-		 * OpenSSL provides SSL_renegotiation_pending(), but this doesn't
-		 * seem to work in testing.
-		 */
-		SSL_clear_num_renegotiations(port->ssl);
-
-		SSL_set_session_id_context(port->ssl, (void *) &SSL_context,
-								   sizeof(SSL_context));
-		if (SSL_renegotiate(port->ssl) <= 0)
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("SSL failure during renegotiation start")));
-		else
-		{
-			int			retries;
-
-			/*
-			 * A handshake can fail, so be prepared to retry it, but only
-			 * a few times.
-			 */
-			for (retries = 0;; retries++)
-			{
-				if (SSL_do_handshake(port->ssl) > 0)
-					break;	/* done */
-				ereport(COMMERROR,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("SSL handshake failure on renegotiation, retrying")));
-				if (retries >= 20)
-					ereport(FATAL,
-							(errcode(ERRCODE_PROTOCOL_VIOLATION),
-							 errmsg("unable to complete SSL handshake")));
-			}
-		}
-	}
-
-wloop:
-	errno = 0;
-	n = SSL_write(port->ssl, ptr, len);
-	err = SSL_get_error(port->ssl, n);
-	switch (err)
-	{
-		case SSL_ERROR_NONE:
-			port->count += n;
-			break;
-		case SSL_ERROR_WANT_READ:
-		case SSL_ERROR_WANT_WRITE:
-#ifdef WIN32
-			pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
-										(err == SSL_ERROR_WANT_READ) ?
-									FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE,
-										INFINITE);
-#endif
-			goto wloop;
-		case SSL_ERROR_SYSCALL:
-			/* leave it to caller to ereport the value of errno */
-			if (n != -1)
-			{
-				errno = ECONNRESET;
-				n = -1;
-			}
-			break;
-		case SSL_ERROR_SSL:
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("SSL error: %s", SSLerrmessage())));
-			/* fall through */
-		case SSL_ERROR_ZERO_RETURN:
-			errno = ECONNRESET;
-			n = -1;
-			break;
-		default:
-			ereport(COMMERROR,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("unrecognized SSL error code: %d",
-							err)));
-			errno = ECONNRESET;
-			n = -1;
-			break;
-	}
-
-	if (n >= 0)
-	{
-		/* is renegotiation complete? */
-		if (in_ssl_renegotiation &&
-			SSL_num_renegotiations(port->ssl) >= 1)
-		{
-			in_ssl_renegotiation = false;
-			port->count = 0;
-		}
-
-		/*
-		 * if renegotiation is still ongoing, and we've gone beyond the
-		 * limit, kill the connection now -- continuing to use it can be
-		 * considered a security problem.
-		 */
-		if (in_ssl_renegotiation &&
-			port->count > ssl_renegotiation_limit * 1024L)
-			ereport(FATAL,
-					(errcode(ERRCODE_PROTOCOL_VIOLATION),
-					 errmsg("SSL failed to renegotiate connection before limit expired")));
-	}
-
-	return n;
-}
 
 /* ------------------------------------------------------------ */
-/*						OpenSSL specific code					*/
+/*						 Public interface						*/
 /* ------------------------------------------------------------ */
 
-/*
- * Private substitute BIO: this does the sending and receiving using send() and
- * recv() instead. This is so that we can enable and disable interrupts
- * just while calling recv(). We cannot have interrupts occurring while
- * the bulk of openssl runs, because it uses malloc() and possibly other
- * non-reentrant libc facilities. We also need to call send() and recv()
- * directly so it gets passed through the socket/signals layer on Win32.
- *
- * These functions are closely modelled on the standard socket BIO in OpenSSL;
- * see sock_read() and sock_write() in OpenSSL's crypto/bio/bss_sock.c.
- * XXX OpenSSL 1.0.1e considers many more errcodes than just EINTR as reasons
- * to retry; do we need to adopt their logic for that?
- */
-
-static bool my_bio_initialized = false;
-static BIO_METHOD my_bio_methods;
-
-static int
-my_sock_read(BIO *h, char *buf, int size)
-{
-	int			res = 0;
-
-	if (buf != NULL)
-	{
-		res = secure_raw_read(((Port *)h->ptr), buf, size);
-		BIO_clear_retry_flags(h);
-		if (res <= 0)
-		{
-			/* If we were interrupted, tell caller to retry */
-			if (errno == EINTR)
-			{
-				BIO_set_retry_read(h);
-			}
-		}
-	}
-
-	return res;
-}
-
-static int
-my_sock_write(BIO *h, const char *buf, int size)
-{
-	int			res = 0;
-
-	res = secure_raw_write(((Port *) h->ptr), buf, size);
-	BIO_clear_retry_flags(h);
-	if (res <= 0)
-	{
-		if (errno == EINTR)
-		{
-			BIO_set_retry_write(h);
-		}
-	}
-
-	return res;
-}
-
-static BIO_METHOD *
-my_BIO_s_socket(void)
-{
-	if (!my_bio_initialized)
-	{
-		memcpy(&my_bio_methods, BIO_s_socket(), sizeof(BIO_METHOD));
-		my_bio_methods.bread = my_sock_read;
-		my_bio_methods.bwrite = my_sock_write;
-		my_bio_initialized = true;
-	}
-	return &my_bio_methods;
-}
-
-/* This should exactly match openssl's SSL_set_fd except for using my BIO */
-static int
-my_SSL_set_fd(Port *port, int fd)
-{
-	int			ret = 0;
-	BIO		   *bio = NULL;
-
-	bio = BIO_new(my_BIO_s_socket());
-
-	if (bio == NULL)
-	{
-		SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
-		goto err;
-	}
-	/* Use 'ptr' to store pointer to PGconn */
-	bio->ptr = port;
-
-	BIO_set_fd(bio, fd, BIO_NOCLOSE);
-	SSL_set_bio(port->ssl, bio, bio);
-	ret = 1;
-err:
-	return ret;
-}
-
-/*
- *	Load precomputed DH parameters.
- *
- *	To prevent "downgrade" attacks, we perform a number of checks
- *	to verify that the DBA-generated DH parameters file contains
- *	what we expect it to contain.
- */
-static DH  *
-load_dh_file(int keylength)
-{
-	FILE	   *fp;
-	char		fnbuf[MAXPGPATH];
-	DH		   *dh = NULL;
-	int			codes;
-
-	/* attempt to open file.  It's not an error if it doesn't exist. */
-	snprintf(fnbuf, sizeof(fnbuf), "dh%d.pem", keylength);
-	if ((fp = fopen(fnbuf, "r")) == NULL)
-		return NULL;
-
-/*	flock(fileno(fp), LOCK_SH); */
-	dh = PEM_read_DHparams(fp, NULL, NULL, NULL);
-/*	flock(fileno(fp), LOCK_UN); */
-	fclose(fp);
-
-	/* is the prime the correct size? */
-	if (dh != NULL && 8 * DH_size(dh) < keylength)
-	{
-		elog(LOG, "DH errors (%s): %d bits expected, %d bits found",
-			 fnbuf, keylength, 8 * DH_size(dh));
-		dh = NULL;
-	}
-
-	/* make sure the DH parameters are usable */
-	if (dh != NULL)
-	{
-		if (DH_check(dh, &codes) == 0)
-		{
-			elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage());
-			return NULL;
-		}
-		if (codes & DH_CHECK_P_NOT_PRIME)
-		{
-			elog(LOG, "DH error (%s): p is not prime", fnbuf);
-			return NULL;
-		}
-		if ((codes & DH_NOT_SUITABLE_GENERATOR) &&
-			(codes & DH_CHECK_P_NOT_SAFE_PRIME))
-		{
-			elog(LOG,
-				 "DH error (%s): neither suitable generator or safe prime",
-				 fnbuf);
-			return NULL;
-		}
-	}
-
-	return dh;
-}
-
-/*
- *	Load hardcoded DH parameters.
- *
- *	To prevent problems if the DH parameters files don't even
- *	exist, we can load DH parameters hardcoded into this file.
- */
-static DH  *
-load_dh_buffer(const char *buffer, size_t len)
-{
-	BIO		   *bio;
-	DH		   *dh = NULL;
-
-	bio = BIO_new_mem_buf((char *) buffer, len);
-	if (bio == NULL)
-		return NULL;
-	dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
-	if (dh == NULL)
-		ereport(DEBUG2,
-				(errmsg_internal("DH load buffer: %s",
-								 SSLerrmessage())));
-	BIO_free(bio);
-
-	return dh;
-}
-
-/*
- *	Generate an ephemeral DH key.  Because this can take a long
- *	time to compute, we can use precomputed parameters of the
- *	common key sizes.
- *
- *	Since few sites will bother to precompute these parameter
- *	files, we also provide a fallback to the parameters provided
- *	by the OpenSSL project.
- *
- *	These values can be static (once loaded or computed) since
- *	the OpenSSL library can efficiently generate random keys from
- *	the information provided.
- */
-static DH  *
-tmp_dh_cb(SSL *s, int is_export, int keylength)
-{
-	DH		   *r = NULL;
-	static DH  *dh = NULL;
-	static DH  *dh512 = NULL;
-	static DH  *dh1024 = NULL;
-	static DH  *dh2048 = NULL;
-	static DH  *dh4096 = NULL;
-
-	switch (keylength)
-	{
-		case 512:
-			if (dh512 == NULL)
-				dh512 = load_dh_file(keylength);
-			if (dh512 == NULL)
-				dh512 = load_dh_buffer(file_dh512, sizeof file_dh512);
-			r = dh512;
-			break;
-
-		case 1024:
-			if (dh1024 == NULL)
-				dh1024 = load_dh_file(keylength);
-			if (dh1024 == NULL)
-				dh1024 = load_dh_buffer(file_dh1024, sizeof file_dh1024);
-			r = dh1024;
-			break;
-
-		case 2048:
-			if (dh2048 == NULL)
-				dh2048 = load_dh_file(keylength);
-			if (dh2048 == NULL)
-				dh2048 = load_dh_buffer(file_dh2048, sizeof file_dh2048);
-			r = dh2048;
-			break;
-
-		case 4096:
-			if (dh4096 == NULL)
-				dh4096 = load_dh_file(keylength);
-			if (dh4096 == NULL)
-				dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096);
-			r = dh4096;
-			break;
-
-		default:
-			if (dh == NULL)
-				dh = load_dh_file(keylength);
-			r = dh;
-	}
-
-	/* this may take a long time, but it may be necessary... */
-	if (r == NULL || 8 * DH_size(r) < keylength)
-	{
-		ereport(DEBUG2,
-				(errmsg_internal("DH: generating parameters (%d bits)",
-								 keylength)));
-		r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL);
-	}
-
-	return r;
-}
-
-/*
- *	Certificate verification callback
- *
- *	This callback allows us to log intermediate problems during
- *	verification, but for now we'll see if the final error message
- *	contains enough information.
- *
- *	This callback also allows us to override the default acceptance
- *	criteria (e.g., accepting self-signed or expired certs), but
- *	for now we accept the default checks.
- */
-static int
-verify_cb(int ok, X509_STORE_CTX *ctx)
-{
-	return ok;
-}
-
-/*
- *	This callback is used to copy SSL information messages
- *	into the PostgreSQL log.
- */
-static void
-info_cb(const SSL *ssl, int type, int args)
-{
-	switch (type)
-	{
-		case SSL_CB_HANDSHAKE_START:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: handshake start")));
-			break;
-		case SSL_CB_HANDSHAKE_DONE:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: handshake done")));
-			break;
-		case SSL_CB_ACCEPT_LOOP:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: accept loop")));
-			break;
-		case SSL_CB_ACCEPT_EXIT:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: accept exit (%d)", args)));
-			break;
-		case SSL_CB_CONNECT_LOOP:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: connect loop")));
-			break;
-		case SSL_CB_CONNECT_EXIT:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: connect exit (%d)", args)));
-			break;
-		case SSL_CB_READ_ALERT:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: read alert (0x%04x)", args)));
-			break;
-		case SSL_CB_WRITE_ALERT:
-			ereport(DEBUG4,
-					(errmsg_internal("SSL: write alert (0x%04x)", args)));
-			break;
-	}
-}
-
-#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_ECDH)
-static void
-initialize_ecdh(void)
-{
-	EC_KEY	   *ecdh;
-	int			nid;
-
-	nid = OBJ_sn2nid(SSLECDHCurve);
-	if (!nid)
-		ereport(FATAL,
-				(errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
-
-	ecdh = EC_KEY_new_by_curve_name(nid);
-	if (!ecdh)
-		ereport(FATAL,
-				(errmsg("ECDH: could not create key")));
-
-	SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_ECDH_USE);
-	SSL_CTX_set_tmp_ecdh(SSL_context, ecdh);
-	EC_KEY_free(ecdh);
-}
-#else
-#define initialize_ecdh()
-#endif
-
 /*
  *	Initialize global SSL context.
  */
@@ -959,6 +496,9 @@ be_tls_close(Port *port)
 	}
 }
 
+/*
+ *	Read data from a secure connection.
+ */
 ssize_t
 be_tls_read(Port *port, void *ptr, size_t len)
 {
@@ -1019,6 +559,477 @@ rloop:
 	return n;
 }
 
+/*
+ *	Write data to a secure connection.
+ */
+ssize_t
+be_tls_write(Port *port, void *ptr, size_t len)
+{
+	ssize_t		n;
+	int			err;
+
+	/*
+	 * If SSL renegotiations are enabled and we're getting close to the
+	 * limit, start one now; but avoid it if there's one already in
+	 * progress.  Request the renegotiation 1kB before the limit has
+	 * actually expired.
+	 */
+	if (ssl_renegotiation_limit && !in_ssl_renegotiation &&
+		port->count > (ssl_renegotiation_limit - 1) * 1024L)
+	{
+		in_ssl_renegotiation = true;
+
+		/*
+		 * The way we determine that a renegotiation has completed is by
+		 * observing OpenSSL's internal renegotiation counter.  Make sure
+		 * we start out at zero, and assume that the renegotiation is
+		 * complete when the counter advances.
+		 *
+		 * OpenSSL provides SSL_renegotiation_pending(), but this doesn't
+		 * seem to work in testing.
+		 */
+		SSL_clear_num_renegotiations(port->ssl);
+
+		SSL_set_session_id_context(port->ssl, (void *) &SSL_context,
+								   sizeof(SSL_context));
+		if (SSL_renegotiate(port->ssl) <= 0)
+			ereport(COMMERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("SSL failure during renegotiation start")));
+		else
+		{
+			int			retries;
+
+			/*
+			 * A handshake can fail, so be prepared to retry it, but only
+			 * a few times.
+			 */
+			for (retries = 0;; retries++)
+			{
+				if (SSL_do_handshake(port->ssl) > 0)
+					break;	/* done */
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("SSL handshake failure on renegotiation, retrying")));
+				if (retries >= 20)
+					ereport(FATAL,
+							(errcode(ERRCODE_PROTOCOL_VIOLATION),
+							 errmsg("unable to complete SSL handshake")));
+			}
+		}
+	}
+
+wloop:
+	errno = 0;
+	n = SSL_write(port->ssl, ptr, len);
+	err = SSL_get_error(port->ssl, n);
+	switch (err)
+	{
+		case SSL_ERROR_NONE:
+			port->count += n;
+			break;
+		case SSL_ERROR_WANT_READ:
+		case SSL_ERROR_WANT_WRITE:
+#ifdef WIN32
+			pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
+										(err == SSL_ERROR_WANT_READ) ?
+									FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE,
+										INFINITE);
+#endif
+			goto wloop;
+		case SSL_ERROR_SYSCALL:
+			/* leave it to caller to ereport the value of errno */
+			if (n != -1)
+			{
+				errno = ECONNRESET;
+				n = -1;
+			}
+			break;
+		case SSL_ERROR_SSL:
+			ereport(COMMERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("SSL error: %s", SSLerrmessage())));
+			/* fall through */
+		case SSL_ERROR_ZERO_RETURN:
+			errno = ECONNRESET;
+			n = -1;
+			break;
+		default:
+			ereport(COMMERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("unrecognized SSL error code: %d",
+							err)));
+			errno = ECONNRESET;
+			n = -1;
+			break;
+	}
+
+	if (n >= 0)
+	{
+		/* is renegotiation complete? */
+		if (in_ssl_renegotiation &&
+			SSL_num_renegotiations(port->ssl) >= 1)
+		{
+			in_ssl_renegotiation = false;
+			port->count = 0;
+		}
+
+		/*
+		 * if renegotiation is still ongoing, and we've gone beyond the
+		 * limit, kill the connection now -- continuing to use it can be
+		 * considered a security problem.
+		 */
+		if (in_ssl_renegotiation &&
+			port->count > ssl_renegotiation_limit * 1024L)
+			ereport(FATAL,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("SSL failed to renegotiate connection before limit expired")));
+	}
+
+	return n;
+}
+
+/* ------------------------------------------------------------ */
+/*						Internal functions						*/
+/* ------------------------------------------------------------ */
+
+/*
+ * Private substitute BIO: this does the sending and receiving using send() and
+ * recv() instead. This is so that we can enable and disable interrupts
+ * just while calling recv(). We cannot have interrupts occurring while
+ * the bulk of openssl runs, because it uses malloc() and possibly other
+ * non-reentrant libc facilities. We also need to call send() and recv()
+ * directly so it gets passed through the socket/signals layer on Win32.
+ *
+ * These functions are closely modelled on the standard socket BIO in OpenSSL;
+ * see sock_read() and sock_write() in OpenSSL's crypto/bio/bss_sock.c.
+ * XXX OpenSSL 1.0.1e considers many more errcodes than just EINTR as reasons
+ * to retry; do we need to adopt their logic for that?
+ */
+
+static bool my_bio_initialized = false;
+static BIO_METHOD my_bio_methods;
+
+static int
+my_sock_read(BIO *h, char *buf, int size)
+{
+	int			res = 0;
+
+	if (buf != NULL)
+	{
+		res = secure_raw_read(((Port *)h->ptr), buf, size);
+		BIO_clear_retry_flags(h);
+		if (res <= 0)
+		{
+			/* If we were interrupted, tell caller to retry */
+			if (errno == EINTR)
+			{
+				BIO_set_retry_read(h);
+			}
+		}
+	}
+
+	return res;
+}
+
+static int
+my_sock_write(BIO *h, const char *buf, int size)
+{
+	int			res = 0;
+
+	res = secure_raw_write(((Port *) h->ptr), buf, size);
+	BIO_clear_retry_flags(h);
+	if (res <= 0)
+	{
+		if (errno == EINTR)
+		{
+			BIO_set_retry_write(h);
+		}
+	}
+
+	return res;
+}
+
+static BIO_METHOD *
+my_BIO_s_socket(void)
+{
+	if (!my_bio_initialized)
+	{
+		memcpy(&my_bio_methods, BIO_s_socket(), sizeof(BIO_METHOD));
+		my_bio_methods.bread = my_sock_read;
+		my_bio_methods.bwrite = my_sock_write;
+		my_bio_initialized = true;
+	}
+	return &my_bio_methods;
+}
+
+/* This should exactly match openssl's SSL_set_fd except for using my BIO */
+static int
+my_SSL_set_fd(Port *port, int fd)
+{
+	int			ret = 0;
+	BIO		   *bio = NULL;
+
+	bio = BIO_new(my_BIO_s_socket());
+
+	if (bio == NULL)
+	{
+		SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
+		goto err;
+	}
+	/* Use 'ptr' to store pointer to PGconn */
+	bio->ptr = port;
+
+	BIO_set_fd(bio, fd, BIO_NOCLOSE);
+	SSL_set_bio(port->ssl, bio, bio);
+	ret = 1;
+err:
+	return ret;
+}
+
+/*
+ *	Load precomputed DH parameters.
+ *
+ *	To prevent "downgrade" attacks, we perform a number of checks
+ *	to verify that the DBA-generated DH parameters file contains
+ *	what we expect it to contain.
+ */
+static DH  *
+load_dh_file(int keylength)
+{
+	FILE	   *fp;
+	char		fnbuf[MAXPGPATH];
+	DH		   *dh = NULL;
+	int			codes;
+
+	/* attempt to open file.  It's not an error if it doesn't exist. */
+	snprintf(fnbuf, sizeof(fnbuf), "dh%d.pem", keylength);
+	if ((fp = fopen(fnbuf, "r")) == NULL)
+		return NULL;
+
+/*	flock(fileno(fp), LOCK_SH); */
+	dh = PEM_read_DHparams(fp, NULL, NULL, NULL);
+/*	flock(fileno(fp), LOCK_UN); */
+	fclose(fp);
+
+	/* is the prime the correct size? */
+	if (dh != NULL && 8 * DH_size(dh) < keylength)
+	{
+		elog(LOG, "DH errors (%s): %d bits expected, %d bits found",
+			 fnbuf, keylength, 8 * DH_size(dh));
+		dh = NULL;
+	}
+
+	/* make sure the DH parameters are usable */
+	if (dh != NULL)
+	{
+		if (DH_check(dh, &codes) == 0)
+		{
+			elog(LOG, "DH_check error (%s): %s", fnbuf, SSLerrmessage());
+			return NULL;
+		}
+		if (codes & DH_CHECK_P_NOT_PRIME)
+		{
+			elog(LOG, "DH error (%s): p is not prime", fnbuf);
+			return NULL;
+		}
+		if ((codes & DH_NOT_SUITABLE_GENERATOR) &&
+			(codes & DH_CHECK_P_NOT_SAFE_PRIME))
+		{
+			elog(LOG,
+				 "DH error (%s): neither suitable generator or safe prime",
+				 fnbuf);
+			return NULL;
+		}
+	}
+
+	return dh;
+}
+
+/*
+ *	Load hardcoded DH parameters.
+ *
+ *	To prevent problems if the DH parameters files don't even
+ *	exist, we can load DH parameters hardcoded into this file.
+ */
+static DH  *
+load_dh_buffer(const char *buffer, size_t len)
+{
+	BIO		   *bio;
+	DH		   *dh = NULL;
+
+	bio = BIO_new_mem_buf((char *) buffer, len);
+	if (bio == NULL)
+		return NULL;
+	dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+	if (dh == NULL)
+		ereport(DEBUG2,
+				(errmsg_internal("DH load buffer: %s",
+								 SSLerrmessage())));
+	BIO_free(bio);
+
+	return dh;
+}
+
+/*
+ *	Generate an ephemeral DH key.  Because this can take a long
+ *	time to compute, we can use precomputed parameters of the
+ *	common key sizes.
+ *
+ *	Since few sites will bother to precompute these parameter
+ *	files, we also provide a fallback to the parameters provided
+ *	by the OpenSSL project.
+ *
+ *	These values can be static (once loaded or computed) since
+ *	the OpenSSL library can efficiently generate random keys from
+ *	the information provided.
+ */
+static DH  *
+tmp_dh_cb(SSL *s, int is_export, int keylength)
+{
+	DH		   *r = NULL;
+	static DH  *dh = NULL;
+	static DH  *dh512 = NULL;
+	static DH  *dh1024 = NULL;
+	static DH  *dh2048 = NULL;
+	static DH  *dh4096 = NULL;
+
+	switch (keylength)
+	{
+		case 512:
+			if (dh512 == NULL)
+				dh512 = load_dh_file(keylength);
+			if (dh512 == NULL)
+				dh512 = load_dh_buffer(file_dh512, sizeof file_dh512);
+			r = dh512;
+			break;
+
+		case 1024:
+			if (dh1024 == NULL)
+				dh1024 = load_dh_file(keylength);
+			if (dh1024 == NULL)
+				dh1024 = load_dh_buffer(file_dh1024, sizeof file_dh1024);
+			r = dh1024;
+			break;
+
+		case 2048:
+			if (dh2048 == NULL)
+				dh2048 = load_dh_file(keylength);
+			if (dh2048 == NULL)
+				dh2048 = load_dh_buffer(file_dh2048, sizeof file_dh2048);
+			r = dh2048;
+			break;
+
+		case 4096:
+			if (dh4096 == NULL)
+				dh4096 = load_dh_file(keylength);
+			if (dh4096 == NULL)
+				dh4096 = load_dh_buffer(file_dh4096, sizeof file_dh4096);
+			r = dh4096;
+			break;
+
+		default:
+			if (dh == NULL)
+				dh = load_dh_file(keylength);
+			r = dh;
+	}
+
+	/* this may take a long time, but it may be necessary... */
+	if (r == NULL || 8 * DH_size(r) < keylength)
+	{
+		ereport(DEBUG2,
+				(errmsg_internal("DH: generating parameters (%d bits)",
+								 keylength)));
+		r = DH_generate_parameters(keylength, DH_GENERATOR_2, NULL, NULL);
+	}
+
+	return r;
+}
+
+/*
+ *	Certificate verification callback
+ *
+ *	This callback allows us to log intermediate problems during
+ *	verification, but for now we'll see if the final error message
+ *	contains enough information.
+ *
+ *	This callback also allows us to override the default acceptance
+ *	criteria (e.g., accepting self-signed or expired certs), but
+ *	for now we accept the default checks.
+ */
+static int
+verify_cb(int ok, X509_STORE_CTX *ctx)
+{
+	return ok;
+}
+
+/*
+ *	This callback is used to copy SSL information messages
+ *	into the PostgreSQL log.
+ */
+static void
+info_cb(const SSL *ssl, int type, int args)
+{
+	switch (type)
+	{
+		case SSL_CB_HANDSHAKE_START:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: handshake start")));
+			break;
+		case SSL_CB_HANDSHAKE_DONE:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: handshake done")));
+			break;
+		case SSL_CB_ACCEPT_LOOP:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: accept loop")));
+			break;
+		case SSL_CB_ACCEPT_EXIT:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: accept exit (%d)", args)));
+			break;
+		case SSL_CB_CONNECT_LOOP:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: connect loop")));
+			break;
+		case SSL_CB_CONNECT_EXIT:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: connect exit (%d)", args)));
+			break;
+		case SSL_CB_READ_ALERT:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: read alert (0x%04x)", args)));
+			break;
+		case SSL_CB_WRITE_ALERT:
+			ereport(DEBUG4,
+					(errmsg_internal("SSL: write alert (0x%04x)", args)));
+			break;
+	}
+}
+
+static void
+initialize_ecdh(void)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_ECDH)
+	EC_KEY	   *ecdh;
+	int			nid;
+
+	nid = OBJ_sn2nid(SSLECDHCurve);
+	if (!nid)
+		ereport(FATAL,
+				(errmsg("ECDH: unrecognized curve name: %s", SSLECDHCurve)));
+
+	ecdh = EC_KEY_new_by_curve_name(nid);
+	if (!ecdh)
+		ereport(FATAL,
+				(errmsg("ECDH: could not create key")));
+
+	SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_ECDH_USE);
+	SSL_CTX_set_tmp_ecdh(SSL_context, ecdh);
+	EC_KEY_free(ecdh);
+#endif
+}
+
 /*
  * Obtain reason string for last SSL error
  *