From c92275b6605d7929cda5551de47a4c60aab7179e Mon Sep 17 00:00:00 2001
From: Robbie Harwood <rharwood@redhat.com>
Date: Tue, 17 Nov 2015 18:34:14 -0500
Subject: [PATCH] Connect encryption support for GSSAPI

Existing GSSAPI authentication code is extended to support connection
encryption.  Connection begins as soon as possible - that is,
immediately after the client and server complete authentication.
---
 configure                           |   2 +
 configure.in                        |   1 +
 doc/src/sgml/client-auth.sgml       |   2 +-
 doc/src/sgml/runtime.sgml           |  20 +-
 src/Makefile.global.in              |   1 +
 src/backend/libpq/Makefile          |   4 +
 src/backend/libpq/auth.c            | 330 +-------------------
 src/backend/libpq/be-gssapi.c       | 584 ++++++++++++++++++++++++++++++++++++
 src/backend/libpq/be-secure.c       |  16 +
 src/backend/postmaster/postmaster.c |  12 +
 src/include/libpq/libpq-be.h        |  31 ++
 src/interfaces/libpq/Makefile       |   4 +
 src/interfaces/libpq/fe-auth.c      | 182 -----------
 src/interfaces/libpq/fe-auth.h      |   5 +
 src/interfaces/libpq/fe-connect.c   |  10 +
 src/interfaces/libpq/fe-gssapi.c    | 475 +++++++++++++++++++++++++++++
 src/interfaces/libpq/fe-secure.c    |  16 +-
 src/interfaces/libpq/libpq-int.h    |  16 +-
 18 files changed, 1193 insertions(+), 518 deletions(-)
 create mode 100644 src/backend/libpq/be-gssapi.c
 create mode 100644 src/interfaces/libpq/fe-gssapi.c

diff --git a/configure b/configure
index 3dd1b15..7fd7610 100755
--- a/configure
+++ b/configure
@@ -712,6 +712,7 @@ with_uuid
 with_selinux
 with_openssl
 krb_srvtab
+with_gssapi
 with_python
 with_perl
 with_tcl
@@ -5488,6 +5489,7 @@ $as_echo "$with_gssapi" >&6; }
 
 
 
+
 #
 # Kerberos configuration parameters
 #
diff --git a/configure.in b/configure.in
index 9398482..b19932e 100644
--- a/configure.in
+++ b/configure.in
@@ -636,6 +636,7 @@ PGAC_ARG_BOOL(with, gssapi, no, [build with GSSAPI support],
   krb_srvtab="FILE:\$(sysconfdir)/krb5.keytab"
 ])
 AC_MSG_RESULT([$with_gssapi])
+AC_SUBST(with_gssapi)
 
 
 AC_SUBST(krb_srvtab)
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 3b2935c..7d37223 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -915,7 +915,7 @@ omicron         bryanh                  guest1
     provides automatic authentication (single sign-on) for systems
     that support it. The authentication itself is secure, but the
     data sent over the database connection will be sent unencrypted unless
-    <acronym>SSL</acronym> is used.
+    <acronym>SSL</acronym> or <acronym>GSSAPI</acronym> are used.
    </para>
 
    <para>
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index cda05f5..bd8156f 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1880,12 +1880,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
   </para>
 
   <para>
-   To prevent spoofing on TCP connections, the best solution is to use
-   SSL certificates and make sure that clients check the server's certificate.
-   To do that, the server
-   must be configured to accept only <literal>hostssl</> connections (<xref
-   linkend="auth-pg-hba-conf">) and have SSL key and certificate files
-   (<xref linkend="ssl-tcp">). The TCP client must connect using
+   To prevent spoofing on TCP connections, the best solutions are either to
+   use GSSAPI for authentication and encryption or to use SSL certificates and
+   make sure that clients check the server's certificate.  To secure using
+   SSL, the server must be configured to accept only <literal>hostssl</>
+   connections (<xref linkend="auth-pg-hba-conf">) and have SSL key and
+   certificate files (<xref linkend="ssl-tcp">). The TCP client must connect
+   using
    <literal>sslmode=verify-ca</> or
    <literal>verify-full</> and have the appropriate root certificate
    file installed (<xref linkend="libq-ssl-certificates">).
@@ -2005,6 +2006,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
       connect to servers only via SSL. <application>Stunnel</> or
       <application>SSH</> can also be used to encrypt transmissions.
      </para>
+
+     <para>
+       Similarly, GSSAPI also encrypts all data sent across the network,
+       including passwords, queries, and data, as in
+       SSL. <filename>pg_hba.conf</> allows specification of GSSAPI
+       connections, which are always encrypted.
+     </para>
     </listitem>
   </varlistentry>
 
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 51f4797..842a397 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -183,6 +183,7 @@ with_perl	= @with_perl@
 with_python	= @with_python@
 with_tcl	= @with_tcl@
 with_openssl	= @with_openssl@
+with_gssapi	= @with_gssapi@
 with_selinux	= @with_selinux@
 with_libxml	= @with_libxml@
 with_libxslt	= @with_libxslt@
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 09410c4..b80ae74 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-gssapi.o
+endif
+
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 57c2f48..c0366fd 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -22,6 +22,7 @@
 #include <unistd.h>
 
 #include "libpq/auth.h"
+#include "libpq/libpq-be.h"
 #include "libpq/crypt.h"
 #include "libpq/ip.h"
 #include "libpq/libpq.h"
@@ -36,7 +37,7 @@
  * Global authentication functions
  *----------------------------------------------------------------
  */
-static void sendAuthRequest(Port *port, AuthRequest areq);
+void sendAuthRequest(Port *port, AuthRequest areq);
 static void auth_failed(Port *port, int status, char *logdetail);
 static char *recv_password_packet(Port *port);
 static int	recv_and_check_password_packet(Port *port, char **logdetail);
@@ -132,21 +133,6 @@ bool		pg_krb_caseins_users;
 
 
 /*----------------------------------------------------------------
- * GSSAPI Authentication
- *----------------------------------------------------------------
- */
-#ifdef ENABLE_GSS
-#if defined(HAVE_GSSAPI_H)
-#include <gssapi.h>
-#else
-#include <gssapi/gssapi.h>
-#endif
-
-static int	pg_GSS_recvauth(Port *port);
-#endif   /* ENABLE_GSS */
-
-
-/*----------------------------------------------------------------
  * SSPI Authentication
  *----------------------------------------------------------------
  */
@@ -167,22 +153,6 @@ static int	pg_SSPI_recvauth(Port *port);
 static int	CheckRADIUSAuth(Port *port);
 
 
-/*
- * Maximum accepted size of GSS and SSPI authentication tokens.
- *
- * Kerberos tickets are usually quite small, but the TGTs issued by Windows
- * domain controllers include an authorization field known as the Privilege
- * Attribute Certificate (PAC), which contains the user's Windows permissions
- * (group memberships etc.). The PAC is copied into all tickets obtained on
- * the basis of this TGT (even those issued by Unix realms which the Windows
- * realm trusts), and can be several kB in size. The maximum token size
- * accepted by Windows systems is determined by the MaxAuthToken Windows
- * registry setting. Microsoft recommends that it is not set higher than
- * 65535 bytes, so that seems like a reasonable limit for us as well.
- */
-#define PG_MAX_AUTH_TOKEN_LENGTH	65535
-
-
 /*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
@@ -565,7 +535,7 @@ ClientAuthentication(Port *port)
 /*
  * Send an authentication request packet to the frontend.
  */
