From 0991d42173394e68e989d84e6574476d6a98e571 Mon Sep 17 00:00:00 2001
From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com>
Date: Tue, 9 Jun 2015 14:38:26 -0400
Subject: server: GSSAPI encryption and decryption

---
 src/backend/libpq/Makefile        |   4 ++
 src/backend/libpq/auth.c          |   2 +-
 src/backend/libpq/be-secure-gss.c | 101 ++++++++++++++++++++++++++++++++++++++
 src/backend/libpq/pqcomm.c        |  39 +++++++++++++++
 src/backend/tcop/postgres.c       |  18 ++++++-
 src/backend/utils/misc/guc.c      |  19 +++++++
 src/include/libpq/auth.h          |   5 ++
 src/include/libpq/libpq-be.h      |   9 ++++
 src/include/libpq/libpq.h         |   4 ++
 9 files changed, 199 insertions(+), 2 deletions(-)
 create mode 100644 src/backend/libpq/be-secure-gss.c

diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..359e9d5 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -21,4 +21,8 @@ ifeq ($(with_openssl),yes)
 OBJS += be-secure-openssl.o
 endif
 
+ifeq ($(with_gssapi),yes)
+OBJS += be-secure-gss.o
+endif
+
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 4699efa..913d356 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -728,7 +728,7 @@ static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc;
 #endif
 
 
-static void
+void
 pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
 {
 	gss_buffer_desc gmsg;
diff --git a/src/backend/libpq/be-secure-gss.c b/src/backend/libpq/be-secure-gss.c
new file mode 100644
index 0000000..64a4ed7
--- /dev/null
+++ b/src/backend/libpq/be-secure-gss.c
@@ -0,0 +1,101 @@
+#include <assert.h>
+
+#include "postgres.h"
+
+#include "libpq/libpq.h"
+#include "libpq/auth.h"
+#include "miscadmin.h"
+
+/* GUC value */
+bool gss_encrypt;
+
+size_t
+be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	uint32 len_n;
+	int conf;
+	char *ptr = *((char **)msgptr);
+	char *newbuf = palloc(len + 5);
+
+	len += 4;
+	len_n = htonl(len);
+
+	newbuf[0] = msgtype;
+	memcpy(newbuf + 1, &len_n, 4);
+	memcpy(newbuf + 5, ptr, len - 4);
+
+	input.length = len + 1; /* include type */
+	input.value = newbuf;
+	output.length = 0;
+	output.value = NULL;
+
+	major = gss_wrap(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAULT, &input,
+					 &conf, &output);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error(ERROR, gettext_noop("unwrapping GSS message failed"),
+					 major, minor);
+		return -1;
+	}
+	assert(conf);
+
+	newbuf = repalloc(newbuf, output.length);
+	memcpy(newbuf, output.value, output.length);
+
+	len = output.length;
+	*msgptr = newbuf;
+	gss_release_buffer(&minor, &output);
+
+	return len;
+}
+
+int
+be_gss_inplace_decrypt(StringInfo inBuf)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	int qtype, conf;
+	size_t msglen = 0;
+
+	input.length = inBuf->len;
+	input.value = inBuf->data;
+	output.length = 0;
+	output.value = NULL;
+
+	major = gss_unwrap(&minor, MyProcPort->gss->ctx, &input, &output,
+					   &conf, NULL);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error(ERROR, gettext_noop("wrapping GSS message failed"),
+					 major, minor);
+		return -1;
+	}
+	else if (conf == 0)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("Expected GSSAPI confidentiality but it was not received")));
+		return -1;
+	}
+
+	qtype = ((char *)output.value)[0]; /* first byte is message type */
+	inBuf->len = output.length - 5; /* message starts */
+
+	memcpy((char *)&msglen, ((char *)output.value) + 1, 4);
+	msglen = ntohl(msglen);
+	if (msglen - 4 != inBuf->len)
+	{
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("Length value inside GSSAPI-encrypted packet was malformed")));
+		return -1;
+	}
+
+	memcpy(inBuf->data, ((char *)output.value) + 5, inBuf->len);
+	inBuf->data[inBuf->len] = '\0'; /* invariant */
+	gss_release_buffer(&minor, &output);
+
+	return qtype;
+}
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index a4b37ed..5a929a8 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -1485,6 +1485,19 @@ socket_putmessage(char msgtype, const char *s, size_t len)
 {
 	if (DoingCopyOut || PqCommBusy)
 		return 0;
+
+#ifdef ENABLE_GSS
+	/* Do not wrap auth requests. */
+	if (MyProcPort->hba->auth_method == uaGSS && gss_encrypt &&
+		msgtype != 'R' && msgtype != 'g')
+	{
+		len = be_gss_encrypt(MyProcPort, msgtype, &s, len);
+		if (len < 0)
+			goto fail;
+		msgtype = 'g';
+	}
+#endif
+
 	PqCommBusy = true;
 	if (msgtype)
 		if (internal_putbytes(&msgtype, 1))
@@ -1500,10 +1513,20 @@ socket_putmessage(char msgtype, const char *s, size_t len)
 	if (internal_putbytes(s, len))
 		goto fail;
 	PqCommBusy = false;
+#ifdef ENABLE_GSS
+	/* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */
+	if (msgtype == 'g')
+		pfree((char *)s);
+#endif
 	return 0;
 
 fail:
 	PqCommBusy = false;
+#ifdef ENABLE_GSS
+	/* if we're GSSAPI encrypting, s was allocated in be_gss_encrypt */
+	if (msgtype == 'g')
+		pfree((char *)s);
+#endif
 	return EOF;
 }
 
