v5-GSSAPI-encryption-support.patch
text/x-diff
Filename: v5-GSSAPI-encryption-support.patch
Type: text/x-diff
Part: 0
Patch
Format: format-patch
Series: patch v5
Subject: Connect encryption support for GSSAPI
| File | + | − |
|---|---|---|
| configure | 2 | 0 |
| configure.in | 1 | 0 |
| doc/src/sgml/client-auth.sgml | 1 | 1 |
| doc/src/sgml/runtime.sgml | 14 | 6 |
| src/backend/libpq/auth.c | 3 | 327 |
| src/backend/libpq/be-gssapi.c | 583 | 0 |
| src/backend/libpq/be-secure.c | 16 | 0 |
| src/backend/libpq/Makefile | 4 | 0 |
| src/backend/postmaster/postmaster.c | 12 | 0 |
| src/include/libpq/libpq-be.h | 31 | 0 |
| src/interfaces/libpq/fe-auth.c | 0 | 182 |
| src/interfaces/libpq/fe-auth.h | 5 | 0 |
| src/interfaces/libpq/fe-connect.c | 10 | 0 |
| src/interfaces/libpq/fe-gssapi.c | 474 | 0 |
| src/interfaces/libpq/fe-secure.c | 15 | 1 |
| src/interfaces/libpq/libpq-int.h | 15 | 1 |
| src/interfaces/libpq/Makefile | 4 | 0 |
| src/Makefile.global.in | 1 | 0 |
From dc10e3519f0f6c67f79abd157dc8ff1a1c293f53 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 | 583 ++++++++++++++++++++++++++++++++++++
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 | 474 +++++++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-int.h | 16 +-
18 files changed, 1191 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 b3f3abe..a5bd629 100755
--- a/configure
+++ b/configure
@@ -713,6 +713,7 @@ with_systemd
with_selinux
with_openssl
krb_srvtab
+with_gssapi
with_python
with_perl
with_tcl
@@ -5491,6 +5492,7 @@ $as_echo "$with_gssapi" >&6; }
+
#
# Kerberos configuration parameters
#
diff --git a/configure.in b/configure.in
index 0bd90d7..4fd8f05 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 209eb9e..f76b79b 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -1915,12 +1915,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">).
@@ -2040,6 +2041,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 e94d6a5..3dbc5c2 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_systemd = @with_systemd@
with_libxml = @with_libxml@
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..d4d6c3a
--- /dev/null
+++ b/src/backend/libpq/be-gssapi.c
@@ -0,0 +1,583 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-gssapi.c
+ * GSSAPI authentication and encryption support
+ *
+ * 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 b16fc28..d2f2a63 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2355,6 +2355,10 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+#endif
#endif
return port;
@@ -2371,7 +2375,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..471c6a2
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi.c
@@ -0,0 +1,474 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth.c
+ * The front-end (client) support for GSSAPI
+ *
+ * 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