-static void
+void
 sendAuthRequest(Port *port, AuthRequest areq)
 {
 	StringInfoData buf;
@@ -707,300 +677,6 @@ recv_and_check_password_packet(Port *port, char **logdetail)
 	return result;
 }
 
-
-
-/*----------------------------------------------------------------
- * GSSAPI authentication system
- *----------------------------------------------------------------
- */
-#ifdef ENABLE_GSS
-
-#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
-/*
- * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
- * that contain the OIDs required. Redefine here, values copied
- * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
- */
-static const gss_OID_desc GSS_C_NT_USER_NAME_desc =
-{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"};
-static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc;
-#endif
-
-
-static void
-pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
-{
-	gss_buffer_desc gmsg;
-	OM_uint32	lmin_s,
-				msg_ctx;
-	char		msg_major[128],
-				msg_minor[128];
-
-	/* Fetch major status message */
-	msg_ctx = 0;
-	gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE,
-					   GSS_C_NO_OID, &msg_ctx, &gmsg);
-	strlcpy(msg_major, gmsg.value, sizeof(msg_major));
-	gss_release_buffer(&lmin_s, &gmsg);
-
-	if (msg_ctx)
-
-		/*
-		 * More than one message available. XXX: Should we loop and read all
-		 * messages? (same below)
-		 */
-		ereport(WARNING,
-				(errmsg_internal("incomplete GSS error report")));
-
-	/* Fetch mechanism minor status message */
-	msg_ctx = 0;
-	gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE,
-					   GSS_C_NO_OID, &msg_ctx, &gmsg);
-	strlcpy(msg_minor, gmsg.value, sizeof(msg_minor));
-	gss_release_buffer(&lmin_s, &gmsg);
-
-	if (msg_ctx)
-		ereport(WARNING,
-				(errmsg_internal("incomplete GSS minor error report")));
-
-	/*
-	 * errmsg_internal, since translation of the first part must be done
-	 * before calling this function anyway.
-	 */
-	ereport(severity,
-			(errmsg_internal("%s", errmsg),
-			 errdetail_internal("%s: %s", msg_major, msg_minor)));
-}
-
-static int
-pg_GSS_recvauth(Port *port)
-{
-	OM_uint32	maj_stat,
-				min_stat,
-				lmin_s,
-				gflags;
-	int			mtype;
-	int			ret;
-	StringInfoData buf;
-	gss_buffer_desc gbuf;
-
-	/*
-	 * GSS auth is not supported for protocol versions before 3, because it
-	 * relies on the overall message length word to determine the GSS payload
-	 * size in AuthenticationGSSContinue and PasswordMessage messages. (This
-	 * is, in fact, a design error in our GSS support, because protocol
-	 * messages are supposed to be parsable without relying on the length
-	 * word; but it's not worth changing it now.)
-	 */
-	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
-		ereport(FATAL,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("GSSAPI is not supported in protocol version 2")));
-
-	if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0)
-	{
-		/*
-		 * Set default Kerberos keytab file for the Krb5 mechanism.
-		 *
-		 * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv()
-		 * not always available.
-		 */
-		if (getenv("KRB5_KTNAME") == NULL)
-		{
-			size_t		kt_len = strlen(pg_krb_server_keyfile) + 14;
-			char	   *kt_path = malloc(kt_len);
-
-			if (!kt_path ||
-				snprintf(kt_path, kt_len, "KRB5_KTNAME=%s",
-						 pg_krb_server_keyfile) != kt_len - 2 ||
-				putenv(kt_path) != 0)
-			{
-				ereport(LOG,
-						(errcode(ERRCODE_OUT_OF_MEMORY),
-						 errmsg("out of memory")));
-				return STATUS_ERROR;
-			}
-		}
-	}
-
-	/*
-	 * We accept any service principal that's present in our keytab. This
-	 * increases interoperability between kerberos implementations that see
-	 * for example case sensitivity differently, while not really opening up
-	 * any vector of attack.
-	 */
-	port->gss->cred = GSS_C_NO_CREDENTIAL;
-
-	/*
-	 * Initialize sequence with an empty context
-	 */
-	port->gss->ctx = GSS_C_NO_CONTEXT;
-
-	/*
-	 * Loop through GSSAPI message exchange. This exchange can consist of
-	 * multiple messags sent in both directions. First message is always from
-	 * the client. All messages from client to server are password packets
-	 * (type 'p').
-	 */
-	do
-	{
-		pq_startmsgread();
-
-		CHECK_FOR_INTERRUPTS();
-
-		mtype = pq_getbyte();
-		if (mtype != 'p')
-		{
-			/* Only log error if client didn't disconnect. */
-			if (mtype != EOF)
-				ereport(COMMERROR,
-						(errcode(ERRCODE_PROTOCOL_VIOLATION),
-						 errmsg("expected GSS response, got message type %d",
-								mtype)));
-			return STATUS_ERROR;
-		}
-
-		/* Get the actual GSS token */
-		initStringInfo(&buf);
-		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
-		{
-			/* EOF - pq_getmessage already logged error */
-			pfree(buf.data);
-			return STATUS_ERROR;
-		}
-
-		/* Map to GSSAPI style buffer */
-		gbuf.length = buf.len;
-		gbuf.value = buf.data;
-
-		elog(DEBUG4, "Processing received GSS token of length %u",
-			 (unsigned int) gbuf.length);
-
-		maj_stat = gss_accept_sec_context(
-										  &min_stat,
-										  &port->gss->ctx,
-										  port->gss->cred,
-										  &gbuf,
-										  GSS_C_NO_CHANNEL_BINDINGS,
-										  &port->gss->name,
-										  NULL,
-										  &port->gss->outbuf,
-										  &gflags,
-										  NULL,
-										  NULL);
-
-		/* gbuf no longer used */
-		pfree(buf.data);
-
-		elog(DEBUG5, "gss_accept_sec_context major: %d, "
-			 "minor: %d, outlen: %u, outflags: %x",
-			 maj_stat, min_stat,
-			 (unsigned int) port->gss->outbuf.length, gflags);
-
-		CHECK_FOR_INTERRUPTS();
-
-		if (port->gss->outbuf.length != 0)
-		{
-			/*
-			 * Negotiation generated data to be sent to the client.
-			 */
-			elog(DEBUG4, "sending GSS response token of length %u",
-				 (unsigned int) port->gss->outbuf.length);
-
-			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
-
-			gss_release_buffer(&lmin_s, &port->gss->outbuf);
-		}
-
-		if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
-		{
-			gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
-			pg_GSS_error(ERROR,
-					   gettext_noop("accepting GSS security context failed"),
-						 maj_stat, min_stat);
-		}
-
-		if (maj_stat == GSS_S_CONTINUE_NEEDED)
-			elog(DEBUG4, "GSS continue needed");
-
-	} while (maj_stat == GSS_S_CONTINUE_NEEDED);
-
-	if (port->gss->cred != GSS_C_NO_CREDENTIAL)
-	{
-		/*
-		 * Release service principal credentials
-		 */
-		gss_release_cred(&min_stat, &port->gss->cred);
-	}
-
-	/*
-	 * GSS_S_COMPLETE indicates that authentication is now complete.
-	 *
-	 * Get the name of the user that authenticated, and compare it to the pg
-	 * username that was specified for the connection.
-	 */
-	maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL);
-	if (maj_stat != GSS_S_COMPLETE)
-		pg_GSS_error(ERROR,
-					 gettext_noop("retrieving GSS user name failed"),
-					 maj_stat, min_stat);
-
-	/*
-	 * Split the username at the realm separator
-	 */
-	if (strchr(gbuf.value, '@'))
-	{
-		char	   *cp = strchr(gbuf.value, '@');
-
-		/*
-		 * If we are not going to include the realm in the username that is
-		 * passed to the ident map, destructively modify it here to remove the
-		 * realm. Then advance past the separator to check the realm.
-		 */
-		if (!port->hba->include_realm)
-			*cp = '\0';
-		cp++;
-
-		if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
-		{
-			/*
-			 * Match the realm part of the name first
-			 */
-			if (pg_krb_caseins_users)
-				ret = pg_strcasecmp(port->hba->krb_realm, cp);
-			else
-				ret = strcmp(port->hba->krb_realm, cp);
-
-			if (ret)
-			{
-				/* GSS realm does not match */
-				elog(DEBUG2,
-				   "GSSAPI realm (%s) and configured realm (%s) don't match",
-					 cp, port->hba->krb_realm);
-				gss_release_buffer(&lmin_s, &gbuf);
-				return STATUS_ERROR;
-			}
-		}
-	}
-	else if (port->hba->krb_realm && strlen(port->hba->krb_realm))
-	{
-		elog(DEBUG2,
-			 "GSSAPI did not return realm but realm matching was requested");
-
-		gss_release_buffer(&lmin_s, &gbuf);
-		return STATUS_ERROR;
-	}
-
-	ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
-						pg_krb_caseins_users);
-
-	gss_release_buffer(&lmin_s, &gbuf);
-
-	return ret;
-}
-#endif   /* ENABLE_GSS */
-
-
 /*----------------------------------------------------------------
  * SSPI authentication system
  *----------------------------------------------------------------
diff --git a/src/backend/libpq/be-gssapi.c b/src/backend/libpq/be-gssapi.c
new file mode 100644
index 0000000..04d8a98
--- /dev/null
+++ b/src/backend/libpq/be-gssapi.c
@@ -0,0 +1,584 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-gssapi.c
+ *	  GSSAPI authentication and encryption support
+ *
+ * Portions Copyright (c) 2015-2016, Red Hat, Inc.
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/libpq/auth.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "libpq/libpq.h"
+#include "libpq/libpq-be.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+
+#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
+/*
+ * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
+ * that contain the OIDs required. Redefine here, values copied
+ * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
+ */
+static const gss_OID_desc GSS_C_NT_USER_NAME_desc =
+{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"};
+static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc;
+#endif
+
+static void
+pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+	gss_buffer_desc gmsg;
+	OM_uint32	lmin_s,
+				msg_ctx;
+	char		msg_major[128],
+				msg_minor[128];
+
+	/* Fetch major status message */
+	msg_ctx = 0;
+	gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE,
+					   GSS_C_NO_OID, &msg_ctx, &gmsg);
+	strlcpy(msg_major, gmsg.value, sizeof(msg_major));
+	gss_release_buffer(&lmin_s, &gmsg);
+
+	if (msg_ctx)
+
+		/*
+		 * More than one message available. XXX: Should we loop and read all
+		 * messages? (same below)
+		 */
+		ereport(WARNING,
+				(errmsg_internal("incomplete GSS error report")));
+
+	/* Fetch mechanism minor status message */
+	msg_ctx = 0;
+	gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE,
+					   GSS_C_NO_OID, &msg_ctx, &gmsg);
+	strlcpy(msg_minor, gmsg.value, sizeof(msg_minor));
+	gss_release_buffer(&lmin_s, &gmsg);
+
+	if (msg_ctx)
+		ereport(WARNING,
+				(errmsg_internal("incomplete GSS minor error report")));
+
+	/*
+	 * errmsg_internal, since translation of the first part must be done
+	 * before calling this function anyway.
+	 */
+	ereport(severity,
+			(errmsg_internal("%s", errmsg),
+			 errdetail_internal("%s: %s", msg_major, msg_minor)));
+}
+
+int
+pg_GSS_recvauth(Port *port)
+{
+	OM_uint32	maj_stat,
+				min_stat,
+				lmin_s,
+				gflags;
+	int			mtype;
+	int			ret;
+	StringInfoData buf;
+	gss_buffer_desc gbuf;
+
+	/*
+	 * GSS auth is not supported for protocol versions before 3, because it
+	 * relies on the overall message length word to determine the GSS payload
+	 * size in AuthenticationGSSContinue and PasswordMessage messages. (This
+	 * is, in fact, a design error in our GSS support, because protocol
+	 * messages are supposed to be parsable without relying on the length
+	 * word; but it's not worth changing it now.)
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("GSSAPI is not supported in protocol version 2")));
+
+	if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0)
+	{
+		/*
+		 * Set default Kerberos keytab file for the Krb5 mechanism.
+		 *
+		 * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv()
+		 * not always available.
+		 */
+		if (getenv("KRB5_KTNAME") == NULL)
+		{
+			size_t		kt_len = strlen(pg_krb_server_keyfile) + 14;
+			char	   *kt_path = malloc(kt_len);
+
+			if (!kt_path ||
+				snprintf(kt_path, kt_len, "KRB5_KTNAME=%s",
+						 pg_krb_server_keyfile) != kt_len - 2 ||
+				putenv(kt_path) != 0)
+			{
+				ereport(LOG,
+						(errcode(ERRCODE_OUT_OF_MEMORY),
+						 errmsg("out of memory")));
+				return STATUS_ERROR;
+			}
+		}
+	}
+
+	/*
+	 * We accept any service principal that's present in our keytab. This
+	 * increases interoperability between kerberos implementations that see
+	 * for example case sensitivity differently, while not really opening up
+	 * any vector of attack.
+	 */
+	port->gss->cred = GSS_C_NO_CREDENTIAL;
+
+	/*
+	 * Initialize sequence with an empty context
+	 */
+	port->gss->ctx = GSS_C_NO_CONTEXT;
+
+	/*
+	 * Loop through GSSAPI message exchange. This exchange can consist of
+	 * multiple messags sent in both directions. First message is always from
+	 * the client. All messages from client to server are password packets
+	 * (type 'p').
+	 */
+	do
+	{
+		pq_startmsgread();
+
+		CHECK_FOR_INTERRUPTS();
+
+		mtype = pq_getbyte();
+		if (mtype != 'p')
+		{
+			/* Only log error if client didn't disconnect. */
+			if (mtype != EOF)
+				ereport(COMMERROR,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("expected GSS response, got message type %d",
+								mtype)));
+			return STATUS_ERROR;
+		}
+
+		/* Get the actual GSS token */
+		initStringInfo(&buf);
+		if (pq_getmessage(&buf, PG_MAX_AUTH_TOKEN_LENGTH))
+		{
+			/* EOF - pq_getmessage already logged error */
+			pfree(buf.data);
+			return STATUS_ERROR;
+		}
+
+		/* Map to GSSAPI style buffer */
+		gbuf.length = buf.len;
+		gbuf.value = buf.data;
+
+		elog(DEBUG4, "Processing received GSS token of length %u",
+			 (unsigned int) gbuf.length);
+
+		maj_stat = gss_accept_sec_context(
+										  &min_stat,
+										  &port->gss->ctx,
+										  port->gss->cred,
+										  &gbuf,
+										  GSS_C_NO_CHANNEL_BINDINGS,
+										  &port->gss->name,
+										  NULL,
+										  &port->gss->outbuf,
+										  &gflags,
+										  NULL,
+										  NULL);
+
+		/* gbuf no longer used */
+		pfree(buf.data);
+
+		elog(DEBUG5, "gss_accept_sec_context major: %d, "
+			 "minor: %d, outlen: %u, outflags: %x",
+			 maj_stat, min_stat,
+			 (unsigned int) port->gss->outbuf.length, gflags);
+
+		CHECK_FOR_INTERRUPTS();
+
+		if (port->gss->outbuf.length != 0)
+		{
+			/*
+			 * Negotiation generated data to be sent to the client.
+			 */
+			elog(DEBUG4, "sending GSS response token of length %u",
+				 (unsigned int) port->gss->outbuf.length);
+
+			sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+
+			gss_release_buffer(&lmin_s, &port->gss->outbuf);
+		}
+
+		if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
+		{
+			gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
+			pg_GSS_error(ERROR,
+					   gettext_noop("accepting GSS security context failed"),
+						 maj_stat, min_stat);
+		}
+
+		if (maj_stat == GSS_S_CONTINUE_NEEDED)
+			elog(DEBUG4, "GSS continue needed");
+
+	} while (maj_stat == GSS_S_CONTINUE_NEEDED);
+
+	if (port->gss->cred != GSS_C_NO_CREDENTIAL)
+	{
+		/*
+		 * Release service principal credentials
+		 */
+		gss_release_cred(&min_stat, &port->gss->cred);
+	}
+
+	/*
+	 * GSS_S_COMPLETE indicates that authentication is now complete.
+	 *
+	 * Get the name of the user that authenticated, and compare it to the pg
+	 * username that was specified for the connection.
+	 */
+	maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL);
+	if (maj_stat != GSS_S_COMPLETE)
+		pg_GSS_error(ERROR,
+					 gettext_noop("retrieving GSS user name failed"),
+					 maj_stat, min_stat);
+
+	/*
+	 * Split the username at the realm separator
+	 */
+	if (strchr(gbuf.value, '@'))
+	{
+		char	   *cp = strchr(gbuf.value, '@');
+
+		/*
+		 * If we are not going to include the realm in the username that is
+		 * passed to the ident map, destructively modify it here to remove the
+		 * realm. Then advance past the separator to check the realm.
+		 */
+		if (!port->hba->include_realm)
+			*cp = '\0';
+		cp++;
+
+		if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
+		{
+			/*
+			 * Match the realm part of the name first
+			 */
+			if (pg_krb_caseins_users)
+				ret = pg_strcasecmp(port->hba->krb_realm, cp);
+			else
+				ret = strcmp(port->hba->krb_realm, cp);
+
+			if (ret)
+			{
+				/* GSS realm does not match */
+				elog(DEBUG2,
+				   "GSSAPI realm (%s) and configured realm (%s) don't match",
+					 cp, port->hba->krb_realm);
+				gss_release_buffer(&lmin_s, &gbuf);
+				return STATUS_ERROR;
+			}
+		}
+	}
+	else if (port->hba->krb_realm && strlen(port->hba->krb_realm))
+	{
+		elog(DEBUG2,
+			 "GSSAPI did not return realm but realm matching was requested");
+
+		gss_release_buffer(&lmin_s, &gbuf);
+		return STATUS_ERROR;
+	}
+
+	ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
+						pg_krb_caseins_users);
+
+	gss_release_buffer(&lmin_s, &gbuf);
+
+	return ret;
+}
+
+static ssize_t
+be_gssapi_should_crypto(Port *port)
+{
+	OM_uint32 major, minor;
+	int open = 1;
+
+	if (port->gss->ctx == GSS_C_NO_CONTEXT)
+		return 0;
+	else if (port->gss->should_encrypt)
+		return 1;
+
+	major = gss_inquire_context(&minor, port->gss->ctx,
+								NULL, NULL, NULL, NULL, NULL, NULL,
+								&open);
+	if (major == GSS_S_NO_CONTEXT)
+	{
+		/*
+		 * In MIT krb5 < 1.14, it was not possible to call gss_inquire_context
+		 * on an incomplete context.  This was a violation of rfc2744 and has
+		 * been corrected in https://github.com/krb5/krb5/pull/285
+		 */
+		return 0;
+	}
+	else if (GSS_ERROR(major))
+	{
+		pg_GSS_error(ERROR,
+					 gettext_noop("GSSAPI context state error"),
+					 major, minor);
+		return -1;
+	}
+	else if (open != 0)
+	{
+		/*
+		 * Though we can start encrypting here, our client is not ready since
+		 * it has not received the final auth packet.  Set encryption on for
+		 * the next packet, but send this one in the clear.
+		 */
+		port->gss->should_encrypt = true;
+	}
+	return 0;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	ssize_t ret;
+	int conf;
+	uint32 netlen;
+	char lenbuf[4];
+	struct iovec iov[2];
+
+	ret = be_gssapi_should_crypto(port);
+	if (ret == -1)
+		return -1;
+	else if (ret == 0)
+		return secure_raw_write(port, ptr, len);
+
+	if (port->gss->writebuf.len != 0)
+	{
+		ret = send(port->sock,
+				   port->gss->writebuf.data + port->gss->writebuf.cursor,
+				   port->gss->writebuf.len - port->gss->writebuf.cursor,
+				   0);
+		if (ret < 0)
+			return ret;
+
+		port->gss->writebuf.cursor += ret;
+		if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+		{
+			port->gss->writebuf.len = port->gss->writebuf.cursor = 0;
+			port->gss->writebuf.data[0] = '\0';
+			/* The entire request has now been written */
+			return len;
+		}
+		/* need to be called again */
+		return 0;
+	}
+
+	output.value = NULL;
+	output.length = 0;
+
+	input.value = ptr;
+	input.length = len;
+
+	conf = 0;
+	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("GSSAPI wrap error"),
+					 major, minor);
+		ret = -1;
+		goto cleanup;
+	}
+	else if (conf == 0)
+	{
+		ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+		ret = -1;
+		goto cleanup;
+	}
+
+	netlen = htonl(output.length);
+	memcpy(lenbuf, &netlen, 4);
+	iov[0].iov_base = lenbuf;
+	iov[0].iov_len = 4;
+	iov[1].iov_base = output.value;
+	iov[1].iov_len = output.length;
+	ret = writev(port->sock, iov, 2);
+	if (ret == output.length + 4)
+	{
+		/*
+		 * Strictly speaking, this isn't true; we did write more than `len`
+		 * bytes.  However, this information is actually used to keep track of
+		 * what has/hasn't been written yet, not actually report the number of
+		 * bytes we wrote.
+		 */
+		ret = len;
+		goto cleanup;
+	}
+	else if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
+	{
+		ereport(FATAL, (errmsg("Failed to send entire GSSAPI blob")));
+		ret = -1;
+		goto cleanup;
+	}
+
+	if (ret < 4)
+	{
+		appendBinaryStringInfo(&port->gss->writebuf, lenbuf + ret, 4 - ret);
+		ret = 0;
+	}
+	else
+	{
+		ret -= 4;
+	}
+	appendBinaryStringInfo(&port->gss->writebuf, (char *)output.value + ret,
+						   output.length - ret);
+
+	/* Set return so that we get retried when the socket becomes writable */
+	ret = 0;
+ cleanup:
+	if (output.value != NULL)
+		gss_release_buffer(&minor, &output);
+
+	return ret;
+}
+
+static ssize_t
+be_gssapi_read_from_buffer(Port *port, void *ptr, size_t len)
+{
+	ssize_t ret = 0;
+
+	if (port->gss->buf.len > 4 && port->gss->buf.cursor < port->gss->buf.len)
+	{
+		if (len > port->gss->buf.len - port->gss->buf.cursor)
+			len = port->gss->buf.len - port->gss->buf.cursor;
+
+		memcpy(ptr, port->gss->buf.data + port->gss->buf.cursor, len);
+		port->gss->buf.cursor += len;
+
+		ret = len;
+	}
+
+	if (port->gss->buf.cursor == port->gss->buf.len)
+	{
+		port->gss->buf.cursor = port->gss->buf.len = 0;
+		port->gss->buf.data[0] = '\0';
+	}
+
+	return ret;
+}
+
+/*
+ * Here's how the buffering works:
+ *
+ * First, we read the packet into port->gss->buf.data.  The first four bytes
+ * of this will be the network-order length of the GSSAPI-encrypted blob; from
+ * position 4 to port->gss->buf.len is then this blob.  Therefore, at this
+ * point port->gss->buf.len is the length of the blob plus 4.
+ * port->gss->buf.cursor is zero for this entire step.
+ *
+ * Then we overwrite port->gss->buf.data entirely with the decrypted contents.
+ * At this point, port->gss->buf.len reflects the actual length of the
+ * decrypted data.  port->gss->buf.cursor is then used to incrementally return
+ * this data to the caller and is therefore nonzero during this step.
+ *
+ * Once all decrypted data is returned to the caller, the cycle repeats.
+ */
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	ssize_t ret;
+	int conf = 0;
+
+	ret = be_gssapi_should_crypto(port);
+	if (ret == -1)
+		return -1;
+	else if (ret == 0)
+		return secure_raw_read(port, ptr, len);
+
+	if (len == 0)
+		return 0;
+
+	if (port->gss->buf.cursor > 0)
+	{
+		ret = be_gssapi_read_from_buffer(port, ptr, len);
+		if (ret > 0)
+			return ret + be_gssapi_read(port, (char *)ptr + ret, len - ret);
+	}
+
+	/* our buffer is now empty */
+	if (port->gss->buf.len < 4)
+	{
+		enlargeStringInfo(&port->gss->buf, 4);
+		ret = secure_raw_read(port, port->gss->buf.data + port->gss->buf.len,
+							  4 - port->gss->buf.len);
+		if (ret < 0)
+			return ret;
+
+		port->gss->buf.len += ret;
+		port->gss->buf.data[port->gss->buf.len] = '\0';
+		if (port->gss->buf.len < 4)
+			return 0;
+	}
+
+	/* we know the length of the packet at this point */
+	memcpy((char *)&input.length, port->gss->buf.data, 4);
+	input.length = ntohl(input.length);
+	enlargeStringInfo(&port->gss->buf, input.length - port->gss->buf.len + 4);
+
+	ret = secure_raw_read(port, port->gss->buf.data + port->gss->buf.len,
+						  input.length - port->gss->buf.len + 4);
+	if (ret < 0)
+		return ret;
+
+	port->gss->buf.len += ret;
+	port->gss->buf.data[port->gss->buf.len] = '\0';
+	if (port->gss->buf.len - 4 < input.length)
+		return 0;
+
+	output.value = NULL;
+	output.length = 0;
+	input.value = port->gss->buf.data + 4;
+	major = gss_unwrap(&minor, port->gss->ctx, &input, &output, &conf, NULL);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error(ERROR,
+					 gettext_noop("GSSAPI unwrap error"),
+					 major, minor);
+		ret = -1;
+		goto cleanup;
+	}
+	else if (conf == 0)
+	{
+		ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+		ret = -1;
+		goto cleanup;
+	}
+
+	port->gss->buf.cursor = port->gss->buf.len = 0;
+	port->gss->buf.data[0] = '\0';
+	enlargeStringInfo(&port->gss->buf, output.length);
+	memcpy(port->gss->buf.data, output.value, output.length);
+	port->gss->buf.len = output.length;
+	port->gss->buf.data[port->gss->buf.len] = '\0';
+
+	ret = be_gssapi_read_from_buffer(port, ptr, len);
+ cleanup:
+	if (output.value != NULL)
+		gss_release_buffer(&minor, &output);
+
+	return ret;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index ac709d1..d43fa1b 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -132,6 +132,14 @@ retry:
 	}
 	else
 #endif
