Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-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
11 files changed, 946 insertions, 81 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);