@@ -1519,6 +1542,22 @@ socket_putmessage_noblock(char msgtype, const char *s, size_t len)
 	int res		PG_USED_FOR_ASSERTS_ONLY;
 	int			required;
 
+#ifdef ENABLE_GSS
+	/*
+	 * Because socket_putmessage is also a front-facing function, we need the
+	 * ability to GSSAPI encrypt from either.  Since socket_putmessage_noblock
+	 * calls into socket_putmessage, socket_putmessage will handle freeing the
+	 * allocated string.
+	 */
+	if (gss_encrypt && msgtype != 'R' && msgtype != 'g')
+	{
+		len = be_gss_encrypt(MyProcPort, msgtype, &s, len);
+		if (len < 0)
+			return;
+		msgtype = 'g';
+	}
+#endif
+
 	/*
 	 * Ensure we have enough space in the output buffer for the message header
 	 * as well as the message itself.
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index ce4bdaf..8510908 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -336,6 +336,7 @@ static int
 SocketBackend(StringInfo inBuf)
 {
 	int			qtype;
+	bool		msg_got = false;
 
 	/*
 	 * Get message type code from the frontend.
@@ -365,6 +366,21 @@ SocketBackend(StringInfo inBuf)
 		return qtype;
 	}
 
+#ifdef ENABLE_GSS
+	else if (qtype == 'g' && gss_encrypt &&
+			 MyProcPort->hba->auth_method == uaGSS)
+	{
+		/* GSSAPI wrapping implies protocol >= 3 */
+		if (pq_getmessage(inBuf, 0))
+			return EOF;
+		msg_got = true;
+
+		qtype = be_gss_inplace_decrypt(inBuf);
+		if (qtype < 0)
+			return EOF;
+	}
+#endif
+
 	/*
 	 * Validate message type code before trying to read body; if we have lost
 	 * sync, better to say "command unknown" than to run out of memory because
@@ -490,7 +506,7 @@ SocketBackend(StringInfo inBuf)
 	 * after the type code; we can read the message contents independently of
 	 * the type.
 	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3 && !msg_got)
 	{
 		if (pq_getmessage(inBuf, 0))
 			return EOF;			/* suitable message already logged */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0356ecb..a978af0 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -186,6 +186,10 @@ static const char *show_log_file_mode(void);
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
 						  bool applySettings, int elevel);
 
+#ifdef ENABLE_GSS
+static void assign_gss_encrypt(bool newval, void *extra);
+#endif
+
 
 /*
  * Options for enum values defined in this module.
@@ -1618,6 +1622,15 @@ static struct config_bool ConfigureNamesBool[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"gss_encrypt", PGC_USERSET, CONN_AUTH_SECURITY,
+		 gettext_noop("Whether client wants encryption for this connection."),
+		 NULL,
+		 GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&gss_encrypt, false, NULL, assign_gss_encrypt, NULL
+	},
+
 	/* End-of-list marker */
 	{
 		{NULL, 0, 0, NULL, NULL}, NULL, false, NULL, NULL, NULL
@@ -10114,4 +10127,10 @@ show_log_file_mode(void)
 	return buf;
 }
 
+static void
+assign_gss_encrypt(bool newval, void *extra)
+{
+	gss_encrypt = newval;
+}
+
 #include "guc-file.c"
diff --git a/src/include/libpq/auth.h b/src/include/libpq/auth.h
index 80f26a8..e98f560 100644
--- a/src/include/libpq/auth.h
+++ b/src/include/libpq/auth.h
@@ -26,4 +26,9 @@ extern void ClientAuthentication(Port *port);
 typedef void (*ClientAuthentication_hook_type) (Port *, int);
 extern PGDLLIMPORT ClientAuthentication_hook_type ClientAuthentication_hook;
 
+#ifdef ENABLE_GSS
+void pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat,
+				  OM_uint32 min_stat);
+#endif
+
 #endif   /* AUTH_H */
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 6171ef3..58712fc 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -30,6 +30,8 @@
 #endif
 
 #ifdef ENABLE_GSS
+#include "lib/stringinfo.h"
+
 #if defined(HAVE_GSSAPI_H)
 #include <gssapi.h>
 #else
@@ -219,6 +221,13 @@ extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
 extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
 #endif
 
+#ifdef ENABLE_GSS
+/* These functions are implemented in be-secure-gss.c */
+extern size_t
+be_gss_encrypt(Port *port, char msgtype, const char **msgptr, size_t len);
+extern int be_gss_inplace_decrypt(StringInfo inBuf);
+#endif
+
 extern ProtocolVersion FrontendProtocol;
 
 /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index c408e5b..e788cc8 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -99,4 +99,8 @@ extern char *SSLCipherSuites;
 extern char *SSLECDHCurve;
 extern bool SSLPreferServerCiphers;
 
+#ifdef ENABLE_GSS
+extern bool gss_encrypt;
+#endif
+
 #endif   /* LIBPQ_H */
-- 
2.1.4