+#ifdef ENABLE_GSS
+	if (port->gss != NULL)
+	{
+		n = be_gssapi_read(port, ptr, len);
+		waitfor = WL_SOCKET_READABLE;
+	}
+	else
+#endif
 	{
 		n = secure_raw_read(port, ptr, len);
 		waitfor = WL_SOCKET_READABLE;
@@ -234,6 +242,14 @@ retry:
 	}
 	else
 #endif
+#ifdef ENABLE_GSS
+	if (port->gss != NULL)
+	{
+		n = be_gssapi_write(port, ptr, len);
+		waitfor = WL_SOCKET_WRITEABLE;
+	}
+	else
+#endif
 	{
 		n = secure_raw_write(port, ptr, len);
 		waitfor = WL_SOCKET_WRITEABLE;
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 9aaed5b..1d827ce 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2349,6 +2349,10 @@ ConnCreate(int serverFd)
 		ExitPostmaster(1);
 	}
 #endif
+#ifdef ENABLE_GSS
+	initStringInfo(&port->gss->buf);
+	initStringInfo(&port->gss->writebuf);
+#endif
 #endif
 
 	return port;
@@ -2365,7 +2369,15 @@ ConnFree(Port *conn)
 	secure_close(conn);
 #endif
 	if (conn->gss)
