aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/catalog/system_views.sql8
-rw-r--r--src/backend/libpq/Makefile4
-rw-r--r--src/backend/libpq/auth.c115
-rw-r--r--src/backend/libpq/be-gssapi-common.c74
-rw-r--r--src/backend/libpq/be-gssapi-common.h26
-rw-r--r--src/backend/libpq/be-secure-gssapi.c627
-rw-r--r--src/backend/libpq/be-secure.c16
-rw-r--r--src/backend/libpq/hba.c51
-rw-r--r--src/backend/postmaster/pgstat.c46
-rw-r--r--src/backend/postmaster/postmaster.c40
-rw-r--r--src/backend/utils/adt/pgstatfuncs.c20
-rw-r--r--src/bin/psql/command.c17
-rw-r--r--src/include/catalog/pg_proc.dat6
-rw-r--r--src/include/libpq/hba.h4
-rw-r--r--src/include/libpq/libpq-be.h20
-rw-r--r--src/include/libpq/libpq.h3
-rw-r--r--src/include/libpq/pqcomm.h5
-rw-r--r--src/include/pgstat.h24
-rw-r--r--src/interfaces/libpq/Makefile4
-rw-r--r--src/interfaces/libpq/exports.txt2
-rw-r--r--src/interfaces/libpq/fe-auth.c84
-rw-r--r--src/interfaces/libpq/fe-connect.c236
-rw-r--r--src/interfaces/libpq/fe-gssapi-common.c130
-rw-r--r--src/interfaces/libpq/fe-gssapi-common.h23
-rw-r--r--src/interfaces/libpq/fe-secure-gssapi.c635
-rw-r--r--src/interfaces/libpq/fe-secure.c31
-rw-r--r--src/interfaces/libpq/libpq-fe.h9
-rw-r--r--src/interfaces/libpq/libpq-int.h23
-rw-r--r--src/test/kerberos/t/002_enc.pl197
-rw-r--r--src/test/regress/expected/rules.out11
-rw-r--r--src/tools/msvc/Mkvcbuild.pm10
31 files changed, 2326 insertions, 175 deletions
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3f2a7ef0158..72f786d6f8a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -787,6 +787,14 @@ CREATE VIEW pg_stat_ssl AS
S.ssl_issuer_dn AS issuer_dn
FROM pg_stat_get_activity(NULL) AS S;
+CREATE VIEW pg_stat_gssapi AS
+ SELECT
+ S.pid,
+ S.gss_auth AS gss_authenticated,
+ S.gss_princ AS principal,
+ S.gss_enc AS encrypted
+ FROM pg_stat_get_activity(NULL) AS S;
+
CREATE VIEW pg_replication_slots AS
SELECT
L.slot_name,
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30a..47efef0682d 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-common.o be-secure-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 6f03c7c2a5e..62466be7023 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -36,6 +36,7 @@
#include "port/pg_bswap.h"
#include "replication/walsender.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
#include "utils/timestamp.h"
@@ -172,12 +173,9 @@ bool pg_krb_caseins_users;
*----------------------------------------------------------------
*/
#ifdef ENABLE_GSS
-#if defined(HAVE_GSSAPI_H)
-#include <gssapi.h>
-#else
-#include <gssapi/gssapi.h>
-#endif
+#include "be-gssapi-common.h"
+static int pg_GSS_checkauth(Port *port);
static int pg_GSS_recvauth(Port *port);
#endif /* ENABLE_GSS */
@@ -383,6 +381,17 @@ ClientAuthentication(Port *port)
errmsg("connection requires a valid client certificate")));
}
+#ifdef ENABLE_GSS
+ if (port->gss->enc && port->hba->auth_method != uaReject &&
+ port->hba->auth_method != uaImplicitReject &&
+ port->hba->auth_method != uaTrust &&
+ port->hba->auth_method != uaGSS)
+ {
+ ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+ errmsg("GSSAPI encryption can only be used with gss, trust, or reject authentication methods")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -523,8 +532,14 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ port->gss->auth = true;
+ if (port->gss->enc)
+ status = pg_GSS_checkauth(port);
+ else
+ {
+ sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
+ status = pg_GSS_recvauth(port);
+ }
#else
Assert(false);
#endif
@@ -1031,68 +1046,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
*----------------------------------------------------------------
*/
#ifdef ENABLE_GSS
-
-#if defined(WIN32) && !defined(_MSC_VER)
-/*
- * 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
-
-
-/*
- * Generate an error for GSSAPI authentication. The caller should apply
- * _() to errmsg to make it translatable.
- */
-static void
-pg_GSS_error(int severity, const 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)
{
@@ -1101,7 +1054,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1254,10 +1206,23 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+/*
+ * Check whether the GSSAPI-authenticated user is allowed to connect as the
+ * claimed username.
+ */
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat,
+ min_stat,
+ lmin_s;
+ gss_buffer_desc gbuf;
/*
- * 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.
*/
@@ -1268,6 +1233,12 @@ pg_GSS_recvauth(Port *port)
maj_stat, min_stat);
/*
+ * Copy the original name of the authenticated principal into our backend
+ * memory for display later.
+ */
+ port->gss->princ = MemoryContextStrdup(TopMemoryContext, gbuf.value);
+
+ /*
* Split the username at the realm separator
*/
if (strchr(gbuf.value, '@'))
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 00000000000..40ada14bdde
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-gssapi-common.c
+ * Common code for GSSAPI authentication and encryption
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-gssapi-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "be-gssapi-common.h"
+
+/*
+ * Helper function for getting all strings of a GSSAPI error (of specified
+ * stat). Call once for GSS_CODE and once for MECH_CODE.
+ */
+static void
+pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type)
+{
+ gss_buffer_desc gmsg;
+ size_t i = 0;
+ OM_uint32 lmin_s,
+ msg_ctx = 0;
+
+ gmsg.value = NULL;
+ gmsg.length = 0;
+
+ do
+ {
+ gss_display_status(&lmin_s, stat, type,
+ GSS_C_NO_OID, &msg_ctx, &gmsg);
+ strlcpy(s + i, gmsg.value, len - i);
+ i += gmsg.length;
+ gss_release_buffer(&lmin_s, &gmsg);
+ }
+ while (msg_ctx && i < len);
+
+ if (msg_ctx || i == len)
+ ereport(WARNING,
+ (errmsg_internal("incomplete GSS error report")));
+}
+
+/*
+ * Fetch and report all error messages from GSSAPI. To avoid allocation,
+ * total error size is capped (at 128 bytes for each of major and minor). No
+ * known mechanisms will produce error messages beyond this cap.
+ */
+void
+pg_GSS_error(int severity, const char *errmsg,
+ OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+ char msg_major[128],
+ msg_minor[128];
+
+ /* Fetch major status message */
+ pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE);
+
+ /* Fetch mechanism minor status message */
+ pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE);
+
+ /*
+ * 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)));
+}
diff --git a/src/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h
new file mode 100644
index 00000000000..f6e90ea3a7c
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-gssapi-common.h
+ * Definitions for GSSAPI authentication and encryption handling
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/libpq/be-gssapi-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef BE_GSSAPI_COMMON_H
+#define BE_GSSAPI_COMMON_H
+
+#if defined(HAVE_GSSAPI_H)
+#include <gssapi.h>
+#else
+#include <gssapi/gssapi.h>
+#endif
+
+void pg_GSS_error(int severity, const char *errmsg,
+ OM_uint32 maj_stat, OM_uint32 min_stat);
+
+#endif /* BE_GSSAPI_COMMON_H */
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
new file mode 100644
index 00000000000..6089d627abb
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,627 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-gssapi.c
+ * GSSAPI encryption support
+ *
+ * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/be-secure-gssapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "be-gssapi-common.h"
+
+#include "libpq/auth.h"
+#include "libpq/libpq.h"
+#include "libpq/libpq-be.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include <unistd.h>
+
+
+/*
+ * Handle the encryption/decryption of data using GSSAPI.
+ *
+ * In the encrypted data stream on the wire, we break up the data
+ * into packets where each packet starts with a sizeof(uint32)-byte
+ * length (not allowed to be larger than the buffer sizes defined
+ * below) and then the encrypted data of that length immediately
+ * following.
+ *
+ * Encrypted data typically ends up being larger than the same data
+ * unencrypted, so we use fixed-size buffers for handling the
+ * encryption/decryption which are larger than PQComm's buffer will
+ * typically be to minimize the times where we have to make multiple
+ * packets and therefore sets of recv/send calls for a single
+ * read/write call to us.
+ *
+ * NOTE: The client and server have to agree on the max packet size,
+ * because we have to pass an entire packet to GSSAPI at a time and we
+ * don't want the other side to send arbitrairly huge packets as we
+ * would have to allocate memory for them to then pass them to GSSAPI.
+ */
+#define PQ_GSS_SEND_BUFFER_SIZE 16384
+#define PQ_GSS_RECV_BUFFER_SIZE 16384
+
+/* PqGSSSendBuffer is for *encrypted* data */
+static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE];
+static int PqGSSSendPointer; /* Next index to store a byte in
+ * PqGSSSendBuffer */
+static int PqGSSSendStart; /* Next index to send a byte in
+ * PqGSSSendBuffer */
+
+/* PqGSSRecvBuffer is for *encrypted* data */
+static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int PqGSSRecvLength; /* End of data available in PqGSSRecvBuffer */
+
+/* PqGSSResultBuffer is for *unencrypted* data */
+static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int PqGSSResultPointer; /* Next index to read a byte from
+ * PqGSSResultBuffer */
+static int PqGSSResultLength; /* End of data available in PqGSSResultBuffer */
+
+uint32 max_packet_size; /* Maximum size we can encrypt and fit the
+ * results into our output buffer */
+
+/*
+ * Attempt to write len bytes of data from ptr along a GSSAPI-encrypted connection.
+ *
+ * Connection must be fully established (including authentication step) before
+ * calling. Returns the bytes actually consumed once complete. Data is
+ * internally buffered; in the case of an incomplete write, the amount of data we
+ * processed (encrypted into our output buffer to be sent) will be returned. If
+ * an error occurs or we would block, a negative value is returned and errno is
+ * set appropriately.
+ *
+ * To continue writing in the case of EWOULDBLOCK and similar, call this function
+ * again with matching ptr and len parameters.
+ */
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ size_t bytes_to_encrypt = len;
+ size_t bytes_encrypted = 0;
+
+ /*
+ * Loop through encrypting data and sending it out until
+ * secure_raw_write() complains (which would likely mean that the socket
+ * is non-blocking and the requested send() would block, or there was some
+ * kind of actual error) and then return.
+ */
+ while (bytes_to_encrypt || PqGSSSendPointer)
+ {
+ OM_uint32 major,
+ minor;
+ gss_buffer_desc input,
+ output;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ /*
+ * Check if we have data in the encrypted output buffer that needs to
+ * be sent, and if so, try to send it. If we aren't able to, return
+ * that back up to the caller.
+ */
+ if (PqGSSSendPointer)
+ {
+ ssize_t ret;
+ ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
+
+ ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, amount);
+ if (ret <= 0)
+ {
+ /*
+ * If we encrypted some data and it's in our output buffer,
+ * but send() is saying that we would block, then tell the
+ * caller how far we got with encrypting the data so that they
+ * can call us again with whatever is left, at which point we
+ * will try to send the remaining encrypted data first and
+ * then move on to encrypting the rest of the data.
+ */
+ if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
+ return bytes_encrypted;
+ else
+ return ret;
+ }
+
+ /*
+ * Check if this was a partial write, and if so, move forward that
+ * far in our buffer and try again.
+ */
+ if (ret != amount)
+ {
+ PqGSSSendStart += ret;
+ continue;
+ }
+
+ /* All encrypted data was sent, our buffer is empty now. */
+ PqGSSSendPointer = PqGSSSendStart = 0;
+ }
+
+ /*
+ * Check if there are any bytes left to encrypt. If not, we're done.
+ */
+ if (!bytes_to_encrypt)
+ return bytes_encrypted;
+
+ /*
+ * max_packet_size is the maximum amount of unencrypted data that,
+ * when encrypted, will fit into our encrypted-data output buffer.
+ *
+ * If we are being asked to send more than max_packet_size unencrypted
+ * data, then we will loop and create multiple packets, each with
+ * max_packet_size unencrypted data encrypted in them (at least, until
+ * secure_raw_write returns a failure saying we would be blocked, at
+ * which point we will let the caller know how far we got).
+ */
+ if (bytes_to_encrypt > max_packet_size)
+ input.length = max_packet_size;
+ else
+ input.length = bytes_to_encrypt;
+
+ input.value = (char *) ptr + bytes_encrypted;
+
+ output.value = NULL;
+ output.length = 0;
+
+ /* Create the next encrypted packet */
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (major != GSS_S_COMPLETE)
+ pg_GSS_error(FATAL, gettext_noop("GSSAPI wrap error"), major, minor);
+
+ if (conf == 0)
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+
+ if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+ ereport(FATAL, (errmsg("GSSAPI tried to send packet of size: %ld", output.length)));
+
+ bytes_encrypted += input.length;
+ bytes_to_encrypt -= input.length;
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32));
+ PqGSSSendPointer += sizeof(uint32);
+
+ memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+ PqGSSSendPointer += output.length;
+ }
+
+ return bytes_encrypted;
+}
+
+/*
+ * Read up to len bytes from a GSSAPI-encrypted connection into ptr. Call
+ * only after the connection has been fully established (i.e., GSSAPI
+ * authentication is complete). On success, returns the number of bytes
+ * written into ptr; otherwise, returns -1 and sets errno appropriately.
+ */
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major,
+ minor;
+ gss_buffer_desc input,
+ output;
+ ssize_t ret;
+ size_t bytes_to_return = len;
+ size_t bytes_returned = 0;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ /*
+ * The goal here is to read an incoming encrypted packet, one at a time,
+ * decrypt it into our out buffer, returning to the caller what they asked
+ * for, and then saving anything else for the next call.
+ *
+ * First we look to see if we have unencrypted bytes available and, if so,
+ * copy those to the result. If the caller asked for more than we had
+ * immediately available, then we try to read a packet off the wire and
+ * decrypt it. If the read would block, then return the amount of
+ * unencrypted data we copied into the caller's ptr.
+ */
+ while (bytes_to_return)
+ {
+ /* Check if we have data in our buffer that we can return immediately */
+ if (PqGSSResultPointer < PqGSSResultLength)
+ {
+ int bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
+ int bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
+
+ /*
+ * Copy the data from our output buffer into the caller's buffer,
+ * at the point where we last left off filling their buffer
+ */
+ memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultPointer, bytes_to_copy);
+ PqGSSResultPointer += bytes_to_copy;
+ bytes_to_return -= bytes_to_copy;
+ bytes_returned += bytes_to_copy;
+
+ /* Check if our result buffer is now empty and, if so, reset */
+ if (PqGSSResultPointer == PqGSSResultLength)
+ PqGSSResultPointer = PqGSSResultLength = 0;
+
+ continue;
+ }
+
+ /*
+ * At this point, our output buffer should be empty with more bytes
+ * being requested to be read. We are now ready to load the next
+ * packet and decrypt it (entirely) into our buffer.
+ *
+ * If we get a partial read back while trying to read a packet off the
+ * wire then we return the number of unencrypted bytes we were able to
+ * copy (if any, if we didn't copy any, then we return whatever
+ * secure_raw_read returned when we called it; likely -1) into the
+ * caller's ptr and wait to be called again, until we get a full
+ * packet to decrypt.
+ */
+
+ /* Check if we have the size of the packet already in our buffer. */
+ if (PqGSSRecvLength < sizeof(uint32))
+ {
+ /*
+ * We were not able to get the length of the packet last time, so
+ * we need to do that first.
+ */
+ ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength,
+ sizeof(uint32) - PqGSSRecvLength);
+ if (ret < 0)
+ return bytes_returned ? bytes_returned : ret;
+
+ PqGSSRecvLength += ret;
+
+ /*
+ * If we only got part of the packet length, then return however
+ * many unencrypted bytes we copied to the caller and wait to be
+ * called again.
+ */
+ if (PqGSSRecvLength < sizeof(uint32))
+ return bytes_returned;
+ }
+
+ /*
+ * We have the length of the next packet at this point, so pull it out
+ * and then read whatever we have left of the packet to read.
+ */
+ input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+
+ /* Check for over-length packet */
+ if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
+ ereport(FATAL, (errmsg("Over-size GSSAPI packet sent by the client.")));
+
+ /*
+ * Read as much of the packet as we are able to on this call into
+ * wherever we left off from the last time we were called.
+ */
+ ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength,
+ input.length - (PqGSSRecvLength - sizeof(uint32)));
+ if (ret < 0)
+ return bytes_returned ? bytes_returned : ret;
+
+ PqGSSRecvLength += ret;
+
+ /*
+ * If we got less than the rest of the packet then we need to return
+ * and be called again. If we didn't have any bytes to return on this
+ * run then return -1 and set errno to EWOULDBLOCK.
+ */
+ if (PqGSSRecvLength - sizeof(uint32) < input.length)
+ {
+ if (!bytes_returned)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+
+ return bytes_returned;
+ }
+
+ /*
+ * We now have the full packet and we can perform the decryption and
+ * refill our output buffer, then loop back up to pass that back to
+ * the user.
+ */
+ output.value = NULL;
+ output.length = 0;
+ input.value = PqGSSRecvBuffer + sizeof(uint32);
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (major != GSS_S_COMPLETE)
+ pg_GSS_error(FATAL, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+
+ if (conf == 0)
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+
+ memcpy(PqGSSResultBuffer, output.value, output.length);
+
+ PqGSSResultLength = output.length;
+
+ /* Our buffer is now empty, reset it */
+ PqGSSRecvLength = 0;
+
+ gss_release_buffer(&minor, &output);
+ }
+
+ return bytes_returned;
+}
+
+/*
+ * Read the specified number of bytes off the wire, waiting using
+ * WaitLatchOrSocket if we would block.
+ *
+ * Results are read into PqGSSRecvBuffer.
+ *
+ * Will always return either -1, to indicate a permanent error, or len.
+ */
+static ssize_t
+read_or_wait(Port *port, ssize_t len)
+{
+ ssize_t ret;
+
+ /*
+ * Keep going until we either read in everything we were asked to, or we
+ * error out.
+ */
+ while (PqGSSRecvLength != len)
+ {
+ ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, len - PqGSSRecvLength);
+
+ /*
+ * If we got back an error and it wasn't just EWOULDBLOCK/EAGAIN, then
+ * give up.
+ */
+ if (ret < 0 && !(errno == EWOULDBLOCK || errno == EAGAIN))
+ return -1;
+
+ /*
+ * Ok, we got back either a positive value, zero, or a negative result
+ * but EWOULDBLOCK or EAGAIN was set.
+ *
+ * If it was zero or negative, then we try to wait on the socket to be
+ * readable again.
+ */
+ if (ret <= 0)
+ {
+ /*
+ * If we got back less than zero, indicating an error, and that
+ * wasn't just a EWOULDBOCK/EAGAIN, then give up.
+ */
+ if (ret < 0 && !(errno == EWOULDBLOCK || errno == EAGAIN))
+ return -1;
+
+ /*
+ * We got back either zero, or -1 with EWOULDBLOCK/EAGAIN, so wait
+ * on socket to be readable again.
+ */
+ WaitLatchOrSocket(MyLatch,
+ WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH,
+ port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
+
+ /*
+ * If we got back zero bytes, and then waited on the socket to be
+ * readable and got back zero bytes on a second read, then this is
+ * EOF and the client hung up on us.
+ *
+ * If we did get data here, then we can just fall through and
+ * handle it just as if we got data the first time.
+ *
+ * Otherwise loop back to the top and try again.
+ */
+ if (ret == 0)
+ {
+ ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, len - PqGSSRecvLength);
+ if (ret == 0)
+ return -1;
+ }
+ else
+ continue;
+ }
+
+ PqGSSRecvLength += ret;
+ }
+
+ return len;
+}
+
+/*
+ * Start up a GSSAPI-encrypted connection. This performs GSSAPI
+ * authentication; after this function completes, it is safe to call
+ * be_gssapi_read and be_gssapi_write. Returns -1 and logs on failure;
+ * otherwise, returns 0 and marks the connection as ready for GSSAPI
+ * encryption.
+ *
+ * Note that unlike the be_gssapi_read/be_gssapi_write functions, this
+ * function WILL block on the socket to be ready for read/write (using
+ * WaitLatchOrSocket) as appropriate while establishing the GSSAPI
+ * session.
+ */
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ bool complete_next = false;
+ OM_uint32 major,
+ minor;
+
+ /* initialize state variables */
+ PqGSSSendPointer = PqGSSSendStart = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
+
+ /*
+ * Use the configured keytab, if there is one. Unfortunately, Heimdal
+ * doesn't support the cred store extensions, so use the env var.
+ */
+ if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0)
+ setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1);
+
+ while (true)
+ {
+ ssize_t ret;
+ gss_buffer_desc input,
+ output = GSS_C_EMPTY_BUFFER;
+
+ /*
+ * The client always sends first, so try to go ahead and read the
+ * length and wait on the socket to be readable again if that fails.
+ */
+ ret = read_or_wait(port, sizeof(uint32));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Get the length for this packet from the length header.
+ */
+ input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+
+ /* Done with the length, reset our buffer */
+ PqGSSRecvLength = 0;
+
+ /*
+ * During initialization, packets are always fully consumed and
+ * shouldn't ever be over PQ_GSS_RECV_BUFFER_SIZE in length.
+ *
+ * Verify on our side that the client doesn't do something funny.
+ */
+ if (input.length > PQ_GSS_RECV_BUFFER_SIZE)
+ ereport(FATAL, (errmsg("Over-size GSSAPI packet sent by the client: %ld", input.length)));
+
+ /*
+ * Get the rest of the packet so we can pass it to GSSAPI to accept
+ * the context.
+ */
+ ret = read_or_wait(port, input.length);
+ if (ret < 0)
+ return ret;
+
+ input.value = PqGSSRecvBuffer;
+
+ /* Process incoming data. (The client sends first.) */
+ major = gss_accept_sec_context(&minor, &port->gss->ctx,
+ GSS_C_NO_CREDENTIAL, &input,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &port->gss->name, NULL, &output, NULL,
+ NULL, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"),
+ major, minor);
+ gss_release_buffer(&minor, &output);
+ return -1;
+ }
+ else if (!(major & GSS_S_CONTINUE_NEEDED))
+ {
+ /*
+ * rfc2744 technically permits context negotiation to be complete
+ * both with and without a packet to be sent.
+ */
+ complete_next = true;
+ }
+
+ /* Done handling the incoming packet, reset our buffer */
+ PqGSSRecvLength = 0;
+
+ /*
+ * Check if we have data to send and, if we do, make sure to send it
+ * all
+ */
+ if (output.length != 0)
+ {
+ uint32 netlen = htonl(output.length);
+
+ if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+ ereport(FATAL, (errmsg("GSSAPI tried to send oversize packet")));
+
+ memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
+ PqGSSSendPointer += sizeof(uint32);
+
+ memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+ PqGSSSendPointer += output.length;
+
+ while (PqGSSSendStart != sizeof(uint32) + output.length)
+ {
+ ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, sizeof(uint32) + output.length - PqGSSSendStart);
+ if (ret <= 0)
+ {
+ WaitLatchOrSocket(MyLatch,
+ WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH,
+ port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ PqGSSSendStart += ret;
+ }
+
+ /* Done sending the packet, reset our buffer */
+ PqGSSSendStart = PqGSSSendPointer = 0;
+
+ gss_release_buffer(&minor, &output);
+ }
+
+ /*
+ * If we got back that the connection is finished being set up, now
+ * that's we've sent the last packet, exit our loop.
+ */
+ if (complete_next)
+ break;
+ }
+
+ /*
+ * Determine the max packet size which will fit in our buffer, after
+ * accounting for the length
+ */
+ major = gss_wrap_size_limit(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
+
+ if (GSS_ERROR(major))
+ pg_GSS_error(FATAL, gettext_noop("GSSAPI size check error"),
+ major, minor);
+
+ port->gss->enc = true;
+
+ return 0;
+}
+
+/*
+ * Return if GSSAPI authentication was used on this connection.
+ */
+bool
+be_gssapi_get_auth(Port *port)
+{
+ if (!port || !port->gss)
+ return false;
+
+ return port->gss->auth;
+}
+
+/*
+ * Return if GSSAPI encryption is enabled and being used on this connection.
+ */
+bool
+be_gssapi_get_enc(Port *port)
+{
+ if (!port || !port->gss)
+ return false;
+
+ return port->gss->enc;
+}
+
+/*
+ * Return the GSSAPI principal used for authentication on this connection.
+ */
+const char *
+be_gssapi_get_princ(Port *port)
+{
+ if (!port || !port->gss->auth)
+ return NULL;
+
+ return port->gss->princ;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index a7def3168d1..b90eb0ab6b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -160,6 +160,14 @@ retry:
}
else
#endif
+#ifdef ENABLE_GSS
+ if (port->gss->enc)
+ {
+ n = be_gssapi_read(port, ptr, len);
+ waitfor = WL_SOCKET_READABLE;
+ }
+ else
+#endif
{
n = secure_raw_read(port, ptr, len);
waitfor = WL_SOCKET_READABLE;
@@ -265,6 +273,14 @@ retry:
}
else
#endif
+#ifdef ENABLE_GSS
+ if (port->gss->enc)
+ {
+ 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/libpq/hba.c b/src/backend/libpq/hba.c
index ce9bca868cc..37d5ad44a54 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
}
else if (strcmp(token->string, "host") == 0 ||
strcmp(token->string, "hostssl") == 0 ||
- strcmp(token->string, "hostnossl") == 0)
+ strcmp(token->string, "hostnossl") == 0 ||
+ strcmp(token->string, "hostgssenc") == 0 ||
+ strcmp(token->string, "hostnogssenc") == 0)
{
if (token->string[4] == 's') /* "hostssl" */
@@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
*err_msg = "hostssl record cannot match because SSL is not supported by this build";
#endif
}
- else if (token->string[4] == 'n') /* "hostnossl" */
+ else if (token->string[4] == 'g') /* "hostgssenc" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgssenc record cannot match because GSSAPI is not supported by this build"),
+ errhint("Compile with --with-gssapi to use GSSAPI connections."),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
+#endif
}
+ else if (token->string[4] == 'n' && token->string[6] == 's')
+ parsedline->conntype = ctHostNoSSL;
+ else if (token->string[4] == 'n' && token->string[6] == 'g')
+ parsedline->conntype = ctHostNoGSS;
else
{
/* "host" */
@@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
*err_msg = "gssapi authentication is not supported on local sockets";
return NULL;
}
+ if (parsedline->conntype == ctHostGSS &&
+ parsedline->auth_method != uaGSS &&
+ parsedline->auth_method != uaReject &&
+ parsedline->auth_method != uaTrust)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, HbaFileName)));
+ *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion";
+ return NULL;
+ }
if (parsedline->conntype != ctLocal &&
parsedline->auth_method == uaPeer)
@@ -2078,6 +2106,17 @@ check_hba(hbaPort *port)
continue;
}
+ /* Check GSSAPI state */
+#ifdef ENABLE_GSS
+ if (port->gss->enc && hba->conntype == ctHostNoGSS)
+ continue;
+ else if (!port->gss->enc && hba->conntype == ctHostGSS)
+ continue;
+#else
+ if (hba->conntype == ctHostGSS)
+ continue;
+#endif
+
/* Check IP address */
switch (hba->ip_cmp_method)
{
@@ -2414,6 +2453,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgssenc";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogssenc";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 2a8472b91ae..0355fa65fb8 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -2634,6 +2634,9 @@ static Size BackendActivityBufferSize = 0;
#ifdef USE_SSL
static PgBackendSSLStatus *BackendSslStatusBuffer = NULL;
#endif
+#ifdef ENABLE_GSS
+static PgBackendGSSStatus *BackendGssStatusBuffer = NULL;
+#endif
/*
@@ -2766,6 +2769,28 @@ CreateSharedBackendStatus(void)
}
}
#endif
+
+#ifdef ENABLE_GSS
+ /* Create or attach to the shared GSSAPI status buffer */
+ size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots);
+ BackendGssStatusBuffer = (PgBackendGSSStatus *)
+ ShmemInitStruct("Backend GSS Status Buffer", size, &found);
+
+ if (!found)
+ {
+ PgBackendGSSStatus *ptr;
+
+ MemSet(BackendGssStatusBuffer, 0, size);
+
+ /* Initialize st_gssstatus pointers. */
+ ptr = BackendGssStatusBuffer;
+ for (i = 0; i < NumBackendStatSlots; i++)
+ {
+ BackendStatusArray[i].st_gssstatus = ptr;
+ ptr++;
+ }
+ }
+#endif
}
@@ -2953,6 +2978,24 @@ pgstat_bestart(void)
#else
beentry->st_ssl = false;
#endif
+
+#ifdef ENABLE_GSS
+ if (MyProcPort && MyProcPort->gss != NULL)
+ {
+ beentry->st_gss = true;
+ beentry->st_gssstatus->gss_auth = be_gssapi_get_auth(MyProcPort);
+ beentry->st_gssstatus->gss_enc = be_gssapi_get_enc(MyProcPort);
+
+ if (beentry->st_gssstatus->gss_auth)
+ strlcpy(beentry->st_gssstatus->gss_princ, be_gssapi_get_princ(MyProcPort), NAMEDATALEN);
+ }
+ else
+ {
+ beentry->st_gss = false;
+ }
+#else
+ beentry->st_gss = false;
+#endif
beentry->st_state = STATE_UNDEFINED;
beentry->st_appname[0] = '\0';
beentry->st_activity_raw[0] = '\0';
@@ -3595,6 +3638,9 @@ pgstat_get_wait_client(WaitEventClient w)
case WAIT_EVENT_WAL_SENDER_WRITE_DATA:
event_name = "WalSenderWriteData";
break;
+ case WAIT_EVENT_GSS_OPEN_SERVER:
+ event_name = "GSSOpenServer";
+ break;
/* no default case, so that compiler will warn */
}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index fe599632d3d..067487fdcb0 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1889,9 +1889,12 @@ initMasks(fd_set *rmask)
* if that's what you want. Return STATUS_ERROR if you don't want to
* send anything to the client, which would typically be appropriate
* if we detect a communications failure.)
+ *
+ * Set secure_done when negotiation of an encrypted layer (currently, TLS or
+ * GSSAPI) is already completed.
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1924,9 +1927,10 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes(((char *) &len) + 1, 3) == EOF)
{
/* Got a partial length word, so bleat about that */
- ereport(COMMERROR,
- (errcode(ERRCODE_PROTOCOL_VIOLATION),
- errmsg("incomplete startup packet")));
+ if (!secure_done)
+ ereport(COMMERROR,
+ (errcode(ERRCODE_PROTOCOL_VIOLATION),
+ errmsg("incomplete startup packet")));
return STATUS_ERROR;
}
@@ -1975,7 +1979,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -2008,6 +2012,32 @@ retry1:
/* but not another SSL negotiation request */
return ProcessStartupPacket(port, true);
}
+ else if (proto == NEGOTIATE_GSS_CODE && !secure_done)
+ {
+ char GSSok = 'N';
+#ifdef ENABLE_GSS
+ /* No GSSAPI encryption when on Unix socket */
+ if (!IS_AF_UNIX(port->laddr.addr.ss_family))
+ GSSok = 'G';
+#endif
+
+ while (send(port->sock, &GSSok, 1, 0) != 1)
+ {
+ if (errno == EINTR)
+ continue;
+ ereport(COMMERROR,
+ (errcode_for_socket_access(),
+ errmsg("failed to send GSSAPI negotiation response: %m)")));
+ return STATUS_ERROR; /* close the connection */
+ }
+
+#ifdef ENABLE_GSS
+ if (GSSok == 'G' && secure_open_gssapi(port) == -1)
+ return STATUS_ERROR;
+#endif
+ /* Won't ever see more than one negotiation request */
+ return ProcessStartupPacket(port, true);
+ }
/* Could add additional special packet types here */
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 7c2afe64272..9a1d07bee33 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -545,7 +545,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
Datum
pg_stat_get_activity(PG_FUNCTION_ARGS)
{
-#define PG_STAT_GET_ACTIVITY_COLS 26
+#define PG_STAT_GET_ACTIVITY_COLS 29
int num_backends = pgstat_fetch_stat_numbackends();
int curr_backend;
int pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -859,6 +859,21 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
values[18] = BoolGetDatum(false); /* ssl */
nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = nulls[25] = true;
}
+
+ /* GSSAPI information */
+ if (beentry->st_gss)
+ {
+ values[26] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */
+ values[27] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ);
+ values[28] = BoolGetDatum(beentry->st_gssstatus->gss_enc); /* GSS Encryption in use */
+ }
+ else
+ {
+ values[26] = BoolGetDatum(false); /* gss_auth */
+ nulls[27] = true; /* No GSS principal */
+ values[28] = BoolGetDatum(false); /* GSS Encryption not in
+ * use */
+ }
}
else
{
@@ -883,6 +898,9 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
nulls[23] = true;
nulls[24] = true;
nulls[25] = true;
+ nulls[26] = true;
+ nulls[27] = true;
+ nulls[28] = true;
}
tuplestore_putvalues(tupstore, tupdesc, values, nulls);
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 72188b7f3ef..bc3d10e5158 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -160,6 +160,7 @@ static void print_with_linenumbers(FILE *output, char *lines,
static void minimal_error_message(PGresult *res);
static void printSSLInfo(void);
+static void printGSSInfo(void);
static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
static char *pset_value_string(const char *param, struct printQueryOpt *popt);
@@ -621,6 +622,7 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
db, PQuser(pset.db), host, PQport(pset.db));
}
printSSLInfo();
+ printGSSInfo();
}
}
@@ -3184,6 +3186,7 @@ connection_warnings(bool in_startup)
checkWin32Codepage();
#endif
printSSLInfo();
+ printGSSInfo();
}
}
@@ -3216,6 +3219,20 @@ printSSLInfo(void)
(compression && strcmp(compression, "off") != 0) ? _("on") : _("off"));
}
+/*
+ * printGSSInfo
+ *
+ * Prints information about the current GSSAPI connection, if GSSAPI encryption is in use
+ */
+static void
+printGSSInfo(void)
+{
+ if (!PQgssEncInUse(pset.db))
+ return; /* no GSSAPI encryption in use */
+
+ printf(_("GSSAPI Encrypted connection\n"));
+}
+
/*
* checkWin32Codepage
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index a7050edca09..fb257c17c89 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5117,9 +5117,9 @@
proname => 'pg_stat_get_activity', prorows => '100', proisstrict => 'f',
proretset => 't', provolatile => 's', proparallel => 'r',
prorettype => 'record', proargtypes => 'int4',
- proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,bool,text,numeric,text}',
- proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
- proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,sslcompression,ssl_client_dn,ssl_client_serial,ssl_issuer_dn}',
+ proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,bool,text,numeric,text,bool,text,bool}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,sslcompression,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc}',
prosrc => 'pg_stat_get_activity' },
{ oid => '3318',
descr => 'statistics: information about progress of backends running maintenance command',
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index c65eb9dc8a5..186e4335748 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -55,7 +55,9 @@ typedef enum ConnType
ctLocal,
ctHost,
ctHostSSL,
- ctHostNoSSL
+ ctHostNoSSL,
+ ctHostGSS,
+ ctHostNoGSS,
} ConnType;
typedef enum ClientCertMode
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 248055f10b4..059218c85a3 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -86,6 +86,10 @@ 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 */
+ char *princ; /* GSSAPI Principal used for auth, NULL if
+ * GSSAPI auth was not used */
+ bool auth; /* GSSAPI Authentication used */
+ bool enc; /* GSSAPI encryption in use */
#endif
} pg_gssinfo;
#endif
@@ -164,6 +168,9 @@ typedef struct Port
int keepalives_interval;
int keepalives_count;
+ /*
+ * GSSAPI structures.
+ */
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/*
@@ -263,6 +270,13 @@ extern void be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len);
extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
/*
+ * Return information about the GSSAPI authenticated connection
+ */
+extern bool be_gssapi_get_auth(Port *port);
+extern bool be_gssapi_get_enc(Port *port);
+extern const char *be_gssapi_get_princ(Port *port);
+
+/*
* Get the server certificate hash for SCRAM channel binding type
* tls-server-end-point.
*
@@ -279,6 +293,12 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif /* USE_SSL */
+#ifdef ENABLE_GSS
+/* Read and write to a GSSAPI-encrypted connection. */
+extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len);
+extern ssize_t be_gssapi_write(Port *port, void *ptr, size_t len);
+#endif /* ENABLE_GSS */
+
extern ProtocolVersion FrontendProtocol;
/* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 755819cc584..41f9257aa9d 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -93,6 +93,9 @@ extern ssize_t secure_read(Port *port, void *ptr, size_t len);
extern ssize_t secure_write(Port *port, void *ptr, size_t len);
extern ssize_t secure_raw_read(Port *port, void *ptr, size_t len);
extern ssize_t secure_raw_write(Port *port, const void *ptr, size_t len);
+#ifdef ENABLE_GSS
+extern ssize_t secure_open_gssapi(Port *port);
+#endif
extern bool ssl_loaded_verify_locations;
diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h
index 5b84bdda991..baf6a4b6c02 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -199,9 +199,10 @@ typedef struct CancelRequestPacket
/*
- * A client can also start by sending a SSL negotiation request, to get a
- * secure channel.
+ * A client can also start by sending a SSL or GSSAPI negotiation request to
+ * get a secure channel.
*/
#define NEGOTIATE_SSL_CODE PG_PROTOCOL(1234,5679)
+#define NEGOTIATE_GSS_CODE PG_PROTOCOL(1234,5680)
#endif /* PQCOMM_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 53d4a9c4319..5888242f757 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -801,7 +801,8 @@ typedef enum
WAIT_EVENT_SSL_OPEN_SERVER,
WAIT_EVENT_WAL_RECEIVER_WAIT_START,
WAIT_EVENT_WAL_SENDER_WAIT_WAL,
- WAIT_EVENT_WAL_SENDER_WRITE_DATA
+ WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+ WAIT_EVENT_GSS_OPEN_SERVER,
} WaitEventClient;
/* ----------
@@ -989,6 +990,23 @@ typedef struct PgBackendSSLStatus
char ssl_issuer_dn[NAMEDATALEN];
} PgBackendSSLStatus;
+/*
+ * PgBackendGSSStatus
+ *
+ * For each backend, we keep the GSS status in a separate struct, that
+ * is only filled in if GSS is enabled.
+ *
+ * All char arrays must be null-terminated.
+ */
+typedef struct PgBackendGSSStatus
+{
+ /* Information about GSSAPI connection */
+ char gss_princ[NAMEDATALEN]; /* GSSAPI Principal used to auth */
+ bool gss_auth; /* If GSSAPI authentication was used */
+ bool gss_enc; /* If encryption is being used */
+
+} PgBackendGSSStatus;
+
/* ----------
* PgBackendStatus
@@ -1043,6 +1061,10 @@ typedef struct PgBackendStatus
bool st_ssl;
PgBackendSSLStatus *st_sslstatus;
+ /* Information about GSSAPI connection */
+ bool st_gss;
+ PgBackendGSSStatus *st_gssstatus;
+
/* current state */
BackendState st_state;
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index 025542dfe9f..c734965d63f 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -38,6 +38,10 @@ ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o fe-secure-common.o
endif
+ifeq ($(with_gssapi),yes)
+OBJS += fe-gssapi-common.o fe-secure-gssapi.o
+endif
+
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index cc9ee9ce6b8..7c808e5215c 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -174,3 +174,5 @@ PQresultVerboseErrorMessage 171
PQencryptPasswordConn 172
PQresultMemorySize 173
PQhostaddr 174
+PQgssEncInUse 175
+PQgetgssctx 176
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 4cbe64ceb58..624e02bcaae 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -49,52 +49,7 @@
* GSSAPI authentication system.
*/
-#if defined(WIN32) && !defined(_MSC_VER)
-/*
- * 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);
-}
+#include "fe-gssapi-common.h"
/*
* Continue GSS authentication with next token as needed.
@@ -195,10 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen)
static int
pg_GSS_startup(PGconn *conn, int payloadlen)
{
- OM_uint32 maj_stat,
- min_stat;
- int maxlen;
- gss_buffer_desc temp_gbuf;
+ int ret;
char *host = conn->connhost[conn->whichhost].host;
if (!(host && host[0] != '\0'))
@@ -215,33 +167,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen)
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, host);
- 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;
- }
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return ret;
/*
* Initial packet is the same as a continuation packet with no initial
@@ -977,7 +905,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSPI authentication not supported\n"));
return STATUS_ERROR;
-#endif /* !define(ENABLE_GSSAPI) */
+#endif /* !define(ENABLE_GSS) */
#endif /* ENABLE_SSPI */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e3bf6a7449f..68cf4224570 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#else
#define DefaultSSLMode "disable"
#endif
+#ifdef ENABLE_GSS
+#include "fe-gssapi-common.h"
+#define DefaultGSSMode "prefer"
+#else
+#define DefaultGSSMode "disable"
+#endif
/* ----------
* Definition of the conninfo parameters and their fallback resources.
@@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssencmode similarly to sslmode - we can still handle "disable"
+ * and "prefer".
+ */
+ {"gssencmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssencmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1227,6 +1241,39 @@ connectOptions2(PGconn *conn)
}
/*
+ * validate gssencmode option
+ */
+ if (conn->gssencmode)
+ {
+ if (strcmp(conn->gssencmode, "disable") != 0 &&
+ strcmp(conn->gssencmode, "prefer") != 0 &&
+ strcmp(conn->gssencmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssencmode value: \"%s\"\n"),
+ conn->gssencmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssencmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssencmode = strdup(DefaultGSSMode);
+ if (!conn->gssencmode)
+ goto oom_error;
+ }
+
+ /*
* Resolve special "auto" client_encoding from the locale
*/
if (conn->client_encoding_initial &&
@@ -1827,6 +1874,11 @@ connectDBStart(PGconn *conn)
*/
resetPQExpBuffer(&conn->errorMessage);
+#ifdef ENABLE_GSS
+ if (conn->gssencmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
@@ -2099,6 +2151,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2640,17 +2693,57 @@ keep_going: /* We will come back to here until there is
}
#endif /* HAVE_UNIX_SOCKETS */
+ if (IS_AF_UNIX(conn->raddr.addr.ss_family))
+ {
+ /* Don't request SSL or GSSAPI over Unix sockets */
#ifdef USE_SSL
+ conn->allow_ssl_try = false;
+#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = false;
+#endif
+ }
+
+#ifdef ENABLE_GSS
/*
- * If SSL is enabled and we haven't already got it running,
- * request it instead of sending the startup message.
+ * If GSSAPI is enabled and we have a ccache, try to set it up
+ * before sending startup messages. If it's already
+ * operating, don't try SSL and instead just build the startup
+ * packet.
*/
- if (IS_AF_UNIX(conn->raddr.addr.ss_family))
+ if (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
{
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
+ ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE);
+
+ if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
+ SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+ goto error_return;
+ }
+
+ /* Ok, wait for response */
+ conn->status = CONNECTION_GSS_STARTUP;
+ return PGRES_POLLING_READING;
}
+ else if (!conn->gctx && conn->gssencmode[0] == 'r')
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache, no server support, or using a local socket)\n"));
+ goto error_return;
+ }
+#endif
+
+#ifdef USE_SSL
+
+ /*
+ * If SSL is enabled and we haven't already got it running,
+ * request it instead of sending the startup message.
+ */
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2844,6 +2937,98 @@ keep_going: /* We will come back to here until there is
#endif /* USE_SSL */
}
+ case CONNECTION_GSS_STARTUP:
+ {
+#ifdef ENABLE_GSS
+ PostgresPollingStatusType pollres;
+
+ /*
+ * If we haven't yet, get the postmaster's response to our
+ * negotiation packet
+ */
+ if (conn->try_gss && !conn->gctx)
+ {
+ char gss_ok;
+ int rdresult = pqReadData(conn);
+
+ if (rdresult < 0)
+ /* pqReadData fills in error message */
+ goto error_return;
+ else if (rdresult == 0)
+ /* caller failed to wait for data */
+ return PGRES_POLLING_READING;
+ if (pqGetc(&gss_ok, conn) < 0)
+ /* shouldn't happen... */
+ return PGRES_POLLING_READING;
+
+ if (gss_ok == 'E')
+ {
+ /*
+ * Server failure of some sort. Assume it's a
+ * protocol version support failure, and let's see if
+ * we can't recover (if it's not, we'll get a better
+ * error message on retry). Server gets fussy if we
+ * don't hang up the socket, though.
+ */
+ conn->try_gss = false;
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_NEEDED;
+ goto keep_going;
+ }
+
+ /* mark byte consumed */
+ conn->inStart = conn->inCursor;
+
+ if (gss_ok == 'N')
+ {
+ /* Server doesn't want GSSAPI; fall back if we can */
+ if (conn->gssencmode[0] == 'r')
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
+ goto error_return;
+ }
+
+ conn->try_gss = false;
+ conn->status = CONNECTION_MADE;
+ return PGRES_POLLING_WRITING;
+ }
+ else if (gss_ok != 'G')
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
+ gss_ok);
+ goto error_return;
+ }
+ }
+
+ /* Begin or continue GSSAPI negotiation */
+ pollres = pqsecure_open_gss(conn);
+ if (pollres == PGRES_POLLING_OK)
+ {
+ /* All set for startup packet */
+ conn->status = CONNECTION_MADE;
+ return PGRES_POLLING_WRITING;
+ }
+ else if (pollres == PGRES_POLLING_FAILED &&
+ conn->gssencmode[0] == 'p')
+ {
+ /*
+ * We failed, but we can retry on "prefer". Have to drop
+ * the current connection to do so, though.
+ */
+ conn->try_gss = false;
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_NEEDED;
+ goto keep_going;
+ }
+ return pollres;
+#else /* !ENABLE_GSS */
+ /* unreachable */
+ goto error_return;
+#endif /* ENABLE_GSS */
+ }
+
/*
* Handle authentication exchange: wait for postmaster messages
* and respond as necessary.
@@ -2997,6 +3182,26 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
+#ifdef ENABLE_GSS
+
+ /*
+ * If gssencmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssencmode[0] == 'p')
+ {
+ OM_uint32 minor;
+
+ /* postmaster expects us to drop the connection */
+ conn->try_gss = false;
+ conn->gssenc = false;
+ gss_delete_sec_context(&minor, &conn->gctx, NULL);
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_NEEDED;
+ goto keep_going;
+ }
+#endif
+
#ifdef USE_SSL
/*
@@ -3564,6 +3769,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3695,10 +3903,28 @@ freePGconn(PGconn *conn)
free(conn->requirepeer);
if (conn->connip)
free(conn->connip);
+ if (conn->gssencmode)
+ free(conn->gssencmode);
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
if (conn->krbsrvname)
free(conn->krbsrvname);
#endif
+#ifdef ENABLE_GSS
+ if (conn->gcred != GSS_C_NO_CREDENTIAL)
+ {
+ OM_uint32 minor;
+
+ gss_release_cred(&minor, &conn->gcred);
+ conn->gcred = GSS_C_NO_CREDENTIAL;
+ }
+ if (conn->gctx)
+ {
+ OM_uint32 minor;
+
+ gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER);
+ conn->gctx = NULL;
+ }
+#endif
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib)
free(conn->gsslib);
diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c
new file mode 100644
index 00000000000..3192f9190b5
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,130 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-gssapi-common.c
+ * The front-end (client) GSSAPI common code
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-gssapi-common.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "fe-gssapi-common.h"
+
+#include "libpq-int.h"
+#include "pqexpbuffer.h"
+
+/*
+ * 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.
+ */
+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);
+}
+
+/*
+ * Check if we can acquire credentials at all (and yield them if so).
+ */
+bool
+pg_GSS_have_ccache(gss_cred_id_t *cred_out)
+{
+ OM_uint32 major,
+ minor;
+ gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+
+ major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET,
+ GSS_C_INITIATE, &cred, NULL, NULL);
+ if (major != GSS_S_COMPLETE)
+ {
+ *cred_out = NULL;
+ return false;
+ }
+ *cred_out = cred;
+ return true;
+}
+
+/*
+ * Try to load service name for a connection
+ */
+int
+pg_GSS_load_servicename(PGconn *conn)
+{
+ OM_uint32 maj_stat,
+ min_stat;
+ int maxlen;
+ gss_buffer_desc temp_gbuf;
+ char *host;
+
+ if (conn->gtarg_nam != NULL)
+ /* Already taken care of - move along */
+ return STATUS_OK;
+
+ host = PQhost(conn);
+ if (!(host && host[0] != '\0'))
+ {
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("host name must be specified\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, host);
+ 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;
+ }
+ return STATUS_OK;
+}
diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h
new file mode 100644
index 00000000000..b429e79848b
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.h
@@ -0,0 +1,23 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-gssapi-common.h
+ *
+ * Definitions for GSSAPI common routines
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/interfaces/libpq/fe-gssapi-common.h
+ */
+
+#ifndef FE_GSSAPI_COMMON_H
+#define FE_GSSAPI_COMMON_H
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+
+void pg_GSS_error(const char *mprefix, PGconn *conn,
+ OM_uint32 maj_stat, OM_uint32 min_stat);
+bool pg_GSS_have_ccache(gss_cred_id_t *cred_out);
+int pg_GSS_load_servicename(PGconn *conn);
+#endif /* FE_GSSAPI_COMMON_H */
diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
new file mode 100644
index 00000000000..ea1c1cd7b71
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,635 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-gssapi.c
+ * The front-end (client) encryption support for GSSAPI
+ *
+ * Portions Copyright (c) 2016-2018, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/interfaces/libpq/fe-secure-gssapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "fe-gssapi-common.h"
+
+#include "port/pg_bswap.h"
+
+/*
+ * Require encryption support, as well as mutual authentication and
+ * tamperproofing measures.
+ */
+#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \
+ GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG
+
+/*
+ * We use fixed-size buffers for handling the encryption/decryption
+ * which are larger than PQComm's buffer will typically be to minimize
+ * the times where we have to make multiple packets and therefore sets
+ * of recv/send calls for a single read/write call to us.
+ *
+ * NOTE: The client and server have to agree on the max packet size,
+ * because we have to pass an entire packet to GSSAPI at a time and we
+ * don't want the other side to send arbitrairly huge packets as we
+ * would have to allocate memory for them to then pass them to GSSAPI.
+ */
+#define PQ_GSS_SEND_BUFFER_SIZE 16384
+#define PQ_GSS_RECV_BUFFER_SIZE 16384
+
+/* PqGSSSendBuffer is for *encrypted* data */
+static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE];
+static int PqGSSSendPointer; /* Next index to store a byte in
+ * PqGSSSendBuffer */
+static int PqGSSSendStart; /* Next index to send a byte in
+ * PqGSSSendBuffer */
+
+/* PqGSSRecvBuffer is for *encrypted* data */
+static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int PqGSSRecvPointer; /* Next index to read a byte from
+ * PqGSSRecvBuffer */
+static int PqGSSRecvLength; /* End of data available in PqGSSRecvBuffer */
+
+/* PqGSSResultBuffer is for *unencrypted* data */
+static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int PqGSSResultPointer; /* Next index to read a byte from
+ * PqGSSResultBuffer */
+static int PqGSSResultLength; /* End of data available in PqGSSResultBuffer */
+
+uint32 max_packet_size; /* Maximum size we can encrypt and fit the
+ * results into our output buffer */
+
+/*
+ * Write len bytes of data from ptr along a GSSAPI-encrypted connection. Note
+ * that the connection must be already set up for GSSAPI encryption (i.e.,
+ * GSSAPI transport negotiation is complete). Returns len when all data has
+ * been written; retry when errno is EWOULDBLOCK or similar with the same
+ * values of ptr and len. On non-socket failures, will log an error message.
+ */
+ssize_t
+pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
+{
+ gss_buffer_desc input,
+ output = GSS_C_EMPTY_BUFFER;
+ OM_uint32 major,
+ minor;
+ ssize_t ret = -1;
+ size_t bytes_to_encrypt = len;
+ size_t bytes_encrypted = 0;
+
+ /*
+ * Loop through encrypting data and sending it out until
+ * pqsecure_raw_write() complains (which would likely mean that the socket
+ * is non-blocking and the requested send() would block, or there was some
+ * kind of actual error) and then return.
+ */
+ while (bytes_to_encrypt || PqGSSSendPointer)
+ {
+ int conf = 0;
+ uint32 netlen;
+
+ /*
+ * Check if we have data in the encrypted output buffer that needs to
+ * be sent, and if so, try to send it. If we aren't able to, return
+ * that back up to the caller.
+ */
+ if (PqGSSSendPointer)
+ {
+ ssize_t ret;
+ ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
+
+ ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
+ if (ret < 0)
+ {
+ /*
+ * If we encrypted some data and it's in our output buffer,
+ * but send() is saying that we would block, then tell the
+ * client how far we got with encrypting the data so that they
+ * can call us again with whatever is left, at which point we
+ * will try to send the remaining encrypted data first and
+ * then move on to encrypting the rest of the data.
+ */
+ if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
+ return bytes_encrypted;
+ else
+ return ret;
+ }
+
+ /*
+ * Partial write, move forward that far in our buffer and try
+ * again
+ */
+ if (ret != amount)
+ {
+ PqGSSSendStart += ret;
+ continue;
+ }
+
+ /* All encrypted data was sent, our buffer is empty now. */
+ PqGSSSendPointer = PqGSSSendStart = 0;
+ }
+
+ /*
+ * Check if there are any bytes left to encrypt. If not, we're done.
+ */
+ if (!bytes_to_encrypt)
+ return bytes_encrypted;
+
+ /*
+ * Check how much we are being asked to send, if it's too much, then
+ * we will have to loop and possibly be called multiple times to get
+ * through all the data.
+ */
+ if (bytes_to_encrypt > max_packet_size)
+ input.length = max_packet_size;
+ else
+ input.length = bytes_to_encrypt;
+
+ input.value = (char *) ptr + bytes_encrypted;
+
+ output.value = NULL;
+ output.length = 0;
+
+ /* Create the next encrypted packet */
+ major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (major != GSS_S_COMPLETE)
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ goto cleanup;
+ }
+
+ if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI attempt to send oversize packet\n"));
+ goto cleanup;
+ }
+
+ bytes_encrypted += input.length;
+ bytes_to_encrypt -= input.length;
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32));
+ PqGSSSendPointer += sizeof(uint32);
+
+ memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+ PqGSSSendPointer += output.length;
+ }
+
+ ret = bytes_encrypted;
+
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+/*
+ * Read up to len bytes of data into ptr from a GSSAPI-encrypted connection.
+ * Note that GSSAPI transport must already have been negotiated. Returns the
+ * number of bytes read into ptr; otherwise, returns -1. Retry with the same
+ * ptr and len when errno is EWOULDBLOCK or similar.
+ */
+ssize_t
+pg_GSS_read(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major,
+ minor;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
+ output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = 0;
+ size_t bytes_to_return = len;
+ size_t bytes_returned = 0;
+
+ /*
+ * The goal here is to read an incoming encrypted packet, one at a time,
+ * decrypt it into our out buffer, returning to the caller what they asked
+ * for, and then saving anything else for the next call.
+ *
+ * We get a read request, we look if we have cleartext bytes available
+ * and, if so, copy those to the result, and then we try to decrypt the
+ * next packet.
+ *
+ * We should not try to decrypt the next packet until the read buffer is
+ * completely empty.
+ *
+ * If the caller asks for more bytes than one decrypted packet, then we
+ * should try to return all bytes asked for.
+ */
+ while (bytes_to_return)
+ {
+ int conf = 0;
+
+ /* Check if we have data in our buffer that we can return immediately */
+ if (PqGSSResultPointer < PqGSSResultLength)
+ {
+ int bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
+ int bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
+
+ /*
+ * Copy the data from our output buffer into the caller's buffer,
+ * at the point where we last left off filling their buffer
+ */
+ memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultPointer, bytes_to_copy);
+ PqGSSResultPointer += bytes_to_copy;
+ bytes_to_return -= bytes_to_copy;
+ bytes_returned += bytes_to_copy;
+
+ /* Check if our result buffer is now empty and, if so, reset */
+ if (PqGSSResultPointer == PqGSSResultLength)
+ PqGSSResultPointer = PqGSSResultLength = 0;
+
+ continue;
+ }
+
+ /*
+ * At this point, our output buffer should be empty with more bytes
+ * being requested to be read. We are now ready to load the next
+ * packet and decrypt it (entirely) into our buffer.
+ *
+ * If we get a partial read back while trying to read a packet off the
+ * wire then we return back what bytes we were able to return and wait
+ * to be called again, until we get a full packet to decrypt.
+ */
+
+ /* Check if we got a partial read just trying to get the length */
+ if (PqGSSRecvLength < sizeof(uint32))
+ {
+ /* Try to get whatever of the length we still need */
+ ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
+ sizeof(uint32) - PqGSSRecvLength);
+ if (ret < 0)
+ return bytes_returned ? bytes_returned : ret;
+
+ PqGSSRecvLength += ret;
+ if (PqGSSRecvLength < sizeof(uint32))
+ return bytes_returned;
+ }
+
+ /*
+ * We should have the whole length at this point, so pull it out and
+ * then read whatever we have left of the packet
+ */
+ input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+
+ /* Check for over-length packet */
+ if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /*
+ * Read as much of the packet as we are able to on this call into
+ * wherever we left off from the last time we were called.
+ */
+ ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
+ input.length - (PqGSSRecvLength - sizeof(uint32)));
+ if (ret < 0)
+ return bytes_returned ? bytes_returned : ret;
+
+ /*
+ * If we got less than the rest of the packet then we need to return
+ * and be called again.
+ */
+ PqGSSRecvLength += ret;
+ if (PqGSSRecvLength - sizeof(uint32) < input.length)
+ return bytes_returned ? bytes_returned : -1;
+
+ /*
+ * We now have the full packet and we can perform the decryption and
+ * refill our output buffer, then loop back up to pass that back to
+ * the user.
+ */
+ output.value = NULL;
+ output.length = 0;
+ input.value = PqGSSRecvBuffer + sizeof(uint32);
+
+ major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+ if (major != GSS_S_COMPLETE)
+ {
+ 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;
+ }
+
+ memcpy(PqGSSResultBuffer, output.value, output.length);
+ PqGSSResultLength = output.length;
+
+ /* Our buffer is now empty, reset it */
+ PqGSSRecvPointer = PqGSSRecvLength = 0;
+
+ gss_release_buffer(&minor, &output);
+ }
+
+ ret = bytes_returned;
+
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+/*
+ * Simple wrapper for reading from pqsecure_raw_read.
+ *
+ * This takes the same arguments as pqsecure_raw_read, plus an output parameter
+ * to return the number of bytes read. This handles if blocking would occur and
+ * if we detect EOF on the connection.
+ */
+static PostgresPollingStatusType
+gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
+{
+ *ret = pqsecure_raw_read(conn, recv_buffer, length);
+ if (*ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (*ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ /* Check for EOF */
+ if (*ret == 0)
+ {
+ int result = pqReadReady(conn);
+
+ if (result < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (!result)
+ return PGRES_POLLING_READING;
+
+ *ret = pqsecure_raw_read(conn, recv_buffer, length);
+ if (*ret == 0)
+ return PGRES_POLLING_FAILED;
+ }
+
+ return PGRES_POLLING_OK;
+}
+
+/*
+ * Negotiate GSSAPI transport for a connection. When complete, returns
+ * PGRES_POLLING_OK. Will return PGRES_POLLING_READING or
+ * PGRES_POLLING_WRITING as appropriate whenever it would block, and
+ * PGRES_POLLING_FAILED if transport could not be negotiated.
+ */
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ static int first = 1;
+ ssize_t ret;
+ OM_uint32 major,
+ minor;
+ uint32 netlen;
+ PostgresPollingStatusType result;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
+ output = GSS_C_EMPTY_BUFFER;
+
+ /* Check for data that needs to be written */
+ if (first)
+ {
+ PqGSSSendPointer = PqGSSSendStart = PqGSSRecvPointer = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
+ first = 0;
+ }
+
+ /*
+ * Check if we have anything to send from a prior call and if so, send it.
+ */
+ if (PqGSSSendPointer)
+ {
+ ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
+
+ ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+
+ if (ret != amount)
+ {
+ PqGSSSendStart += amount;
+ return PGRES_POLLING_WRITING;
+ }
+
+ PqGSSSendPointer = PqGSSSendStart = 0;
+ }
+
+ /*
+ * Client sends first, and sending creates a context, therefore this will
+ * be false the first time through, and then when we get called again we
+ * will check for incoming data.
+ */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+
+ /* See if we are still trying to get the length */
+ if (PqGSSRecvLength < sizeof(uint32))
+ {
+ /* Attempt to get the length first */
+ result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, sizeof(uint32) - PqGSSRecvLength, &ret);
+ if (result != PGRES_POLLING_OK)
+ return result;
+
+ PqGSSRecvLength += ret;
+
+ if (PqGSSRecvLength < sizeof(uint32))
+ return PGRES_POLLING_READING;
+ }
+
+ /*
+ * Check if we got an error packet
+ *
+ * This is safe to do because we shouldn't ever get a packet over 8192
+ * and therefore the actual length bytes, being that they are in
+ * network byte order, for any real packet will be two zero bytes.
+ */
+ if (PqGSSRecvBuffer[0] == 'E')
+ {
+ /*
+ * For an error message, the length is after the E, so read one
+ * more byte to get the full length
+ */
+ result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, 1, &ret);
+ if (result != PGRES_POLLING_OK)
+ return result;
+
+ PqGSSRecvLength += ret;
+
+ if (PqGSSRecvLength < 1 + sizeof(uint32))
+ return PGRES_POLLING_READING;
+
+ input.length = ntohl(*(uint32 *) PqGSSRecvBuffer + 1);
+ if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32) - 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Over-size error packet sent by the server."));
+ return PGRES_POLLING_FAILED;
+ }
+
+ result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, input.length - PqGSSRecvLength - 1 - sizeof(uint32), &ret);
+ if (result != PGRES_POLLING_OK)
+ return result;
+
+ PqGSSRecvLength += ret;
+
+ if (PqGSSRecvLength < 1 + sizeof(uint32) + input.length)
+ return PGRES_POLLING_READING;
+
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ PqGSSRecvBuffer + 1 + sizeof(int32));
+
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * We should have the whole length at this point, so pull it out and
+ * then read whatever we have left of the packet
+ */
+
+ /* Get the length and check for over-length packet */
+ input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+ if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Over-size GSSAPI packet sent by the server: %ld"), input.length);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /*
+ * Read as much of the packet as we are able to on this call into
+ * wherever we left off from the last time we were called.
+ */
+ result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
+ input.length - (PqGSSRecvLength - sizeof(uint32)), &ret);
+ if (result != PGRES_POLLING_OK)
+ return result;
+
+ PqGSSRecvLength += ret;
+
+ /*
+ * If we got less than the rest of the packet then we need to return
+ * and be called again.
+ */
+ if (PqGSSRecvLength - sizeof(uint32) < input.length)
+ return PGRES_POLLING_READING;
+
+ input.value = PqGSSRecvBuffer + sizeof(uint32);
+ }
+
+ /* Load the service name (no-op if already done */
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ /*
+ * Call GSS init context, either with an empty input, or with a complete
+ * packet from the server.
+ */
+ major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx,
+ conn->gtarg_nam, GSS_C_NO_OID,
+ GSS_REQUIRED_FLAGS, 0, 0, &input, NULL,
+ &output, NULL, NULL);
+
+ /* GSS Init Sec Context uses the whole packet, so clear it */
+ PqGSSRecvPointer = PqGSSRecvLength = 0;
+
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
+ conn, major, minor);
+ return PGRES_POLLING_FAILED;
+ }
+ else if (output.length == 0)
+ {
+ /*
+ * We're done - hooray! Kind of gross, but we need to disable SSL
+ * here so that we don't accidentally tunnel one over the other.
+ */
+#ifdef USE_SSL
+ conn->allow_ssl_try = false;
+#endif
+ gss_release_cred(&minor, &conn->gcred);
+ conn->gcred = GSS_C_NO_CREDENTIAL;
+ conn->gssenc = true;
+
+ /*
+ * Determine the max packet size which will fit in our buffer, after
+ * accounting for the length
+ */
+ major = gss_wrap_size_limit(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+ PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
+
+ if (GSS_ERROR(major))
+ pg_GSS_error(libpq_gettext("GSSAPI size check error"), conn,
+ major, minor);
+
+ return PGRES_POLLING_OK;
+ }
+
+ /* Must have output.length > 0 */
+ if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
+ conn, major, minor);
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+
+ memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
+ PqGSSSendPointer += sizeof(uint32);
+
+ memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+ PqGSSSendPointer += output.length;
+
+ gss_release_buffer(&minor, &output);
+
+ /* Asked to be called again to write data */
+ return PGRES_POLLING_WRITING;
+}
+
+/*
+ * GSSAPI Information functions.
+ */
+
+/*
+ * Return the GSSAPI Context itself.
+ */
+void *
+PQgetgssctx(PGconn *conn)
+{
+ if (!conn)
+ return NULL;
+
+ return conn->gctx;
+}
+
+/*
+ * Return true if GSSAPI encryption is in use.
+ */
+int
+PQgssEncInUse(PGconn *conn)
+{
+ if (!conn || !conn->gctx)
+ return 0;
+
+ return conn->gssenc;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 4658e27caa6..b8191b4c8f8 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -221,6 +221,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
}
else
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssenc)
+ {
+ n = pg_GSS_read(conn, ptr, len);
+ }
+ else
+#endif
{
n = pqsecure_raw_read(conn, ptr, len);
}
@@ -298,6 +305,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
}
else
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssenc)
+ {
+ n = pg_GSS_write(conn, ptr, len);
+ }
+ else
+#endif
{
n = pqsecure_raw_write(conn, ptr, len);
}
@@ -420,6 +434,23 @@ PQsslAttributeNames(PGconn *conn)
}
#endif /* USE_SSL */
+/* Dummy version of GSSAPI information functions, when built without GSS support */
+#ifndef ENABLE_GSS
+
+void *
+PQgetgssctx(PGconn *conn)
+{
+ return NULL;
+}
+
+int
+PQgssEncInUse(PGconn *conn)
+{
+ return 0;
+}
+
+#endif /* ENABLE_GSS */
+
#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 97bc98b1f3c..27047ddd1f3 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -65,8 +65,9 @@ typedef enum
CONNECTION_NEEDED, /* Internal state: connect() needed */
CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable
* connection. */
- CONNECTION_CONSUME /* Wait for any pending message and consume
+ CONNECTION_CONSUME, /* Wait for any pending message and consume
* them. */
+ CONNECTION_GSS_STARTUP /* Negotiating GSSAPI. */
} ConnStatusType;
typedef enum
@@ -346,6 +347,12 @@ extern void PQinitSSL(int do_init);
/* More detailed way to tell libpq whether it needs to initialize OpenSSL */
extern void PQinitOpenSSL(int do_ssl, int do_crypto);
+/* Return true if GSSAPI encryption is in use */
+extern int PQgssEncInUse(PGconn *conn);
+
+/* Returns GSSAPI context if GSSAPI is in use */
+extern void *PQgetgssctx(PGconn *conn);
+
/* Set verbosity for PQerrorMessage and PQresultErrorMessage */
extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index dbe0f7e5c0b..84222f2c7ca 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -480,9 +480,15 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssencmode; /* GSS mode (require,prefer,disable) */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
gss_name_t gtarg_nam; /* GSS target name */
+
+ /* The following are encryption-only */
+ bool try_gss; /* GSS attempting permitted */
+ bool gssenc; /* GSS encryption is usable */
+ gss_cred_id_t gcred; /* GSS credential temp storage. */
#endif
#ifdef ENABLE_SSPI
@@ -749,6 +755,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
int *names_examined,
char **first_name);
+/* === GSSAPI === */
+
+#ifdef ENABLE_GSS
+
+/*
+ * Establish a GSSAPI-encrypted connection.
+ */
+extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn);
+
+/*
+ * Read and write functions for GSSAPI-encrypted connections, with internal
+ * buffering to handle nonblocking sockets.
+ */
+extern ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len);
+extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
+#endif
+
/* === miscellaneous macros === */
/*
diff --git a/src/test/kerberos/t/002_enc.pl b/src/test/kerberos/t/002_enc.pl
new file mode 100644
index 00000000000..1a7dc30a810
--- /dev/null
+++ b/src/test/kerberos/t/002_enc.pl
@@ -0,0 +1,197 @@
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More;
+use File::Path 'remove_tree';
+
+if ($ENV{with_gssapi} eq 'yes')
+{
+ plan tests => 5;
+}
+else
+{
+ plan skip_all => 'GSSAPI/Kerberos not supported by this build';
+}
+
+my ($krb5_bin_dir, $krb5_sbin_dir);
+
+if ($^O eq 'darwin')
+{
+ $krb5_bin_dir = '/usr/local/opt/krb5/bin';
+ $krb5_sbin_dir = '/usr/local/opt/krb5/sbin';
+}
+elsif ($^O eq 'freebsd')
+{
+ $krb5_bin_dir = '/usr/local/bin';
+ $krb5_sbin_dir = '/usr/local/sbin';
+}
+elsif ($^O eq 'linux')
+{
+ $krb5_sbin_dir = '/usr/sbin';
+}
+
+my $krb5_config = 'krb5-config';
+my $kinit = 'kinit';
+my $kdb5_util = 'kdb5_util';
+my $kadmin_local = 'kadmin.local';
+my $krb5kdc = 'krb5kdc';
+
+if ($krb5_bin_dir && -d $krb5_bin_dir)
+{
+ $krb5_config = $krb5_bin_dir . '/' . $krb5_config;
+ $kinit = $krb5_bin_dir . '/' . $kinit;
+}
+if ($krb5_sbin_dir && -d $krb5_sbin_dir)
+{
+ $kdb5_util = $krb5_sbin_dir . '/' . $kdb5_util;
+ $kadmin_local = $krb5_sbin_dir . '/' . $kadmin_local;
+ $krb5kdc = $krb5_sbin_dir . '/' . $krb5kdc;
+}
+
+my $host = 'auth-test-localhost.postgresql.example.com';
+my $hostaddr = '127.0.0.1';
+my $realm = 'EXAMPLE.COM';
+
+my $krb5_conf = "${TestLib::tmp_check}/krb5.conf";
+my $kdc_conf = "${TestLib::tmp_check}/kdc.conf";
+my $krb5_log = "${TestLib::tmp_check}/krb5libs.log";
+my $kdc_log = "${TestLib::tmp_check}/krb5kdc.log";
+my $kdc_port = int(rand() * 16384) + 49152;
+my $kdc_datadir = "${TestLib::tmp_check}/krb5kdc";
+my $kdc_pidfile = "${TestLib::tmp_check}/krb5kdc.pid";
+my $keytab = "${TestLib::tmp_check}/krb5.keytab";
+
+note "setting up Kerberos";
+
+my ($stdout, $krb5_version);
+run_log [ $krb5_config, '--version' ], '>', \$stdout
+ or BAIL_OUT("could not execute krb5-config");
+BAIL_OUT("Heimdal is not supported") if $stdout =~ m/heimdal/;
+$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/
+ or BAIL_OUT("could not get Kerberos version");
+$krb5_version = $1;
+
+append_to_file(
+ $krb5_conf,
+ qq![logging]
+default = FILE:$krb5_log
+kdc = FILE:$kdc_log
+
+[libdefaults]
+default_realm = $realm
+
+[realms]
+$realm = {
+ kdc = $hostaddr:$kdc_port
+}!);
+
+append_to_file(
+ $kdc_conf,
+ qq![kdcdefaults]
+!);
+
+# For new-enough versions of krb5, use the _listen settings rather
+# than the _ports settings so that we can bind to localhost only.
+if ($krb5_version >= 1.15)
+{
+ append_to_file(
+ $kdc_conf,
+ qq!kdc_listen = $hostaddr:$kdc_port
+kdc_tcp_listen = $hostaddr:$kdc_port
+!);
+}
+else
+{
+ append_to_file(
+ $kdc_conf,
+ qq!kdc_ports = $kdc_port
+kdc_tcp_ports = $kdc_port
+!);
+}
+append_to_file(
+ $kdc_conf,
+ qq!
+[realms]
+$realm = {
+ database_name = $kdc_datadir/principal
+ admin_keytab = FILE:$kdc_datadir/kadm5.keytab
+ acl_file = $kdc_datadir/kadm5.acl
+ key_stash_file = $kdc_datadir/_k5.$realm
+}!);
+
+remove_tree $kdc_datadir;
+mkdir $kdc_datadir or die;
+
+$ENV{'KRB5_CONFIG'} = $krb5_conf;
+$ENV{'KRB5_KDC_PROFILE'} = $kdc_conf;
+
+my $service_principal = "$ENV{with_krb_srvnam}/$host";
+
+system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0';
+
+my $test1_password = 'secret1';
+system_or_bail $kadmin_local, '-q', "addprinc -pw $test1_password test1";
+
+system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal";
+system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $service_principal";
+
+system_or_bail $krb5kdc, '-P', $kdc_pidfile;
+
+END
+{
+ kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile;
+}
+
+note "setting up PostgreSQL instance";
+
+my $node = get_new_node('node');
+$node->init;
+$node->append_conf('postgresql.conf', "listen_addresses = 'localhost'");
+$node->append_conf('postgresql.conf', "krb_server_keyfile = '$keytab'");
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test1;');
+
+note "running tests";
+
+sub test_access
+{
+ my ($node, $gssencmode, $expected_res, $test_name) = @_;
+
+ my $res = $node->psql(
+ "postgres",
+ "SELECT 1",
+ extra_params => [
+ "-d",
+ $node->connstr("postgres") . " host=$host hostaddr=$hostaddr gssencmode=$gssencmode",
+ "-U", "test1",
+ ]);
+ is($res, $expected_res, $test_name);
+ return;
+}
+
+unlink($node->data_dir . "/pg_ident.conf");
+$node->append_conf("pg_ident.conf", 'mymap /^(.*)@EXAMPLE.COM$ \1');
+run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?);
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+ qq{hostgssenc all all $hostaddr/32 gss map=mymap});
+$node->restart;
+test_access($node, "require", 0, "GSS-encrypted access");
+test_access($node, "disable", 2, "GSS encryption disabled");
+
+unlink($node->data_dir . "/pg_hba.conf");
+$node->append_conf("pg_hba.conf", qq{hostgssenc all all $hostaddr/32 trust});
+$node->restart;
+test_access($node, "require", 0, "GSS encryption without auth");
+
+unlink($node->data_dir . "/pg_hba.conf");
+$node->append_conf("pg_hba.conf",
+ qq{hostnogssenc all all localhost gss map=mymap});
+$node->restart;
+test_access($node, "prefer", 0, "GSS unencrypted fallback");
+
+# Check that the client can prevent fallback.
+test_access($node, "require", 2, "GSS unencrypted fallback prevention");
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index dae772d33a2..bf7fca54ee4 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1739,7 +1739,7 @@ pg_stat_activity| SELECT s.datid,
s.backend_xmin,
s.query,
s.backend_type
- FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn)
+ FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc)
LEFT JOIN pg_database d ON ((s.datid = d.oid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
pg_stat_all_indexes| SELECT c.oid AS relid,
@@ -1830,6 +1830,11 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
FROM pg_database d;
+pg_stat_gssapi| SELECT s.pid,
+ s.gss_auth AS gss_authenticated,
+ s.gss_princ AS principal,
+ s.gss_enc AS encrypted
+ FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc);
pg_stat_progress_cluster| SELECT s.pid,
s.datid,
d.datname,
@@ -1926,7 +1931,7 @@ pg_stat_replication| SELECT s.pid,
w.sync_priority,
w.sync_state,
w.reply_time
- FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn)
+ FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc)
JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
pg_stat_ssl| SELECT s.pid,
@@ -1938,7 +1943,7 @@ pg_stat_ssl| SELECT s.pid,
s.ssl_client_dn AS client_dn,
s.ssl_client_serial AS client_serial,
s.ssl_issuer_dn AS issuer_dn
- FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn);
+ FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc);
pg_stat_subscription| SELECT su.oid AS subid,
su.subname,
st.pid,
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c0c6ff6751f..f466df88477 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -194,6 +194,11 @@ sub mkvcbuild
$postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
$postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
}
+ if (!$solution->{options}->{gss})
+ {
+ $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
+ $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c');
+ }
my $snowball = $solution->AddProject('dict_snowball', 'dll', '',
'src/backend/snowball');
@@ -254,6 +259,11 @@ sub mkvcbuild
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
}
+ if (!$solution->{options}->{gss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c');
+ }
my $libpqwalreceiver =
$solution->AddProject('libpqwalreceiver', 'dll', '',