From 36bd232742eb2b920d2cd88dd06176cde7e26cb2 Mon Sep 17 00:00:00 2001
From: "Robbie Harwood (frozencemetery)" <rharwood@redhat.com>
Date: Mon, 8 Jun 2015 21:20:23 -0400
Subject: client: GSSAPI encryption and decryption

---
 src/interfaces/libpq/Makefile        |  4 ++
 src/interfaces/libpq/fe-auth.c       |  2 +-
 src/interfaces/libpq/fe-auth.h       |  5 ++
 src/interfaces/libpq/fe-connect.c    |  5 ++
 src/interfaces/libpq/fe-misc.c       |  5 ++
 src/interfaces/libpq/fe-protocol3.c  | 27 +++++++++++
 src/interfaces/libpq/fe-secure-gss.c | 92 ++++++++++++++++++++++++++++++++++++
 src/interfaces/libpq/libpq-int.h     |  7 +++
 8 files changed, 146 insertions(+), 1 deletion(-)
 create mode 100644 src/interfaces/libpq/fe-secure-gss.c

diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c2105f1..a9fb194 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -48,6 +48,10 @@ ifeq ($(with_openssl),yes)
 OBJS += fe-secure-openssl.o
 endif
 
+ifeq ($(with_gssapi),yes)
+OBJS += fe-secure-gss.o
+endif
+
 ifeq ($(PORTNAME), cygwin)
 override shlib = cyg$(NAME)$(DLSUFFIX)
 endif
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 5891c75..af6dfff 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -82,7 +82,7 @@ pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
 /*
  * GSSAPI errors contain two parts; put both into conn->errorMessage.
  */
-static void
+void
 pg_GSS_error(const char *mprefix, PGconn *conn,
 			 OM_uint32 maj_stat, OM_uint32 min_stat)
 {
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 8d35767..5702a2d 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -21,4 +21,9 @@
 extern int	pg_fe_sendauth(AuthRequest areq, PGconn *conn);
 extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
 
+#ifdef ENABLE_GSS
+void pg_GSS_error(const char *mprefix, PGconn *conn, OM_uint32 maj_stat,
+				  OM_uint32 min_stat);
+#endif
+
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index c6c551a..8fb0a90 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2513,6 +2513,11 @@ keep_going:						/* We will come back to here until there is
 					/* We are done with authentication exchange */
 					conn->status = CONNECTION_AUTH_OK;
 
+#ifdef ENABLE_GSS
+					if (conn->gctx != 0)
+						conn->gss_auth_done = true;
+#endif
+
 					/*
 					 * Set asyncStatus so that PQgetResult will think that
 					 * what comes back next is the result of a query.  See
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 0dbcf73..2379aff 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -604,6 +604,11 @@ pqPutMsgEnd(PGconn *conn)
 		memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4);
 	}
 
+#ifdef ENABLE_GSS
+	if (pggss_encrypt(conn) < 0)
+		return EOF;
+#endif
+
 	/* Make message eligible to send */
 	conn->outCount = conn->outMsgEnd;
 
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 0deaa0f..65acfd1 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -129,6 +129,33 @@ pqParseInput3(PGconn *conn)
 			return;
 		}
 