+	{
+#ifdef ENABLE_GSS
+		if (conn->gss->buf.data)
+			pfree(conn->gss->buf.data);
+		if (conn->gss->writebuf.data)
+			pfree(conn->gss->writebuf.data);
+#endif
 		free(conn->gss);
+	}
 	free(conn);
 }
 
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 5d07b78..b0a31ae 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -68,6 +68,7 @@ typedef struct
 #include "datatype/timestamp.h"
 #include "libpq/hba.h"
 #include "libpq/pqcomm.h"
+#include "lib/stringinfo.h"
 
 
 typedef enum CAC_state
@@ -88,6 +89,9 @@ typedef struct
 	gss_cred_id_t cred;			/* GSSAPI connection cred's */
 	gss_ctx_id_t ctx;			/* GSSAPI connection context */
 	gss_name_t	name;			/* GSSAPI client name */
+	StringInfoData buf;			/* GSSAPI encryption data buffering */
+	bool should_encrypt;		/* GSSAPI encryption start */
+	StringInfoData writebuf;	/* GSSAPI nonblocking write buffering */
 #endif
 } pg_gssinfo;
 #endif
@@ -214,6 +218,33 @@ 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
+int pg_GSS_recvauth(Port *port);
+ssize_t be_gssapi_read(Port *port, void *ptr, size_t len);
+ssize_t be_gssapi_write(Port *port, void *ptr, size_t len);
+
+/* GUC */
+extern char	   *pg_krb_server_keyfile;
+extern bool		pg_krb_caseins_users;
+
+/*
+ * Maximum accepted size of GSS and SSPI authentication tokens.
+ *
+ * Kerberos tickets are usually quite small, but the TGTs issued by Windows
+ * domain controllers include an authorization field known as the Privilege
+ * Attribute Certificate (PAC), which contains the user's Windows permissions
+ * (group memberships etc.). The PAC is copied into all tickets obtained on
+ * the basis of this TGT (even those issued by Unix realms which the Windows
+ * realm trusts), and can be several kB in size. The maximum token size
+ * accepted by Windows systems is determined by the MaxAuthToken Windows
+ * registry setting. Microsoft recommends that it is not set higher than
+ * 65535 bytes, so that seems like a reasonable limit for us as well.
+ */
+#define PG_MAX_AUTH_TOKEN_LENGTH       65535
+#endif
+
+extern void sendAuthRequest(Port *port, AuthRequest areq);
+
 extern ProtocolVersion FrontendProtocol;
 
 /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 1b292d2..4b206b4 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-gssapi.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 cd863a5..c50f6dd 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -42,188 +42,6 @@
 #include "fe-auth.h"
 #include "libpq/md5.h"
 