+#ifdef ENABLE_GSS
+		/* We want to be ready in both IDLE and BUSY states for encryption */
+		if (id == 'g')
+		{
+			ssize_t encEnd, next;
+
+			encEnd = pggss_inplace_decrypt(conn, msgLength);
+			if (encEnd <= 0)
+			{
+				/* error message placed by pggss_inplace_decrypt() */
+				pqSaveErrorResult(conn);
+				conn->asyncStatus = PGASYNC_READY;
+				pqDropConnection(conn);
+				conn->status = CONNECTION_BAD;
+				return;
+			}
+
+			/* shift contents of buffer to account for slack */
+			encEnd += conn->inStart;
+			next = conn->inStart + msgLength + 5;
+			memmove(conn->inBuffer + encEnd, conn->inBuffer + next,
+					conn->inEnd - next);
+			conn->inEnd = (conn->inEnd - next) + encEnd;
+			continue;
+		}
+#endif
+
 		/*
 		 * NOTIFY and NOTICE messages can happen in any state; always process
 		 * them right away.
diff --git a/src/interfaces/libpq/fe-secure-gss.c b/src/interfaces/libpq/fe-secure-gss.c
new file mode 100644
index 0000000..afea9c3
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gss.c
@@ -0,0 +1,92 @@
+#include <assert.h>
+
+#include "libpq-fe.h"
+#include "postgres_fe.h"
+#include "fe-auth.h"
+#include "libpq-int.h"
+
+ssize_t
+pggss_inplace_decrypt(PGconn *conn, int gsslen)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	ssize_t n;
+	int conf;
+
+	input.length = gsslen;
+	input.value = conn->inBuffer + conn->inCursor;
+	output.length = 0;
+	output.value = NULL;
+
+	major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error("GSSAPI unwrap error", conn, major, minor);
+		return -1;
+	}
+	else if (conf == 0)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext(
+							  "received GSSAPI message without confidentiality\n"));
+		return -1;
+	}
+
+	memcpy(conn->inBuffer + conn->inStart, output.value, output.length);
+	n = output.length;
+	gss_release_buffer(&minor, &output);
+	return n;
+}
+
+int
+pggss_encrypt(PGconn *conn)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	int msgLen, conf;
+	uint32 len_n;
+
+	if (conn->gss_disable_enc || !conn->gctx || !conn->gss_auth_done)
+		return 0;
+	assert(conn->outMsgStart > 0);
+
+	/* We need to encrypt message type as well */
+	conn->outMsgStart -= 1;
+	msgLen = conn->outMsgEnd - conn->outMsgStart;
+
+	input.value = conn->outBuffer + conn->outMsgStart;
+	input.length = msgLen;
+	output.length = 0;
+	output.value = NULL;
+
+	major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT, &input, &conf,
+					 &output);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error("GSSAPI wrap error", conn, major, minor);
+		return -1;
+	}
+	else if (conf == 0)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext(
+							  "Failed to obtain confidentiality for outgoing GSSAPI message\n"));
+		return -1;
+	}
+
+	msgLen = output.length + 4;
+	if (pqCheckOutBufferSpace(conn->outMsgStart + msgLen + 1, conn))
+		return -1;
+	
+	conn->outBuffer[conn->outMsgStart] = 'g'; /* GSSAPI message */
+
+	len_n = htonl(msgLen);
+	memcpy(conn->outBuffer + conn->outMsgStart + 1, &len_n, 4);
+
+	memcpy(conn->outBuffer + conn->outMsgStart + 1 + 4,
+			 output.value, output.length);
+	conn->outMsgEnd = conn->outMsgStart + msgLen + 1;
+
+	gss_release_buffer(&minor, &output);
+	return msgLen + 1;
+}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1578d76..ff2e39d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -446,6 +446,7 @@ struct pg_conn
 	gss_buffer_desc ginbuf;		/* GSS input token */
 	gss_buffer_desc goutbuf;	/* GSS output token */
 	bool gss_disable_enc;		/* Does server recognize gss_encrypt? */
+	bool gss_auth_done;			/* Did we finish the AUTH step? */
 #endif
 
 #ifdef ENABLE_SSPI
@@ -643,6 +644,12 @@ extern bool pgtls_read_pending(PGconn *conn);
 extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
 
 /*
+ * GSSAPI encryption functions defined in fe-secure-gss.c
+ */
+extern ssize_t pggss_inplace_decrypt(PGconn *conn, int gsslen);
+extern int pggss_encrypt(PGconn *conn);
+
+/*
  * this is so that we can check if a connection is non-blocking internally
  * without the overhead of a function call
  */
-- 
2.1.4