-
-#ifdef ENABLE_GSS
-/*
- * GSSAPI authentication system.
- */
-
-#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
-/*
- * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
- * that contain the OIDs required. Redefine here, values copied
- * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
- */
-static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc =
-{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
-static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc;
-#endif
-
-/*
- * Fetch all errors of a specific type and append to "str".
- */
-static void
-pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
-				 OM_uint32 stat, int type)
-{
-	OM_uint32	lmin_s;
-	gss_buffer_desc lmsg;
-	OM_uint32	msg_ctx = 0;
-
-	do
-	{
-		gss_display_status(&lmin_s, stat, type,
-						   GSS_C_NO_OID, &msg_ctx, &lmsg);
-		appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value);
-		gss_release_buffer(&lmin_s, &lmsg);
-	} while (msg_ctx);
-}
-
-/*
- * GSSAPI errors contain two parts; put both into conn->errorMessage.
- */
-static void
-pg_GSS_error(const char *mprefix, PGconn *conn,
-			 OM_uint32 maj_stat, OM_uint32 min_stat)
-{
-	resetPQExpBuffer(&conn->errorMessage);
-
-	/* Fetch major error codes */
-	pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE);
-
-	/* Add the minor codes as well */
-	pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE);
-}
-
-/*
- * Continue GSS authentication with next token as needed.
- */
-static int
-pg_GSS_continue(PGconn *conn)
-{
-	OM_uint32	maj_stat,
-				min_stat,
-				lmin_s;
-
-	maj_stat = gss_init_sec_context(&min_stat,
-									GSS_C_NO_CREDENTIAL,
-									&conn->gctx,
-									conn->gtarg_nam,
-									GSS_C_NO_OID,
-									GSS_C_MUTUAL_FLAG,
-									0,
-									GSS_C_NO_CHANNEL_BINDINGS,
-		  (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf,
-									NULL,
-									&conn->goutbuf,
-									NULL,
-									NULL);
-
-	if (conn->gctx != GSS_C_NO_CONTEXT)
-	{
-		free(conn->ginbuf.value);
-		conn->ginbuf.value = NULL;
-		conn->ginbuf.length = 0;
-	}
-
-	if (conn->goutbuf.length != 0)
-	{
-		/*
-		 * GSS generated data to send to the server. We don't care if it's the
-		 * first or subsequent packet, just send the same kind of password
-		 * packet.
-		 */
-		if (pqPacketSend(conn, 'p',
-						 conn->goutbuf.value, conn->goutbuf.length)
-			!= STATUS_OK)
-		{
-			gss_release_buffer(&lmin_s, &conn->goutbuf);
-			return STATUS_ERROR;
-		}
-	}
-	gss_release_buffer(&lmin_s, &conn->goutbuf);
-
-	if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
-	{
-		pg_GSS_error(libpq_gettext("GSSAPI continuation error"),
-					 conn,
-					 maj_stat, min_stat);
-		gss_release_name(&lmin_s, &conn->gtarg_nam);
-		if (conn->gctx)
-			gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER);
-		return STATUS_ERROR;
-	}
-
-	if (maj_stat == GSS_S_COMPLETE)
-		gss_release_name(&lmin_s, &conn->gtarg_nam);
-
-	return STATUS_OK;
-}
-
-/*
- * Send initial GSS authentication token
- */
-static int
-pg_GSS_startup(PGconn *conn)
-{
-	OM_uint32	maj_stat,
-				min_stat;
-	int			maxlen;
-	gss_buffer_desc temp_gbuf;
-
-	if (!(conn->pghost && conn->pghost[0] != '\0'))
-	{
-		printfPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("host name must be specified\n"));
-		return STATUS_ERROR;
-	}
-
-	if (conn->gctx)
-	{
-		printfPQExpBuffer(&conn->errorMessage,
-					libpq_gettext("duplicate GSS authentication request\n"));
-		return STATUS_ERROR;
-	}
-
-	/*
-	 * Import service principal name so the proper ticket can be acquired by
-	 * the GSSAPI system.
-	 */
-	maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2;
-	temp_gbuf.value = (char *) malloc(maxlen);
-	if (!temp_gbuf.value)
-	{
-		printfPQExpBuffer(&conn->errorMessage,
-						  libpq_gettext("out of memory\n"));
-		return STATUS_ERROR;
-	}
-	snprintf(temp_gbuf.value, maxlen, "%s@%s",
-			 conn->krbsrvname, conn->pghost);
-	temp_gbuf.length = strlen(temp_gbuf.value);
-
-	maj_stat = gss_import_name(&min_stat, &temp_gbuf,
-							   GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam);
-	free(temp_gbuf.value);
-
-	if (maj_stat != GSS_S_COMPLETE)
-	{
-		pg_GSS_error(libpq_gettext("GSSAPI name import error"),
-					 conn,
-					 maj_stat, min_stat);
-		return STATUS_ERROR;
-	}
-
-	/*
-	 * Initial packet is the same as a continuation packet with no initial
-	 * context.
-	 */
-	conn->gctx = GSS_C_NO_CONTEXT;
-
-	return pg_GSS_continue(conn);
-}
-#endif   /* ENABLE_GSS */
-
-
 #ifdef ENABLE_SSPI
 /*
  * SSPI authentication system (Windows only)
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index 9d11654..410c731 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
+int pg_GSS_continue(PGconn *conn);
+int pg_GSS_startup(PGconn *conn);
+#endif
+
 #endif   /* FE_AUTH_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 5ad4755..aa2340a 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2798,6 +2798,10 @@ makeEmptyPGconn(void)
 	conn->wait_ssl_try = false;
 	conn->ssl_in_use = false;
 #endif
+#ifdef ENABLE_GSS
+	initPQExpBuffer(&conn->gbuf);
+	initPQExpBuffer(&conn->gwritebuf);
+#endif
 
 	/*
 	 * We try to send at least 8K at a time, which is the usual size of pipe
@@ -2914,6 +2918,10 @@ freePGconn(PGconn *conn)
 	if (conn->krbsrvname)
 		free(conn->krbsrvname);
 #endif
+#if defined(ENABLE_GSS)
+	termPQExpBuffer(&conn->gbuf);
+	termPQExpBuffer(&conn->gwritebuf);
+#endif
 #if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
 	if (conn->gsslib)
 		free(conn->gsslib);
@@ -3019,6 +3027,8 @@ closePGconn(PGconn *conn)
 			gss_release_buffer(&min_s, &conn->ginbuf);
 		if (conn->goutbuf.length)
 			gss_release_buffer(&min_s, &conn->goutbuf);
+		resetPQExpBuffer(&conn->gbuf);
+		resetPQExpBuffer(&conn->gwritebuf);
 	}
 #endif
 #ifdef ENABLE_SSPI
diff --git a/src/interfaces/libpq/fe-gssapi.c b/src/interfaces/libpq/fe-gssapi.c
new file mode 100644
index 0000000..3427eb6
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi.c
@@ -0,0 +1,475 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth.c
+ *	   The front-end (client) support for GSSAPI
+ *
+ * Portions Copyright (c) 2015-2016, Red Hat, Inc.
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/interfaces/libpq/fe-gssapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "fe-auth.h"
+
+#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
+/*
+ * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
+ * that contain the OIDs required. Redefine here, values copied
+ * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
+ */
+static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc =
+{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
+static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc;
+#endif
+
+/*
+ * Fetch all errors of a specific type and append to "str".
+ */
+static void
+pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
+				 OM_uint32 stat, int type)
+{
+	OM_uint32	lmin_s;
+	gss_buffer_desc lmsg;
+	OM_uint32	msg_ctx = 0;
+
+	do
+	{
+		gss_display_status(&lmin_s, stat, type,
+						   GSS_C_NO_OID, &msg_ctx, &lmsg);
+		appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value);
+		gss_release_buffer(&lmin_s, &lmsg);
+	} while (msg_ctx);
+}
+
+/*
+ * GSSAPI errors contain two parts; put both into conn->errorMessage.
+ */
+static void
+pg_GSS_error(const char *mprefix, PGconn *conn,
+			 OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+	resetPQExpBuffer(&conn->errorMessage);
+
+	/* Fetch major error codes */
+	pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE);
+
+	/* Add the minor codes as well */
+	pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE);
+}
+
+/*
+ * Continue GSS authentication with next token as needed.
+ */
+int
+pg_GSS_continue(PGconn *conn)
+{
+	OM_uint32	maj_stat,
+				min_stat,
+				lmin_s;
+
+	maj_stat = gss_init_sec_context(&min_stat,
+									GSS_C_NO_CREDENTIAL,
+									&conn->gctx,
+									conn->gtarg_nam,
+									GSS_C_NO_OID,
+									GSS_C_MUTUAL_FLAG,
+									0,
+									GSS_C_NO_CHANNEL_BINDINGS,
+		  (conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf,
+									NULL,
+									&conn->goutbuf,
+									NULL,
+									NULL);
+
+	if (conn->gctx != GSS_C_NO_CONTEXT)
+	{
+		free(conn->ginbuf.value);
+		conn->ginbuf.value = NULL;
+		conn->ginbuf.length = 0;
+	}
+
+	if (conn->goutbuf.length != 0)
+	{
+		/*
+		 * GSS generated data to send to the server. We don't care if it's the
+		 * first or subsequent packet, just send the same kind of password
+		 * packet.
+		 */
+		if (pqPacketSend(conn, 'p',
+						 conn->goutbuf.value, conn->goutbuf.length)
+			!= STATUS_OK)
+		{
+			gss_release_buffer(&lmin_s, &conn->goutbuf);
+			return STATUS_ERROR;
+		}
+	}
+	gss_release_buffer(&lmin_s, &conn->goutbuf);
+
+	if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
+	{
+		pg_GSS_error(libpq_gettext("GSSAPI continuation error"),
+					 conn,
+					 maj_stat, min_stat);
+		gss_release_name(&lmin_s, &conn->gtarg_nam);
+		if (conn->gctx)
+			gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER);
+		return STATUS_ERROR;
+	}
+
+	if (maj_stat == GSS_S_COMPLETE)
+		gss_release_name(&lmin_s, &conn->gtarg_nam);
+
+	return STATUS_OK;
+}
+
+/*
+ * Send initial GSS authentication token
+ */
+int
+pg_GSS_startup(PGconn *conn)
+{
+	OM_uint32	maj_stat,
+				min_stat;
+	int			maxlen;
+	gss_buffer_desc temp_gbuf;
+
+	if (!(conn->pghost && conn->pghost[0] != '\0'))
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("host name must be specified\n"));
+		return STATUS_ERROR;
+	}
+
+	if (conn->gctx)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+					libpq_gettext("duplicate GSS authentication request\n"));
+		return STATUS_ERROR;
+	}
+
+	/*
+	 * Import service principal name so the proper ticket can be acquired by
+	 * the GSSAPI system.
+	 */
+	maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2;
+	temp_gbuf.value = (char *) malloc(maxlen);
+	if (!temp_gbuf.value)
+	{
+		printfPQExpBuffer(&conn->errorMessage,
+						  libpq_gettext("out of memory\n"));
+		return STATUS_ERROR;
+	}
+	snprintf(temp_gbuf.value, maxlen, "%s@%s",
+			 conn->krbsrvname, conn->pghost);
+	temp_gbuf.length = strlen(temp_gbuf.value);
+
+	maj_stat = gss_import_name(&min_stat, &temp_gbuf,
+							   GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam);
+	free(temp_gbuf.value);
+
+	if (maj_stat != GSS_S_COMPLETE)
+	{
+		pg_GSS_error(libpq_gettext("GSSAPI name import error"),
+					 conn,
+					 maj_stat, min_stat);
+		return STATUS_ERROR;
+	}
+
+	/*
+	 * Initial packet is the same as a continuation packet with no initial
+	 * context.
+	 */
+	conn->gctx = GSS_C_NO_CONTEXT;
+
+	return pg_GSS_continue(conn);
+}
+
+/*
+ * Only consider encryption when GSS context is complete
+ */
+static ssize_t
+pg_GSS_should_crypto(PGconn *conn)
+{
+	OM_uint32 major, minor;
+	int open = 1;
+
+	if (conn->gctx == GSS_C_NO_CONTEXT)
+		return 0;
+	else if (conn->gencrypt)
+		return 1;
+
+	major = gss_inquire_context(&minor, conn->gctx,
+								NULL, NULL, NULL, NULL, NULL, NULL,
+								&open);
+	if (major == GSS_S_NO_CONTEXT)
+	{
+		/*
+         * In MIT krb5 < 1.14, it was not possible to call gss_inquire_context
+         * on an incomplete context.  This was a violation of rfc2744 and has
+         * been corrected in https://github.com/krb5/krb5/pull/285
+         */
+		return 0;
+	}
+	else if (GSS_ERROR(major))
+	{
+		pg_GSS_error(libpq_gettext("GSSAPI context state error"), conn,
+					 major, minor);
+		return -1;
+	}
+	else if (open != 0)
+	{
+		conn->gencrypt = true;
+		return 1;
+	}
+	return 0;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	ssize_t ret;
+	int conf;
+	uint32 netlen;
+	char lenbuf[4];
+	struct iovec iov[2];
+
+	ret = pg_GSS_should_crypto(conn);
+	if (ret == -1)
+		return -1;
+	else if (ret == 0)
+		return pqsecure_raw_write(conn, ptr, len);
+
+	if (conn->gwritebuf.len != 0)
+	{
+		ret = send(conn->sock, conn->gwritebuf.data + conn->gwritecurs,
+				   conn->gwritebuf.len - conn->gwritecurs, 0);
+		if (ret < 0)
+			return ret;
+		conn->gwritecurs += ret;
+		if (conn->gwritecurs == conn->gwritebuf.len)
+		{
+			conn->gwritebuf.len = conn->gwritecurs = 0;
+			conn->gwritebuf.data[0] = '\0';
+			/* The entire request has now been written */
+			return len;
+		}
+		/* need to be called again */
+		return 0;
+	}
+
+	output.value = NULL;
+	output.length = 0;
+
+	input.value = ptr;
+	input.length = len;
+
+	conf = 0;
+	major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+					 &input, &conf, &output);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn,
+					 major, minor);
+		ret = -1;
+		goto cleanup;
+	}
+	else if (conf == 0)
+	{
+		printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+							  "GSSAPI did not provide confidentiality\n"));
+		ret = -1;
+		goto cleanup;
+	}
+
+	netlen = htonl(output.length);
+	memcpy(lenbuf, &netlen, 4);
+	iov[0].iov_base = lenbuf;
+	iov[0].iov_len = 4;
+	iov[1].iov_base = output.value;
+	iov[1].iov_len = output.length;
+	errno = 0;
+	ret = writev(conn->sock, iov, 2);
+	if (ret == output.length + 4)
+	{
+		/*
+		 * pqsecure_write expects the return value, when >= 0, to be the
+		 * number bytes from ptr delivered, not the number of bytes actually
+		 * written to socket.
+		 */
+		ret = len;
+		goto cleanup;
+	}
+	else if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
+	{
+		printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+							  "GSSAPI writev() failed to send everything\n"));
+		ret = -1;
+		goto cleanup;
+	}
+
+	if (ret < 4)
+	{
+		appendBinaryPQExpBuffer(&conn->gwritebuf, lenbuf + ret, 4 - ret);
+		ret = 0;
+	}
+	else
+	{
+		ret -= 4;
+	}
+	appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)output.value + 4 - ret,
+							output.length + 4 - ret);
+
+	/* Set return so that we get retried when the socket becomes writable */
+	ret = 0;
+ cleanup:
+	if (output.value != NULL)
+		gss_release_buffer(&minor, &output);
+
+	return ret;
+}
+
+static ssize_t
+pg_GSS_read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+	ssize_t ret = 0;
+
+	if (conn->gcursor < conn->gbuf.len)
+	{
+		if (len > conn->gbuf.len - conn->gcursor)
+			len = conn->gbuf.len - conn->gcursor;
+
+		memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+		conn->gcursor += len;
+		ret = len;
+	}
+
+	if (conn->gcursor == conn->gbuf.len)
+	{
+		conn->gcursor = conn->gbuf.len = 0;
+		conn->gbuf.data[0] = '\0';
+	}
+
+	return ret;
+}
+
+/*
+ * Buffering behaves as in be_gssapi_read (in be-gssapi.c).  Because this is
+ * the frontend, we use a PQExpBuffer at conn->gbuf instead of a StringInfo,
+ * and so there is an additional, separate cursor field in the structure.
+ */
+ssize_t
+pg_GSS_read(PGconn *conn, void *ptr, size_t len)
+{
+	OM_uint32 major, minor;
+	gss_buffer_desc input, output;
+	ssize_t ret;
+	int conf = 0;
+
+	ret = pg_GSS_should_crypto(conn);
+	if (ret == -1)
+		return -1;
+	else if (ret == 0)
+		return pqsecure_raw_read(conn, ptr, len);
+
+	if (len == 0)
+		return 0;
+
+	if (conn->gcursor > 0)
+	{
+		ret = pg_GSS_read_from_buffer(conn, ptr, len);
+		if (ret > 0)
+			return ret + pg_GSS_read(conn, (char *)ptr + ret, len - ret);
+	}
+
+	/* our buffer is now empty */
+	if (conn->gbuf.len < 4)
+	{
+		ret = enlargePQExpBuffer(&conn->gbuf, 4);
+		if (ret != 1)
+		{
+			printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+								  "Failed to fit packet length in buffer\n"));
+			return -1;
+		}
+		ret = pqsecure_raw_read(conn, conn->gbuf.data, 4);
+		if (ret < 0)
+			/* error already set by secure_raw_read */
+			return ret;
+		conn->gbuf.len += ret;
+		conn->gbuf.data[conn->gbuf.len] = '\0';
+		if (conn->gbuf.len < 4)
+			return 0;
+	}
+
+	/* we know the length of the packet at this point */
+	memcpy((char *)&input.length, conn->gbuf.data, 4);
+	input.length = ntohl(input.length);
+	ret = enlargePQExpBuffer(&conn->gbuf, input.length - conn->gbuf.len + 4);
+	if (ret != 1)
+	{
+		printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+							  "GSSAPI encrypted packet (length %ld) too big\n"),
+						  input.length);
+		return -1;
+	}
+
+	ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+							input.length - conn->gbuf.len + 4);
+	if (ret < 0)
+		return ret;
+	conn->gbuf.len += ret;
+	conn->gbuf.data[conn->gbuf.len] = '\0';
+	if (conn->gbuf.len - 4 < input.length)
+		return 0;
+
+	output.value = NULL;
+	output.length = 0;
+	input.value = conn->gbuf.data + 4;
+	major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+	if (GSS_ERROR(major))
+	{
+		pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
+					 major, minor);
+		ret = -1;
+		goto cleanup;
+	}
+	else if (conf == 0)
+	{
+		printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+							  "GSSAPI did not provide confidentiality\n"));
+		ret = -1;
+		goto cleanup;
+	}
+
+	conn->gcursor = conn->gbuf.len = 0;
+	conn->gbuf.data[0] = '\0';
+	ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+	if (ret != 1)
+	{
+		printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+							  "GSSAPI decrypted packet (length %ld) too big\n"),
+						  output.length);
+		return -1;
+	}
+	memcpy(conn->gbuf.data, output.value, output.length);
+	conn->gbuf.len = output.length;
+	conn->gbuf.data[conn->gbuf.len] = '\0';
+
+	ret = pg_GSS_read_from_buffer(conn, ptr, len);
+
+ cleanup:
+	if (output.value != NULL)
+		gss_release_buffer(&minor, &output);
+	return ret;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 94e47a5..14fba1f 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -213,6 +213,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
 	}
 	else
 #endif
+#ifdef ENABLE_GSS
+	if (conn->gctx != GSS_C_NO_CONTEXT)
+	{
+		n = pg_GSS_read(conn, ptr, len);
+	}
+	else
+#endif
 	{
 		n = pqsecure_raw_read(conn, ptr, len);
 	}
@@ -279,7 +286,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
  * to determine whether to continue/retry after error.
  */
 ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
 {
 	ssize_t		n;
 
@@ -290,6 +297,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
 	}
 	else
 #endif
+#ifdef ENABLE_GSS
+	if (conn->gctx != GSS_C_NO_CONTEXT)
+	{
+		n = pg_GSS_write(conn, ptr, len);
+	}
+	else
+#endif
 	{
 		n = pqsecure_raw_write(conn, ptr, len);
 	}
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 6c9bbf7..6b75d104 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -23,6 +23,7 @@
 /* We assume libpq-fe.h has already been included. */
 #include "postgres_fe.h"
 #include "libpq-events.h"
+#include "lib/stringinfo.h"
 
 #include <time.h>
 #include <sys/types.h>
@@ -445,6 +446,11 @@ struct pg_conn
 	gss_name_t	gtarg_nam;		/* GSS target name */
 	gss_buffer_desc ginbuf;		/* GSS input token */
 	gss_buffer_desc goutbuf;	/* GSS output token */
+	PQExpBufferData gbuf;		/* GSS encryption buffering */
+	size_t gcursor;				/* GSS buffering position */
+	bool gencrypt;				/* GSS is ready for encryption */
+	PQExpBufferData gwritebuf;	/* GSS nonblocking write buffering */
+	size_t gwritecurs;			/* GSS write buffer position */
 #endif
 
 #ifdef ENABLE_SSPI
@@ -620,7 +626,7 @@ extern void pqsecure_destroy(void);
 extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
 extern void pqsecure_close(PGconn *);
 extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
 extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
 extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
 
@@ -642,6 +648,14 @@ extern bool pgtls_read_pending(PGconn *conn);
 extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
 
 /*
+ * The GSSAPI backend in fe-gssapi.c provides these functions.
+ */
+#ifdef ENABLE_GSS
+extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
+extern ssize_t pg_GSS_write(PGconn *conn, void *ptr, size_t len);
+#endif
+
+/*
  * this is so that we can check if a connection is non-blocking internally
  * without the overhead of a function call
  */
-- 
2.7.0

