agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH] libpq GSSAPI encryption support
20+ messages / 8 participants
[nested] [flat]
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Robbie Harwood @ 2018-05-10 20:12 UTC (permalink / raw)
On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.
Add frontend and backend encryption support functions. Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.
In postmaster, pull GSSAPI authorization checking into a shared
function. Also share the initiator name between the encryption and
non-encryption codepaths.
Modify pqsecure_write() to take a non-const pointer. The pointer will
not be modified, but this change (or a const-discarding cast, or a
malloc()+memcpy()) is necessary for GSSAPI due to const/struct
interactions in C.
For HBA, add "hostgss" and "hostnogss" entries that behave similarly
to their SSL counterparts. "hostgss" requires either "gss", "trust",
or "reject" for its authentication.
Simiarly, add a "gssmode" parameter to libpq. Supported values are
"disable", "require", and "prefer". Notably, negotiation will only be
attempted if credentials can be acquired. Move credential acquisition
into its own function to support this behavior.
Finally, add documentation for everything new, and update existing
documentation on connection security.
Thanks to Michael Paquier for the Windows fixes.
---
doc/src/sgml/client-auth.sgml | 75 ++++--
doc/src/sgml/libpq.sgml | 57 +++-
doc/src/sgml/runtime.sgml | 77 +++++-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 107 +++-----
src/backend/libpq/be-gssapi-common.c | 64 +++++
src/backend/libpq/be-gssapi-common.h | 26 ++
src/backend/libpq/be-secure-gssapi.c | 321 ++++++++++++++++++++++
src/backend/libpq/be-secure.c | 16 ++
src/backend/libpq/hba.c | 51 +++-
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/postmaster.c | 64 ++++-
src/include/libpq/hba.h | 4 +-
src/include/libpq/libpq-be.h | 11 +-
src/include/libpq/libpq.h | 3 +
src/include/libpq/pqcomm.h | 5 +-
src/include/pgstat.h | 3 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-auth.c | 82 +-----
src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++-
src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++
src/interfaces/libpq/fe-gssapi-common.h | 23 ++
src/interfaces/libpq/fe-secure-gssapi.c | 345 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-fe.h | 3 +-
src/interfaces/libpq/libpq-int.h | 30 ++-
src/tools/msvc/Mkvcbuild.pm | 10 +
27 files changed, 1571 insertions(+), 193 deletions(-)
create mode 100644 src/backend/libpq/be-gssapi-common.c
create mode 100644 src/backend/libpq/be-gssapi-common.h
create mode 100644 src/backend/libpq/be-secure-gssapi.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.h
create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index c2114021c3..6f9f2b7560 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
</synopsis>
The meaning of the fields is as follows:
@@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
This record matches connection attempts made using TCP/IP.
- <literal>host</literal> records match either
+ <literal>host</literal> records match
<acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection
- attempts.
+ attempts as well as <acronym>GSSAPI</acronym> or
+ non-<acronym>GSSAPI</acronym> connection attempts.
</para>
<note>
<para>
@@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>hostgss</literal></term>
+ <listitem>
+ <para>
+ This record matches connection attempts made using TCP/IP,
+ but only when the connection is made with <acronym>GSSAPI</acronym>
+ encryption.
+ </para>
+
+ <para>
+ To make use of this option the server must be built with
+ <acronym>GSSAPI</acronym> support. Otherwise,
+ the <literal>hostgss</literal> record is ignored except for logging a
+ warning that it cannot match any connections.
+ </para>
+
+ <para>
+ Note that the only supported <xref linkend="auth-methods"/> for use
+ with <acronym>GSSAPI</acronym> encryption
+ are <literal>gss</literal>, <literal>reject</literal>,
+ and <literal>trust</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>hostnogss</literal></term>
+ <listitem>
+ <para>
+ This record type has the opposite behavior of <literal>hostgss</literal>;
+ it only matches connection attempts made over
+ TCP/IP that do not use <acronym>GSSAPI</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>database</replaceable></term>
<listitem>
@@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
Use GSSAPI to authenticate the user. This is only
- available for TCP/IP connections. See <xref
- linkend="gssapi-auth"/> for details.
+ available for TCP/IP connections . See <xref
+ linkend="gssapi-auth"/> for details. It can be used in conjunction
+ with GSSAPI encryption.
</para>
</listitem>
</varlistentry>
@@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256
host all mike .example.com md5
host all all .example.com scram-sha-256
-# In the absence of preceding "host" lines, these two lines will
+# In the absence of preceding "host" lines, these three lines will
# reject all connections from 192.168.54.1 (since that entry will be
-# matched first), but allow GSSAPI connections from anywhere else
-# on the Internet. The zero mask causes no bits of the host IP
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet. Unencrypted GSSAPI connections are allowed from
+# 192.168.12.10 only. The zero mask causes no bits of the host IP
# address to be considered, so it matches any host.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 192.168.54.1/32 reject
-host all all 0.0.0.0/0 gss
+hostgss all all 0.0.0.0/0 gss
+host all all 192.168.12.10/32 gss
# Allow users from 192.168.x.x hosts to connect to any database, if
# they pass the ident check. If, for example, ident says the user is
@@ -1051,13 +1093,16 @@ omicron bryanh guest1
<para>
<productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in RFC 2743.
- <productname>PostgreSQL</productname> supports
- <productname>GSSAPI</productname> with <productname>Kerberos</productname>
- authentication according to RFC 1964. <productname>GSSAPI</productname>
- provides automatic authentication (single sign-on) for systems
- that support it. The authentication itself is secure, but the
- data sent over the database connection will be sent unencrypted unless
- <acronym>SSL</acronym> is used.
+
+ <productname>PostgreSQL</productname>
+ supports <productname>GSSAPI</productname> for use as either an encrypted,
+ authenticated layer, or as authentication
+ only. <productname>GSSAPI</productname> provides automatic authentication
+ (single sign-on) for systems that support it. The authentication itself is
+ secure. If <productname>GSSAPI</productname> encryption
+ (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are
+ used, the data sent along the database connection will be encrypted;
+ otherwise, it will not.
</para>
<para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 06d909e804..27c74056bb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1316,6 +1316,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode">
+ <term><literal>gssmode</literal></term>
+ <listitem>
+ <para>
+ This option determines whether or with what priority a secure
+ <acronym>GSS</acronym> TCP/IP connection will be negotiated with the
+ server. There are three modes:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>disable</literal></term>
+ <listitem>
+ <para>
+ only try a non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>prefer</literal> (default)</term>
+ <listitem>
+ <para>
+ if there are <acronym>GSSAPI</acronym> credentials present (i.e.,
+ in a credentials cache), first try
+ a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or
+ there are no credentials, try a
+ non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>require</literal></term>
+ <listitem>
+ <para>
+ only try a <acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ <literal>gssmode</literal> is ignored for Unix domain socket
+ communication. If <productname>PostgreSQL</productname> is compiled
+ without GSSAPI support, using the <literal>require</literal> option
+ will cause an error, while <literal>prefer</literal> will be accepted
+ but <application>libpq</application> will not actually attempt
+ a <acronym>GSSAPI</acronym>-encrypted
+ connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with
+ libpq</secondary></indexterm>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
<term><literal>sslmode</literal></term>
<listitem>
@@ -7918,7 +7973,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- For a connection to be known secure, SSL usage must be configured
+ For a connection to be known SSL-secured, SSL usage must be configured
on <emphasis>both the client and the server</emphasis> before the connection
is made. If it is only configured on the server, the client may end up
sending sensitive information (e.g. passwords) before
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 8d9d40664b..ac9c882d44 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To prevent spoofing on TCP connections, the best solution is to use
- SSL certificates and make sure that clients check the server's certificate.
- To do that, the server
+ To prevent spoofing on TCP connections, either use
+ SSL certificates and make sure that clients check the server's certificate,
+ or use GSSAPI encryption (or both, if they're on separate connections).
+ </para>
+
+ <para>
+ To prevent spoofing with SSL, the server
must be configured to accept only <literal>hostssl</literal> connections (<xref
linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files
(<xref linkend="ssl-tcp"/>). The TCP client must connect using
@@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<literal>verify-full</literal> and have the appropriate root certificate
file installed (<xref linkend="libq-ssl-certificates"/>).
</para>
+
+ <para>
+ To prevent spoofing with GSSAPI, the server must be configured to accept
+ only <literal>hostgss</literal> connections
+ (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal>
+ authentication with them. The TCP client must connect
+ using <literal>gssmode=require</literal>.
+ </para>
</sect1>
<sect1 id="encryption-options">
@@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
which hosts can use non-encrypted connections (<literal>host</literal>)
and which require SSL-encrypted connections
(<literal>hostssl</literal>). Also, clients can specify that they
- connect to servers only via SSL. <application>Stunnel</application> or
- <application>SSH</application> can also be used to encrypt transmissions.
+ connect to servers only via SSL.
+ </para>
+
+ <para>
+ GSSAPI-encrypted connections encrypt all data sent across the network,
+ including queries and data returned. (No password is sent across the
+ network.) The <filename>pg_hba.conf</filename> file allows
+ administrators to specify which hosts can use non-encrypted connections
+ (<literal>host</literal>) and which require GSSAPI-encrypted connections
+ (<literal>hostgss</literal>). Also, clients can specify that they
+ connect to servers only on GSSAPI-encrypted connections
+ (<literal>gssmode=require</literal>).
+ </para>
+
+ <para>
+ <application>Stunnel</application> or
+ <application>SSH</application> can also be used to encrypt
+ transmissions.
</para>
</listitem>
</varlistentry>
@@ -2504,6 +2532,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</sect1>
+ <sect1 id="gssapi-enc">
+ <title>Secure TCP/IP Connections with GSSAPI encryption</title>
+
+ <indexterm zone="gssapi-enc">
+ <primary>gssapi</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> also has native support for
+ using <acronym>GSSAPI</acronym> to encrypt client/server communications for
+ increased security. Support requires that a <acronym>GSSAPI</acronym>
+ implementation (such as MIT krb5) is installed on both client and server
+ systems, and that support in <productname>PostgreSQL</productname> is
+ enabled at build time (see <xref linkend="installation"/>).
+ </para>
+
+ <sect2 id="gssapi-setup">
+ <title>Basic Setup</title>
+
+ <para>
+ The <productname>PostgreSQL</productname> server will listen for both
+ normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP
+ port, and will negotiate with any connecting client on whether to
+ use <acronym>GSSAPI</acronym> for encryption (and for authentication). By
+ default, this decision is up to the client (which means it can be
+ downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about
+ setting up the server to require the use of <acronym>GSSAPI</acronym> for
+ some or all conections.
+ </para>
+
+ <para>
+ Other than configuration of the negotiation
+ behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond
+ that which necessary for GSSAPI authentication. (For more information on
+ configuring that, see <xref linkend="gssapi-auth"/>.)
+ </para>
+ </sect2>
+ </sect1>
+
<sect1 id="ssh-tunnels">
<title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..47efef0682 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 8517565535..eed1e11ef1 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -173,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 */
@@ -384,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 cannot be combined with non-GSSAPI authentication")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -524,8 +532,13 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ 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
@@ -1024,68 +1037,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)
{
@@ -1094,7 +1045,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1247,10 +1197,17 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat, min_stat;
+ 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.
*/
@@ -1292,7 +1249,7 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI realm (%s) and configured realm (%s) don't match",
cp, port->hba->krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
}
@@ -1302,14 +1259,14 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
pg_krb_caseins_users);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return ret;
}
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 0000000000..78d9f5d325
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+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")));
+}
+
+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 0000000000..c020051d2e
--- /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 0000000000..2b70db2232
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,321 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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>
+
+static ssize_t
+send_buffered_data(Port *port, size_t len)
+{
+ ssize_t ret = secure_raw_write(
+ port,
+ port->gss->writebuf.data + port->gss->writebuf.cursor,
+ port->gss->writebuf.len - port->gss->writebuf.cursor);
+ if (ret < 0)
+ return ret;
+
+ /* update and possibly clear buffer state */
+ port->gss->writebuf.cursor += ret;
+
+ if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+ {
+ /* entire request has now been written */
+ resetStringInfo(&port->gss->writebuf);
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->writebuf.len != 0)
+ return send_buffered_data(port, len);
+
+ /* encrypt the message */
+ output.value = NULL;
+ output.length = 0;
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (major != GSS_S_COMPLETE)
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor);
+ goto cleanup;
+ } else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ goto cleanup;
+ }
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf, output.value, output.length);
+
+ ret = send_buffered_data(port, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* load up any available data */
+ if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len)
+ {
+ /* clamp length */
+ if (len > gss->buf.len - gss->buf.cursor)
+ len = gss->buf.len - gss->buf.cursor;
+
+ memcpy(ptr, gss->buf.data + gss->buf.cursor, len);
+ gss->buf.cursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (gss->buf.cursor == gss->buf.len)
+ resetStringInfo(&gss->buf);
+
+ return ret;
+}
+
+static ssize_t
+load_packetlen(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ ssize_t ret;
+
+ if (gss->buf.len < 4)
+ {
+ enlargeStringInfo(&gss->buf, 4 - gss->buf.len);
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ 4 - gss->buf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len < 4)
+ {
+ /* partial read from secure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(Port *port, size_t len)
+{
+ ssize_t ret;
+ pg_gssinfo *gss = port->gss;
+
+ enlargeStringInfo(&gss->buf, len - gss->buf.len + 4);
+
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ len - gss->buf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len - 4 < len)
+ {
+ /* partial read from secure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->buf.cursor > 0)
+ return read_from_buffer(gss, ptr, len);
+
+ /* load length if not present */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ return ret;
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ return ret;
+
+ /* decrypt the packet */
+ output.value = NULL;
+ output.length = 0;
+ input.value = gss->buf.data + 4;
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (major != GSS_S_COMPLETE)
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* put the decrypted packet in the buffer */
+ resetStringInfo(&gss->buf);
+ enlargeStringInfo(&gss->buf, output.length);
+
+ memcpy(gss->buf.data, output.value, output.length);
+ gss->buf.len = output.length;
+ gss->buf.data[gss->buf.len] = '\0';
+
+ ret = read_from_buffer(gss, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ bool complete_next = false;
+
+ /*
+ * 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)
+ {
+ OM_uint32 major, minor;
+ size_t ret;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+
+ /* Handle any outgoing data */
+ if (gss->writebuf.len != 0)
+ {
+ ret = send_buffered_data(port, 1);
+ if (ret != 1)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ }
+
+ if (complete_next)
+ break;
+
+ /* Load incoming data */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ input.value = gss->buf.data + 4;
+
+ /* 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);
+ resetStringInfo(&gss->buf);
+ 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;
+ }
+
+ if (output.length != 0)
+ {
+ /* Queue packet for writing */
+ uint32 netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf,
+ output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ continue;
+ }
+
+ /* We're done - woohoo! */
+ break;
+ }
+ port->gss->enc = true;
+ return 0;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index d349d7c2c7..9d48fd5dc6 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -153,6 +153,14 @@ retry:
n = be_tls_read(port, ptr, len, &waitfor);
}
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);
@@ -256,6 +264,14 @@ retry:
n = be_tls_write(port, ptr, len, &waitfor);
}
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);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 1a65ec87bd..457ca810d1 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, "hostgss") == 0 ||
+ strcmp(token->string, "hostnogss") == 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') /* "hostgss" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgss 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 = "hostgss 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)
@@ -2060,6 +2088,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)
{
@@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgss";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogss";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 8a5b2b3b42..f980e4e167 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3562,6 +3562,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 41de140ae0..d10c6dae1a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1897,7 +1897,7 @@ initMasks(fd_set *rmask)
* if we detect a communications failure.)
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1908,11 +1908,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes((char *) &len, 4) == EOF)
{
/*
- * EOF after SSLdone probably means the client didn't like our
+ * EOF after secure_done probably means the client didn't like our
* response to NEGOTIATE_SSL_CODE. That's not an error condition, so
* don't clutter the log with a complaint.
*/
- if (!SSLdone)
+ if (!secure_done)
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("incomplete startup packet")));
@@ -1964,7 +1964,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -1997,6 +1997,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 */
@@ -2431,6 +2457,17 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
#endif
return port;
@@ -2447,7 +2484,15 @@ ConnFree(Port *conn)
secure_close(conn);
#endif
if (conn->gss)
+ {
+#ifdef ENABLE_GSS
+ if (conn->gss->buf.data)
+ pfree(conn->gss->buf.data);
+ if (conn->gss->writebuf.data)
+ pfree(conn->gss->writebuf.data);
+#endif
free(conn->gss);
+ }
free(conn);
}
@@ -4779,6 +4824,17 @@ SubPostmasterMain(int argc, char *argv[])
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
/*
* If appropriate, physically re-attach to shared memory segment. We want
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..830ddaa25a 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 struct HbaLine
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index eb8bba4ed8..fae8db4c01 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -66,7 +66,7 @@ typedef struct
#include "datatype/timestamp.h"
#include "libpq/hba.h"
#include "libpq/pqcomm.h"
-
+#include "lib/stringinfo.h"
typedef enum CAC_state
{
@@ -86,6 +86,9 @@ typedef struct
gss_cred_id_t cred; /* GSSAPI connection cred's */
gss_ctx_id_t ctx; /* GSSAPI connection context */
gss_name_t name; /* GSSAPI client name */
+ bool enc; /* GSSAPI encryption in use */
+ StringInfoData buf; /* GSSAPI encryption data buffering */
+ StringInfoData writebuf; /* GSSAPI nonblocking write buffering */
#endif
} pg_gssinfo;
#endif
@@ -284,6 +287,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 36baf6b919..0f06261099 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 cc0e0b32c7..ade1190096 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 d59c24ae23..d5ce48e81e 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -787,7 +787,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;
/* ----------
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index c2171d0856..238f208364 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/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 92641fe5e9..6875ad7f79 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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index d001bc513d..06f37c8a29 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 gssmode similarly to sslmode - we can stil handle "disable" and
+ * "prefer".
+ */
+ {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1222,6 +1236,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * validate gssmode option
+ */
+ if (conn->gssmode)
+ {
+ if (strcmp(conn->gssmode, "disable") != 0 &&
+ strcmp(conn->gssmode, "prefer") != 0 &&
+ strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssmode value: \"%s\"\n"),
+ conn->gssmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssmode = strdup(DefaultGSSMode);
+ if (!conn->gssmode)
+ goto oom_error;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -1812,6 +1859,11 @@ connectDBStart(PGconn *conn)
*/
resetPQExpBuffer(&conn->errorMessage);
+#ifdef ENABLE_GSS
+ if (conn->gssmode[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
@@ -2084,6 +2136,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2609,17 +2662,54 @@ 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 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 (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
+ {
+ 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->gssmode[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 (IS_AF_UNIX(conn->raddr.addr.ss_family))
- {
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
- }
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2813,6 +2903,97 @@ 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->gssmode[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->gssmode[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.
@@ -2966,6 +3147,24 @@ 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 gssmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssmode[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
/*
@@ -3536,6 +3735,11 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+ initPQExpBuffer(&conn->gbuf);
+ initPQExpBuffer(&conn->gwritebuf);
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3665,10 +3869,28 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->gssmode)
+ free(conn->gssmode);
#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;
+ }
+ termPQExpBuffer(&conn->gbuf);
+ termPQExpBuffer(&conn->gwritebuf);
+#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 0000000000..ce71b11aea
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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 0000000000..58811df9f1
--- /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 0000000000..427651ec33
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,345 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * 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
+
+static ssize_t
+send_buffered_data(PGconn *conn, size_t len)
+{
+ ssize_t ret = pqsecure_raw_write(conn,
+ conn->gwritebuf.data + conn->gwritecurs,
+ conn->gwritebuf.len - conn->gwritecurs);
+ if (ret < 0)
+ return ret;
+
+ conn->gwritecurs += ret;
+
+ if (conn->gwritecurs == conn->gwritebuf.len)
+ {
+ /* entire request has now been written */
+ resetPQExpBuffer(&conn->gwritebuf);
+ conn->gwritecurs = 0;
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+
+ if (conn->gwritebuf.len != 0)
+ return send_buffered_data(conn, len);
+
+ /* encrypt the message */
+ input.value = ptr;
+ input.length = len;
+
+ 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;
+ }
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+
+ ret = send_buffered_data(conn, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* check for available data */
+ if (conn->gcursor < conn->gbuf.len)
+ {
+ /* clamp length */
+ if (len > conn->gbuf.len - conn->gcursor)
+ len = conn->gbuf.len - conn->gcursor;
+
+ memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+ conn->gcursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (conn->gcursor == conn->gbuf.len)
+ {
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ }
+
+ return ret;
+}
+
+static ssize_t
+load_packet_length(PGconn *conn)
+{
+ ssize_t ret;
+ if (conn->gbuf.len < 4)
+ {
+ ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "Failed to fit packet length in buffer\n"));
+ return -1;
+ }
+
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ 4 - conn->gbuf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len < 4)
+ {
+ /* partial read from pqsecure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(PGconn *conn, size_t len)
+{
+ ssize_t ret;
+
+ ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI encrypted packet length %ld too big\n"),
+ len);
+ return -1;
+ }
+
+ /* load any missing parts of the packet */
+ if (conn->gbuf.len - 4 < len)
+ {
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ len - conn->gbuf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len - 4 < len)
+ {
+ /* partial read from pqsecure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+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;
+ int conf = 0;
+
+ /* handle any buffered data */
+ if (conn->gcursor != 0)
+ return read_from_buffer(conn, ptr, len);
+
+ /* load in the packet length, if not yet loaded */
+ ret = load_packet_length(conn);
+ if (ret < 0)
+ return ret;
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0)
+ return ret;
+
+ /* decrypt the packet */
+ input.value = conn->gbuf.data + 4;
+
+ 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;
+ }
+
+ /* load decrypted packet into our buffer */
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI decrypted packet length %ld too big\n"),
+ output.length);
+ ret = -1;
+ goto cleanup;
+ }
+
+ memcpy(conn->gbuf.data, output.value, output.length);
+ conn->gbuf.len = output.length;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+
+ ret = read_from_buffer(conn, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ ssize_t ret;
+ OM_uint32 major, minor;
+ uint32 netlen;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER;
+
+ /* Send out any data we might have */
+ if (conn->gwritebuf.len != 0)
+ {
+ ret = send_buffered_data(conn, 1);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+ else if (ret == 1)
+ /* sent all data */
+ return PGRES_POLLING_READING;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Client sends first, and sending creates a context */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+ ret = load_packet_length(conn);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (conn->gbuf.data[0] == 'E')
+ {
+ /*
+ * We can taken an error here, and it's my least favorite thing.
+ * How long can error messages be? That's a good question, but
+ * backend's pg_gss_error() caps them at 256. Do a single read
+ * for that and call it a day. Cope with this here rather than in
+ * load_packet since expandPQExpBuffer will destroy any data in
+ * the buffer on failure.
+ */
+ load_packet(conn, 256);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ conn->gbuf.data + 1);
+ return PGRES_POLLING_FAILED;
+ }
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ input.value = conn->gbuf.data + 4;
+ }
+
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ 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);
+ resetPQExpBuffer(&conn->gbuf);
+ 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;
+ return PGRES_POLLING_OK;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ return PGRES_POLLING_WRITING;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index a06fc7dc82..f4f196e3b4 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, 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);
@@ -287,7 +294,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
* to determine whether to continue/retry after error.
*/
ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
@@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, 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);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 52bd5d2cd8..158e761d0a 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 975ab33d02..bdcb6e2523 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -22,6 +22,7 @@
/* We assume libpq-fe.h has already been included. */
#include "libpq-events.h"
+#include "lib/stringinfo.h"
#include <time.h>
#ifndef WIN32
@@ -477,9 +478,19 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssmode; /* 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 */
+ PQExpBufferData gbuf; /* GSS encryption buffering */
+ size_t gcursor; /* GSS buffering position */
+ PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */
+ size_t gwritecurs; /* GSS write buffer position */
+ 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
@@ -655,7 +666,7 @@ extern void pqsecure_destroy(void);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -747,6 +758,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, 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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 59bed3b8a8..16c13ca3b3 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -189,6 +189,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');
@@ -249,6 +254,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', '',
--
2.19.0
--=-=-=--
^ permalink raw reply [nested|flat] 20+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Robbie Harwood @ 2018-05-10 20:12 UTC (permalink / raw)
On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.
Add frontend and backend encryption support functions. Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.
In postmaster, pull GSSAPI authorization checking into a shared
function. Also share the initiator name between the encryption and
non-encryption codepaths.
Modify pqsecure_write() to take a non-const pointer. The pointer will
not be modified, but this change (or a const-discarding cast, or a
malloc()+memcpy()) is necessary for GSSAPI due to const/struct
interactions in C.
For HBA, add "hostgss" and "hostnogss" entries that behave similarly
to their SSL counterparts. "hostgss" requires either "gss", "trust",
or "reject" for its authentication.
Simiarly, add a "gssmode" parameter to libpq. Supported values are
"disable", "require", and "prefer". Notably, negotiation will only be
attempted if credentials can be acquired. Move credential acquisition
into its own function to support this behavior.
Finally, add documentation for everything new, and update existing
documentation on connection security.
Thanks to Michael Paquier for the Windows fixes.
---
doc/src/sgml/client-auth.sgml | 75 ++++--
doc/src/sgml/libpq.sgml | 57 +++-
doc/src/sgml/runtime.sgml | 77 +++++-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 103 +++----
src/backend/libpq/be-gssapi-common.c | 64 +++++
src/backend/libpq/be-gssapi-common.h | 26 ++
src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++
src/backend/libpq/be-secure.c | 16 ++
src/backend/libpq/hba.c | 51 +++-
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/postmaster.c | 64 ++++-
src/include/libpq/hba.h | 4 +-
src/include/libpq/libpq-be.h | 11 +-
src/include/libpq/libpq.h | 3 +
src/include/libpq/pqcomm.h | 5 +-
src/include/pgstat.h | 3 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-auth.c | 90 +------
src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++-
src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++
src/interfaces/libpq/fe-gssapi-common.h | 23 ++
src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-fe.h | 3 +-
src/interfaces/libpq/libpq-int.h | 30 ++-
src/tools/msvc/Mkvcbuild.pm | 24 +-
27 files changed, 1576 insertions(+), 202 deletions(-)
create mode 100644 src/backend/libpq/be-gssapi-common.c
create mode 100644 src/backend/libpq/be-gssapi-common.h
create mode 100644 src/backend/libpq/be-secure-gssapi.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.h
create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 656d5f9417..38cf32e3be 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
</synopsis>
The meaning of the fields is as follows:
@@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
This record matches connection attempts made using TCP/IP.
- <literal>host</literal> records match either
+ <literal>host</literal> records match
<acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection
- attempts.
+ attempts as well as <acronym>GSSAPI</acronym> or
+ non-<acronym>GSSAPI</acronym> connection attempts.
</para>
<note>
<para>
@@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>hostgss</literal></term>
+ <listitem>
+ <para>
+ This record matches connection attempts made using TCP/IP,
+ but only when the connection is made with <acronym>GSSAPI</acronym>
+ encryption.
+ </para>
+
+ <para>
+ To make use of this option the server must be built with
+ <acronym>GSSAPI</acronym> support. Otherwise,
+ the <literal>hostgss</literal> record is ignored except for logging a
+ warning that it cannot match any connections.
+ </para>
+
+ <para>
+ Note that the only supported <xref linkend="auth-methods"/> for use
+ with <acronym>GSSAPI</acronym> encryption
+ are <literal>gss</literal>, <literal>reject</literal>,
+ and <literal>trust</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>hostnogss</literal></term>
+ <listitem>
+ <para>
+ This record type has the opposite behavior of <literal>hostgss</literal>;
+ it only matches connection attempts made over
+ TCP/IP that do not use <acronym>GSSAPI</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>database</replaceable></term>
<listitem>
@@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
Use GSSAPI to authenticate the user. This is only
- available for TCP/IP connections. See <xref
- linkend="gssapi-auth"/> for details.
+ available for TCP/IP connections . See <xref
+ linkend="gssapi-auth"/> for details. It can be used in conjunction
+ with GSSAPI encryption.
</para>
</listitem>
</varlistentry>
@@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256
host all mike .example.com md5
host all all .example.com scram-sha-256
-# In the absence of preceding "host" lines, these two lines will
+# In the absence of preceding "host" lines, these three lines will
# reject all connections from 192.168.54.1 (since that entry will be
-# matched first), but allow GSSAPI connections from anywhere else
-# on the Internet. The zero mask causes no bits of the host IP
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet. Unencrypted GSSAPI connections are allowed from
+# 192.168.12.10 only. The zero mask causes no bits of the host IP
# address to be considered, so it matches any host.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 192.168.54.1/32 reject
-host all all 0.0.0.0/0 gss
+hostgss all all 0.0.0.0/0 gss
+host all all 192.168.12.10/32 gss
# Allow users from 192.168.x.x hosts to connect to any database, if
# they pass the ident check. If, for example, ident says the user is
@@ -1051,13 +1093,16 @@ omicron bryanh guest1
<para>
<productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in RFC 2743.
- <productname>PostgreSQL</productname> supports
- <productname>GSSAPI</productname> with <productname>Kerberos</productname>
- authentication according to RFC 1964. <productname>GSSAPI</productname>
- provides automatic authentication (single sign-on) for systems
- that support it. The authentication itself is secure, but the
- data sent over the database connection will be sent unencrypted unless
- <acronym>SSL</acronym> is used.
+
+ <productname>PostgreSQL</productname>
+ supports <productname>GSSAPI</productname> for use as either an encrypted,
+ authenticated layer, or as authentication
+ only. <productname>GSSAPI</productname> provides automatic authentication
+ (single sign-on) for systems that support it. The authentication itself is
+ secure. If <productname>GSSAPI</productname> encryption
+ (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are
+ used, the data sent along the database connection will be encrypted;
+ otherwise, it will not.
</para>
<para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 800e68a19e..73d233dc16 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode">
+ <term><literal>gssmode</literal></term>
+ <listitem>
+ <para>
+ This option determines whether or with what priority a secure
+ <acronym>GSS</acronym> TCP/IP connection will be negotiated with the
+ server. There are three modes:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>disable</literal></term>
+ <listitem>
+ <para>
+ only try a non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>prefer</literal> (default)</term>
+ <listitem>
+ <para>
+ if there are <acronym>GSSAPI</acronym> credentials present (i.e.,
+ in a credentials cache), first try
+ a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or
+ there are no credentials, try a
+ non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>require</literal></term>
+ <listitem>
+ <para>
+ only try a <acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ <literal>gssmode</literal> is ignored for Unix domain socket
+ communication. If <productname>PostgreSQL</productname> is compiled
+ without GSSAPI support, using the <literal>require</literal> option
+ will cause an error, while <literal>prefer</literal> will be accepted
+ but <application>libpq</application> will not actually attempt
+ a <acronym>GSSAPI</acronym>-encrypted
+ connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with
+ libpq</secondary></indexterm>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
<term><literal>sslmode</literal></term>
<listitem>
@@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- For a connection to be known secure, SSL usage must be configured
+ For a connection to be known SSL-secured, SSL usage must be configured
on <emphasis>both the client and the server</emphasis> before the connection
is made. If it is only configured on the server, the client may end up
sending sensitive information (e.g. passwords) before
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 330e38a29e..06bf56153c 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To prevent spoofing on TCP connections, the best solution is to use
- SSL certificates and make sure that clients check the server's certificate.
- To do that, the server
+ To prevent spoofing on TCP connections, either use
+ SSL certificates and make sure that clients check the server's certificate,
+ or use GSSAPI encryption (or both, if they're on separate connections).
+ </para>
+
+ <para>
+ To prevent spoofing with SSL, the server
must be configured to accept only <literal>hostssl</literal> connections (<xref
linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files
(<xref linkend="ssl-tcp"/>). The TCP client must connect using
@@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<literal>verify-full</literal> and have the appropriate root certificate
file installed (<xref linkend="libq-ssl-certificates"/>).
</para>
+
+ <para>
+ To prevent spoofing with GSSAPI, the server must be configured to accept
+ only <literal>hostgss</literal> connections
+ (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal>
+ authentication with them. The TCP client must connect
+ using <literal>gssmode=require</literal>.
+ </para>
</sect1>
<sect1 id="encryption-options">
@@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
which hosts can use non-encrypted connections (<literal>host</literal>)
and which require SSL-encrypted connections
(<literal>hostssl</literal>). Also, clients can specify that they
- connect to servers only via SSL. <application>Stunnel</application> or
- <application>SSH</application> can also be used to encrypt transmissions.
+ connect to servers only via SSL.
+ </para>
+
+ <para>
+ GSSAPI-encrypted connections encrypt all data sent across the network,
+ including queries and data returned. (No password is sent across the
+ network.) The <filename>pg_hba.conf</filename> file allows
+ administrators to specify which hosts can use non-encrypted connections
+ (<literal>host</literal>) and which require GSSAPI-encrypted connections
+ (<literal>hostgss</literal>). Also, clients can specify that they
+ connect to servers only on GSSAPI-encrypted connections
+ (<literal>gssmode=require</literal>).
+ </para>
+
+ <para>
+ <application>Stunnel</application> or
+ <application>SSH</application> can also be used to encrypt
+ transmissions.
</para>
</listitem>
</varlistentry>
@@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</sect1>
+ <sect1 id="gssapi-enc">
+ <title>Secure TCP/IP Connections with GSSAPI encryption</title>
+
+ <indexterm zone="gssapi-enc">
+ <primary>gssapi</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> also has native support for
+ using <acronym>GSSAPI</acronym> to encrypt client/server communications for
+ increased security. Support requires that a <acronym>GSSAPI</acronym>
+ implementation (such as MIT krb5) is installed on both client and server
+ systems, and that support in <productname>PostgreSQL</productname> is
+ enabled at build time (see <xref linkend="installation"/>).
+ </para>
+
+ <sect2 id="gssapi-setup">
+ <title>Basic Setup</title>
+
+ <para>
+ The <productname>PostgreSQL</productname> server will listen for both
+ normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP
+ port, and will negotiate with any connecting client on whether to
+ use <acronym>GSSAPI</acronym> for encryption (and for authentication). By
+ default, this decision is up to the client (which means it can be
+ downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about
+ setting up the server to require the use of <acronym>GSSAPI</acronym> for
+ some or all conections.
+ </para>
+
+ <para>
+ Other than configuration of the negotiation
+ behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond
+ that which necessary for GSSAPI authentication. (For more information on
+ configuring that, see <xref linkend="gssapi-auth"/>.)
+ </para>
+ </sect2>
+ </sect1>
+
<sect1 id="ssh-tunnels">
<title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..47efef0682 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 3014b17a7c..6e4cd66a90 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -173,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 */
@@ -384,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 cannot be combined with non-GSSAPI authentication")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -524,8 +532,13 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ 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
@@ -1044,64 +1057,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
-
-
-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)
{
@@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat, min_stat;
+ 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.
*/
@@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI realm (%s) and configured realm (%s) don't match",
cp, port->hba->krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
}
@@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
pg_krb_caseins_users);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return ret;
}
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 0000000000..78d9f5d325
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+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")));
+}
+
+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 0000000000..c020051d2e
--- /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 0000000000..d860f71b71
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,319 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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>
+
+static ssize_t
+send_buffered_data(Port *port, size_t len)
+{
+ ssize_t ret = secure_raw_write(
+ port,
+ port->gss->writebuf.data + port->gss->writebuf.cursor,
+ port->gss->writebuf.len - port->gss->writebuf.cursor);
+ if (ret < 0)
+ return ret;
+
+ /* update and possibly clear buffer state */
+ port->gss->writebuf.cursor += ret;
+
+ if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+ {
+ /* entire request has now been written */
+ resetStringInfo(&port->gss->writebuf);
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->writebuf.len != 0)
+ return send_buffered_data(port, len);
+
+ /* encrypt the message */
+ output.value = NULL;
+ output.length = 0;
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor);
+ goto cleanup;
+ } else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ goto cleanup;
+ }
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf, output.value, output.length);
+
+ ret = send_buffered_data(port, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* load up any available data */
+ if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len)
+ {
+ /* clamp length */
+ if (len > gss->buf.len - gss->buf.cursor)
+ len = gss->buf.len - gss->buf.cursor;
+
+ memcpy(ptr, gss->buf.data + gss->buf.cursor, len);
+ gss->buf.cursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (gss->buf.cursor == gss->buf.len)
+ resetStringInfo(&gss->buf);
+
+ return ret;
+}
+
+static ssize_t
+load_packetlen(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ ssize_t ret;
+
+ if (gss->buf.len < 4)
+ {
+ enlargeStringInfo(&gss->buf, 4 - gss->buf.len);
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ 4 - gss->buf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(Port *port, size_t len)
+{
+ ssize_t ret;
+ pg_gssinfo *gss = port->gss;
+
+ enlargeStringInfo(&gss->buf, len - gss->buf.len + 4);
+
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ len - gss->buf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->buf.cursor > 0)
+ return read_from_buffer(gss, ptr, len);
+
+ /* load length if not present */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ return ret;
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ return ret;
+
+ /* decrypt the packet */
+ output.value = NULL;
+ output.length = 0;
+ input.value = gss->buf.data + 4;
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* put the decrypted packet in the buffer */
+ resetStringInfo(&gss->buf);
+ enlargeStringInfo(&gss->buf, output.length);
+
+ memcpy(gss->buf.data, output.value, output.length);
+ gss->buf.len = output.length;
+ gss->buf.data[gss->buf.len] = '\0';
+
+ ret = read_from_buffer(gss, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ bool complete_next = false;
+
+ /*
+ * 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)
+ {
+ OM_uint32 major, minor;
+ size_t ret;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+
+ /* Handle any outgoing data */
+ if (gss->writebuf.len != 0)
+ {
+ ret = send_buffered_data(port, 1);
+ if (ret != 1)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ }
+
+ if (complete_next)
+ break;
+
+ /* Load incoming data */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ input.value = gss->buf.data + 4;
+
+ /* 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);
+ resetStringInfo(&gss->buf);
+ 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;
+ }
+
+ if (output.length != 0)
+ {
+ /* Queue packet for writing */
+ uint32 netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf,
+ output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ continue;
+ }
+
+ /* We're done - woohoo! */
+ break;
+ }
+ port->gss->enc = true;
+ return 0;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..7dd1cf7090 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -152,6 +152,14 @@ retry:
n = be_tls_read(port, ptr, len, &waitfor);
}
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);
@@ -255,6 +263,14 @@ retry:
n = be_tls_write(port, ptr, len, &waitfor);
}
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);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..9a1aa4e5cc 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, "hostgss") == 0 ||
+ strcmp(token->string, "hostnogss") == 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') /* "hostgss" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgss 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 = "hostgss 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)
@@ -2060,6 +2088,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)
{
@@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgss";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogss";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 084573e77c..72ce147308 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3562,6 +3562,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 a4b53b33cd..87dbb33a88 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask)
* if we detect a communications failure.)
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes((char *) &len, 4) == EOF)
{
/*
- * EOF after SSLdone probably means the client didn't like our
+ * EOF after secure_done probably means the client didn't like our
* response to NEGOTIATE_SSL_CODE. That's not an error condition, so
* don't clutter the log with a complaint.
*/
- if (!SSLdone)
+ if (!secure_done)
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("incomplete startup packet")));
@@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -1994,6 +1994,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 */
@@ -2413,6 +2439,17 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
#endif
return port;
@@ -2429,7 +2466,15 @@ ConnFree(Port *conn)
secure_close(conn);
#endif
if (conn->gss)
+ {
+#ifdef ENABLE_GSS
+ if (conn->gss->buf.data)
+ pfree(conn->gss->buf.data);
+ if (conn->gss->writebuf.data)
+ pfree(conn->gss->writebuf.data);
+#endif
free(conn->gss);
+ }
free(conn);
}
@@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[])
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
/*
* If appropriate, physically re-attach to shared memory segment. We want
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..830ddaa25a 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 struct HbaLine
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..3e46ac437a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -66,7 +66,7 @@ typedef struct
#include "datatype/timestamp.h"
#include "libpq/hba.h"
#include "libpq/pqcomm.h"
-
+#include "lib/stringinfo.h"
typedef enum CAC_state
{
@@ -86,6 +86,9 @@ typedef struct
gss_cred_id_t cred; /* GSSAPI connection cred's */
gss_ctx_id_t ctx; /* GSSAPI connection context */
gss_name_t name; /* GSSAPI client name */
+ bool enc; /* GSSAPI encryption in use */
+ StringInfoData buf; /* GSSAPI encryption data buffering */
+ StringInfoData writebuf; /* GSSAPI nonblocking write buffering */
#endif
} pg_gssinfo;
#endif
@@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len);
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
+#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
+
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 7bf06c65e9..e34f552ef7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -92,6 +92,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 cc0e0b32c7..ade1190096 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 be2f59239b..4f06f7a2bc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -787,7 +787,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;
/* ----------
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..c814e5e35a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ else
OBJS += sha2.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/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3b2073a47f..af2ef78d76 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,18 +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;
- char *host = PQhost(conn);
-
- if (!(host && host[0] != '\0'))
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("host name must be specified\n"));
- return STATUS_ERROR;
- }
+ int ret;
if (conn->gctx)
{
@@ -215,33 +159,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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..2bd1be1b97 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.
@@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssmode similarly to sslmode - we can stil handle "disable" and
+ * "prefer".
+ */
+ {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * validate gssmode option
+ */
+ if (conn->gssmode)
+ {
+ if (strcmp(conn->gssmode, "disable") != 0 &&
+ strcmp(conn->gssmode, "prefer") != 0 &&
+ strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssmode value: \"%s\"\n"),
+ conn->gssmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssmode = strdup(DefaultGSSMode);
+ if (!conn->gssmode)
+ goto oom_error;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn)
conn->wait_ssl_try = true;
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect, with protocol 3.0 as the first attempt.
*/
@@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2426,17 +2479,54 @@ 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 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 (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
+ {
+ 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->gssmode[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 (IS_AF_UNIX(conn->raddr.addr.ss_family))
- {
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
- }
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2629,6 +2719,97 @@ 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->gssmode[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->gssmode[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.
@@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
+#ifdef ENABLE_GSS
+ /*
+ * If gssmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssmode[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
/*
@@ -3361,6 +3560,11 @@ makeEmptyPGconn(void)
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+ initPQExpBuffer(&conn->gbuf);
+ initPQExpBuffer(&conn->gwritebuf);
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->gssmode)
+ free(conn->gssmode);
#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;
+ }
+ termPQExpBuffer(&conn->gbuf);
+ termPQExpBuffer(&conn->gwritebuf);
+#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 0000000000..35e195b7a6
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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 (GSS_ERROR(major))
+ {
+ *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 0000000000..58811df9f1
--- /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 0000000000..a71df69ff2
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * 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
+
+static ssize_t
+send_buffered_data(PGconn *conn, size_t len)
+{
+ ssize_t ret = pqsecure_raw_write(conn,
+ conn->gwritebuf.data + conn->gwritecurs,
+ conn->gwritebuf.len - conn->gwritecurs);
+ if (ret < 0)
+ return ret;
+
+ conn->gwritecurs += ret;
+
+ if (conn->gwritecurs == conn->gwritebuf.len)
+ {
+ /* entire request has now been written */
+ resetPQExpBuffer(&conn->gwritebuf);
+ conn->gwritecurs = 0;
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+
+ if (conn->gwritebuf.len != 0)
+ return send_buffered_data(conn, len);
+
+ /* encrypt the message */
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ goto cleanup;
+ }
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+
+ ret = send_buffered_data(conn, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* check for available data */
+ if (conn->gcursor < conn->gbuf.len)
+ {
+ /* clamp length */
+ if (len > conn->gbuf.len - conn->gcursor)
+ len = conn->gbuf.len - conn->gcursor;
+
+ memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+ conn->gcursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (conn->gcursor == conn->gbuf.len)
+ {
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ }
+
+ return ret;
+}
+
+static ssize_t
+load_packet_length(PGconn *conn)
+{
+ ssize_t ret;
+ if (conn->gbuf.len < 4)
+ {
+ ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "Failed to fit packet length in buffer\n"));
+ return -1;
+ }
+
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ 4 - conn->gbuf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(PGconn *conn, size_t len)
+{
+ ssize_t ret;
+
+ ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI encrypted packet length %ld too big\n"),
+ len);
+ return -1;
+ }
+
+ /* load any missing parts of the packet */
+ if (conn->gbuf.len - 4 < len)
+ {
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ len - conn->gbuf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+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;
+ int conf = 0;
+
+ /* handle any buffered data */
+ if (conn->gcursor != 0)
+ return read_from_buffer(conn, ptr, len);
+
+ /* load in the packet length, if not yet loaded */
+ ret = load_packet_length(conn);
+ if (ret < 0)
+ return ret;
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0)
+ return ret;
+
+ /* decrypt the packet */
+ input.value = conn->gbuf.data + 4;
+
+ major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* load decrypted packet into our buffer */
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI decrypted packet length %ld too big\n"),
+ output.length);
+ ret = -1;
+ goto cleanup;
+ }
+
+ memcpy(conn->gbuf.data, output.value, output.length);
+ conn->gbuf.len = output.length;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+
+ ret = read_from_buffer(conn, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ ssize_t ret;
+ OM_uint32 major, minor;
+ uint32 netlen;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER;
+
+ /* Send out any data we might have */
+ if (conn->gwritebuf.len != 0)
+ {
+ ret = send_buffered_data(conn, 1);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+ else if (ret == 1)
+ /* sent all data */
+ return PGRES_POLLING_READING;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Client sends first, and sending creates a context */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+ ret = load_packet_length(conn);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (conn->gbuf.data[0] == 'E')
+ {
+ /*
+ * We can taken an error here, and it's my least favorite thing.
+ * How long can error messages be? That's a good question, but
+ * backend's pg_gss_error() caps them at 256. Do a single read
+ * for that and call it a day. Cope with this here rather than in
+ * load_packet since expandPQExpBuffer will destroy any data in
+ * the buffer on failure.
+ */
+ load_packet(conn, 256);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ conn->gbuf.data + 1);
+ return PGRES_POLLING_FAILED;
+ }
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ input.value = conn->gbuf.data + 4;
+ }
+
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ 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);
+ resetPQExpBuffer(&conn->gbuf);
+ 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;
+ return PGRES_POLLING_OK;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ return PGRES_POLLING_WRITING;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..18155e4b5e 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, 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);
@@ -287,7 +294,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
* to determine whether to continue/retry after error.
*/
ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
@@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, 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);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..bdd5a10cd8 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..6cf3459a8c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -22,6 +22,7 @@
/* We assume libpq-fe.h has already been included. */
#include "libpq-events.h"
+#include "lib/stringinfo.h"
#include <time.h>
#ifndef WIN32
@@ -473,9 +474,19 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssmode; /* 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 */
+ PQExpBufferData gbuf; /* GSS encryption buffering */
+ size_t gcursor; /* GSS buffering position */
+ PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */
+ size_t gwritecurs; /* GSS write buffer position */
+ 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
@@ -651,7 +662,7 @@ extern void pqsecure_destroy(void);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -745,6 +756,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, 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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 593732fd95..bb32d4db1d 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -183,13 +183,20 @@ sub mkvcbuild
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
- # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
- # if building without OpenSSL
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # be-secure-openssl.c if building without OpenSSL, and
+ # be-gssapi-common.{c,h} and be-secure-gssapi.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$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.h');
+ $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');
@@ -243,9 +250,10 @@ sub mkvcbuild
'src/interfaces/libpq/libpq.rc');
$libpq->AddReference($libpgport);
- # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
- # building with OpenSSL.
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # fe-secure-openssl.c and sha2_openssl.c if building without OpenSSL, and
+ # remove sha2.c if building with OpenSSL. Also remove
+ # fe-gssapi-common.{c,h} and fe-secure-gssapi.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
@@ -256,6 +264,12 @@ sub mkvcbuild
{
$libpq->RemoveFile('src/common/sha2.c');
}
+ if (!$solution->{options}->{gss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
+ $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.h');
+ $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c');
+ }
my $libpqwalreceiver =
$solution->AddProject('libpqwalreceiver', 'dll', '',
--
2.17.0
--=-=-=--
^ permalink raw reply [nested|flat] 20+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Robbie Harwood @ 2018-05-10 20:12 UTC (permalink / raw)
On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.
Add frontend and backend encryption support functions. Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.
In postmaster, pull GSSAPI authorization checking into a shared
function. Also share the initiator name between the encryption and
non-encryption codepaths.
Modify pqsecure_write() to take a non-const pointer. The pointer will
not be modified, but this change (or a const-discarding cast, or a
malloc()+memcpy()) is necessary for GSSAPI due to const/struct
interactions in C.
For HBA, add "hostgss" and "hostnogss" entries that behave similarly
to their SSL counterparts. "hostgss" requires either "gss", "trust",
or "reject" for its authentication.
Simiarly, add a "gssmode" parameter to libpq. Supported values are
"disable", "require", and "prefer". Notably, negotiation will only be
attempted if credentials can be acquired. Move credential acquisition
into its own function to support this behavior.
Finally, add documentation for everything new, and update existing
documentation on connection security.
Thanks to Michael Paquier for the Windows fixes.
---
doc/src/sgml/client-auth.sgml | 75 ++++--
doc/src/sgml/libpq.sgml | 57 +++-
doc/src/sgml/runtime.sgml | 77 +++++-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 103 +++----
src/backend/libpq/be-gssapi-common.c | 64 +++++
src/backend/libpq/be-gssapi-common.h | 26 ++
src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++
src/backend/libpq/be-secure.c | 16 ++
src/backend/libpq/hba.c | 51 +++-
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/postmaster.c | 64 ++++-
src/include/libpq/hba.h | 4 +-
src/include/libpq/libpq-be.h | 11 +-
src/include/libpq/libpq.h | 3 +
src/include/libpq/pqcomm.h | 5 +-
src/include/pgstat.h | 3 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-auth.c | 90 +------
src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++-
src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++
src/interfaces/libpq/fe-gssapi-common.h | 23 ++
src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-fe.h | 3 +-
src/interfaces/libpq/libpq-int.h | 30 ++-
src/tools/msvc/Mkvcbuild.pm | 22 +-
27 files changed, 1574 insertions(+), 202 deletions(-)
create mode 100644 src/backend/libpq/be-gssapi-common.c
create mode 100644 src/backend/libpq/be-gssapi-common.h
create mode 100644 src/backend/libpq/be-secure-gssapi.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.h
create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 656d5f9417..38cf32e3be 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
</synopsis>
The meaning of the fields is as follows:
@@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
This record matches connection attempts made using TCP/IP.
- <literal>host</literal> records match either
+ <literal>host</literal> records match
<acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection
- attempts.
+ attempts as well as <acronym>GSSAPI</acronym> or
+ non-<acronym>GSSAPI</acronym> connection attempts.
</para>
<note>
<para>
@@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>hostgss</literal></term>
+ <listitem>
+ <para>
+ This record matches connection attempts made using TCP/IP,
+ but only when the connection is made with <acronym>GSSAPI</acronym>
+ encryption.
+ </para>
+
+ <para>
+ To make use of this option the server must be built with
+ <acronym>GSSAPI</acronym> support. Otherwise,
+ the <literal>hostgss</literal> record is ignored except for logging a
+ warning that it cannot match any connections.
+ </para>
+
+ <para>
+ Note that the only supported <xref linkend="auth-methods"/> for use
+ with <acronym>GSSAPI</acronym> encryption
+ are <literal>gss</literal>, <literal>reject</literal>,
+ and <literal>trust</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>hostnogss</literal></term>
+ <listitem>
+ <para>
+ This record type has the opposite behavior of <literal>hostgss</literal>;
+ it only matches connection attempts made over
+ TCP/IP that do not use <acronym>GSSAPI</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>database</replaceable></term>
<listitem>
@@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
Use GSSAPI to authenticate the user. This is only
- available for TCP/IP connections. See <xref
- linkend="gssapi-auth"/> for details.
+ available for TCP/IP connections . See <xref
+ linkend="gssapi-auth"/> for details. It can be used in conjunction
+ with GSSAPI encryption.
</para>
</listitem>
</varlistentry>
@@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256
host all mike .example.com md5
host all all .example.com scram-sha-256
-# In the absence of preceding "host" lines, these two lines will
+# In the absence of preceding "host" lines, these three lines will
# reject all connections from 192.168.54.1 (since that entry will be
-# matched first), but allow GSSAPI connections from anywhere else
-# on the Internet. The zero mask causes no bits of the host IP
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet. Unencrypted GSSAPI connections are allowed from
+# 192.168.12.10 only. The zero mask causes no bits of the host IP
# address to be considered, so it matches any host.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 192.168.54.1/32 reject
-host all all 0.0.0.0/0 gss
+hostgss all all 0.0.0.0/0 gss
+host all all 192.168.12.10/32 gss
# Allow users from 192.168.x.x hosts to connect to any database, if
# they pass the ident check. If, for example, ident says the user is
@@ -1051,13 +1093,16 @@ omicron bryanh guest1
<para>
<productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in RFC 2743.
- <productname>PostgreSQL</productname> supports
- <productname>GSSAPI</productname> with <productname>Kerberos</productname>
- authentication according to RFC 1964. <productname>GSSAPI</productname>
- provides automatic authentication (single sign-on) for systems
- that support it. The authentication itself is secure, but the
- data sent over the database connection will be sent unencrypted unless
- <acronym>SSL</acronym> is used.
+
+ <productname>PostgreSQL</productname>
+ supports <productname>GSSAPI</productname> for use as either an encrypted,
+ authenticated layer, or as authentication
+ only. <productname>GSSAPI</productname> provides automatic authentication
+ (single sign-on) for systems that support it. The authentication itself is
+ secure. If <productname>GSSAPI</productname> encryption
+ (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are
+ used, the data sent along the database connection will be encrypted;
+ otherwise, it will not.
</para>
<para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 800e68a19e..73d233dc16 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode">
+ <term><literal>gssmode</literal></term>
+ <listitem>
+ <para>
+ This option determines whether or with what priority a secure
+ <acronym>GSS</acronym> TCP/IP connection will be negotiated with the
+ server. There are three modes:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>disable</literal></term>
+ <listitem>
+ <para>
+ only try a non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>prefer</literal> (default)</term>
+ <listitem>
+ <para>
+ if there are <acronym>GSSAPI</acronym> credentials present (i.e.,
+ in a credentials cache), first try
+ a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or
+ there are no credentials, try a
+ non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>require</literal></term>
+ <listitem>
+ <para>
+ only try a <acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ <literal>gssmode</literal> is ignored for Unix domain socket
+ communication. If <productname>PostgreSQL</productname> is compiled
+ without GSSAPI support, using the <literal>require</literal> option
+ will cause an error, while <literal>prefer</literal> will be accepted
+ but <application>libpq</application> will not actually attempt
+ a <acronym>GSSAPI</acronym>-encrypted
+ connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with
+ libpq</secondary></indexterm>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
<term><literal>sslmode</literal></term>
<listitem>
@@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- For a connection to be known secure, SSL usage must be configured
+ For a connection to be known SSL-secured, SSL usage must be configured
on <emphasis>both the client and the server</emphasis> before the connection
is made. If it is only configured on the server, the client may end up
sending sensitive information (e.g. passwords) before
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 330e38a29e..06bf56153c 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To prevent spoofing on TCP connections, the best solution is to use
- SSL certificates and make sure that clients check the server's certificate.
- To do that, the server
+ To prevent spoofing on TCP connections, either use
+ SSL certificates and make sure that clients check the server's certificate,
+ or use GSSAPI encryption (or both, if they're on separate connections).
+ </para>
+
+ <para>
+ To prevent spoofing with SSL, the server
must be configured to accept only <literal>hostssl</literal> connections (<xref
linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files
(<xref linkend="ssl-tcp"/>). The TCP client must connect using
@@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<literal>verify-full</literal> and have the appropriate root certificate
file installed (<xref linkend="libq-ssl-certificates"/>).
</para>
+
+ <para>
+ To prevent spoofing with GSSAPI, the server must be configured to accept
+ only <literal>hostgss</literal> connections
+ (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal>
+ authentication with them. The TCP client must connect
+ using <literal>gssmode=require</literal>.
+ </para>
</sect1>
<sect1 id="encryption-options">
@@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
which hosts can use non-encrypted connections (<literal>host</literal>)
and which require SSL-encrypted connections
(<literal>hostssl</literal>). Also, clients can specify that they
- connect to servers only via SSL. <application>Stunnel</application> or
- <application>SSH</application> can also be used to encrypt transmissions.
+ connect to servers only via SSL.
+ </para>
+
+ <para>
+ GSSAPI-encrypted connections encrypt all data sent across the network,
+ including queries and data returned. (No password is sent across the
+ network.) The <filename>pg_hba.conf</filename> file allows
+ administrators to specify which hosts can use non-encrypted connections
+ (<literal>host</literal>) and which require GSSAPI-encrypted connections
+ (<literal>hostgss</literal>). Also, clients can specify that they
+ connect to servers only on GSSAPI-encrypted connections
+ (<literal>gssmode=require</literal>).
+ </para>
+
+ <para>
+ <application>Stunnel</application> or
+ <application>SSH</application> can also be used to encrypt
+ transmissions.
</para>
</listitem>
</varlistentry>
@@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</sect1>
+ <sect1 id="gssapi-enc">
+ <title>Secure TCP/IP Connections with GSSAPI encryption</title>
+
+ <indexterm zone="gssapi-enc">
+ <primary>gssapi</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> also has native support for
+ using <acronym>GSSAPI</acronym> to encrypt client/server communications for
+ increased security. Support requires that a <acronym>GSSAPI</acronym>
+ implementation (such as MIT krb5) is installed on both client and server
+ systems, and that support in <productname>PostgreSQL</productname> is
+ enabled at build time (see <xref linkend="installation"/>).
+ </para>
+
+ <sect2 id="gssapi-setup">
+ <title>Basic Setup</title>
+
+ <para>
+ The <productname>PostgreSQL</productname> server will listen for both
+ normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP
+ port, and will negotiate with any connecting client on whether to
+ use <acronym>GSSAPI</acronym> for encryption (and for authentication). By
+ default, this decision is up to the client (which means it can be
+ downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about
+ setting up the server to require the use of <acronym>GSSAPI</acronym> for
+ some or all conections.
+ </para>
+
+ <para>
+ Other than configuration of the negotiation
+ behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond
+ that which necessary for GSSAPI authentication. (For more information on
+ configuring that, see <xref linkend="gssapi-auth"/>.)
+ </para>
+ </sect2>
+ </sect1>
+
<sect1 id="ssh-tunnels">
<title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..47efef0682 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 3014b17a7c..6e4cd66a90 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -173,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 */
@@ -384,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 cannot be combined with non-GSSAPI authentication")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -524,8 +532,13 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ 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
@@ -1044,64 +1057,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
-
-
-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)
{
@@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat, min_stat;
+ 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.
*/
@@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI realm (%s) and configured realm (%s) don't match",
cp, port->hba->krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
}
@@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
pg_krb_caseins_users);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return ret;
}
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 0000000000..78d9f5d325
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+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")));
+}
+
+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 0000000000..c020051d2e
--- /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 0000000000..d860f71b71
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,319 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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>
+
+static ssize_t
+send_buffered_data(Port *port, size_t len)
+{
+ ssize_t ret = secure_raw_write(
+ port,
+ port->gss->writebuf.data + port->gss->writebuf.cursor,
+ port->gss->writebuf.len - port->gss->writebuf.cursor);
+ if (ret < 0)
+ return ret;
+
+ /* update and possibly clear buffer state */
+ port->gss->writebuf.cursor += ret;
+
+ if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+ {
+ /* entire request has now been written */
+ resetStringInfo(&port->gss->writebuf);
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->writebuf.len != 0)
+ return send_buffered_data(port, len);
+
+ /* encrypt the message */
+ output.value = NULL;
+ output.length = 0;
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor);
+ goto cleanup;
+ } else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ goto cleanup;
+ }
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf, output.value, output.length);
+
+ ret = send_buffered_data(port, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* load up any available data */
+ if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len)
+ {
+ /* clamp length */
+ if (len > gss->buf.len - gss->buf.cursor)
+ len = gss->buf.len - gss->buf.cursor;
+
+ memcpy(ptr, gss->buf.data + gss->buf.cursor, len);
+ gss->buf.cursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (gss->buf.cursor == gss->buf.len)
+ resetStringInfo(&gss->buf);
+
+ return ret;
+}
+
+static ssize_t
+load_packetlen(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ ssize_t ret;
+
+ if (gss->buf.len < 4)
+ {
+ enlargeStringInfo(&gss->buf, 4 - gss->buf.len);
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ 4 - gss->buf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(Port *port, size_t len)
+{
+ ssize_t ret;
+ pg_gssinfo *gss = port->gss;
+
+ enlargeStringInfo(&gss->buf, len - gss->buf.len + 4);
+
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ len - gss->buf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->buf.cursor > 0)
+ return read_from_buffer(gss, ptr, len);
+
+ /* load length if not present */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ return ret;
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ return ret;
+
+ /* decrypt the packet */
+ output.value = NULL;
+ output.length = 0;
+ input.value = gss->buf.data + 4;
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* put the decrypted packet in the buffer */
+ resetStringInfo(&gss->buf);
+ enlargeStringInfo(&gss->buf, output.length);
+
+ memcpy(gss->buf.data, output.value, output.length);
+ gss->buf.len = output.length;
+ gss->buf.data[gss->buf.len] = '\0';
+
+ ret = read_from_buffer(gss, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ bool complete_next = false;
+
+ /*
+ * 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)
+ {
+ OM_uint32 major, minor;
+ size_t ret;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+
+ /* Handle any outgoing data */
+ if (gss->writebuf.len != 0)
+ {
+ ret = send_buffered_data(port, 1);
+ if (ret != 1)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ }
+
+ if (complete_next)
+ break;
+
+ /* Load incoming data */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ input.value = gss->buf.data + 4;
+
+ /* 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);
+ resetStringInfo(&gss->buf);
+ 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;
+ }
+
+ if (output.length != 0)
+ {
+ /* Queue packet for writing */
+ uint32 netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf,
+ output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ continue;
+ }
+
+ /* We're done - woohoo! */
+ break;
+ }
+ port->gss->enc = true;
+ return 0;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..7dd1cf7090 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -152,6 +152,14 @@ retry:
n = be_tls_read(port, ptr, len, &waitfor);
}
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);
@@ -255,6 +263,14 @@ retry:
n = be_tls_write(port, ptr, len, &waitfor);
}
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);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..9a1aa4e5cc 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, "hostgss") == 0 ||
+ strcmp(token->string, "hostnogss") == 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') /* "hostgss" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgss 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 = "hostgss 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)
@@ -2060,6 +2088,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)
{
@@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgss";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogss";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 084573e77c..72ce147308 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3562,6 +3562,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 a4b53b33cd..87dbb33a88 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask)
* if we detect a communications failure.)
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes((char *) &len, 4) == EOF)
{
/*
- * EOF after SSLdone probably means the client didn't like our
+ * EOF after secure_done probably means the client didn't like our
* response to NEGOTIATE_SSL_CODE. That's not an error condition, so
* don't clutter the log with a complaint.
*/
- if (!SSLdone)
+ if (!secure_done)
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("incomplete startup packet")));
@@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -1994,6 +1994,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 */
@@ -2413,6 +2439,17 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
#endif
return port;
@@ -2429,7 +2466,15 @@ ConnFree(Port *conn)
secure_close(conn);
#endif
if (conn->gss)
+ {
+#ifdef ENABLE_GSS
+ if (conn->gss->buf.data)
+ pfree(conn->gss->buf.data);
+ if (conn->gss->writebuf.data)
+ pfree(conn->gss->writebuf.data);
+#endif
free(conn->gss);
+ }
free(conn);
}
@@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[])
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
/*
* If appropriate, physically re-attach to shared memory segment. We want
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..830ddaa25a 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 struct HbaLine
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..3e46ac437a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -66,7 +66,7 @@ typedef struct
#include "datatype/timestamp.h"
#include "libpq/hba.h"
#include "libpq/pqcomm.h"
-
+#include "lib/stringinfo.h"
typedef enum CAC_state
{
@@ -86,6 +86,9 @@ typedef struct
gss_cred_id_t cred; /* GSSAPI connection cred's */
gss_ctx_id_t ctx; /* GSSAPI connection context */
gss_name_t name; /* GSSAPI client name */
+ bool enc; /* GSSAPI encryption in use */
+ StringInfoData buf; /* GSSAPI encryption data buffering */
+ StringInfoData writebuf; /* GSSAPI nonblocking write buffering */
#endif
} pg_gssinfo;
#endif
@@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len);
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
+#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
+
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 7bf06c65e9..e34f552ef7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -92,6 +92,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 cc0e0b32c7..ade1190096 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 be2f59239b..4f06f7a2bc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -787,7 +787,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;
/* ----------
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..c814e5e35a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ else
OBJS += sha2.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/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3b2073a47f..af2ef78d76 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,18 +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;
- char *host = PQhost(conn);
-
- if (!(host && host[0] != '\0'))
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("host name must be specified\n"));
- return STATUS_ERROR;
- }
+ int ret;
if (conn->gctx)
{
@@ -215,33 +159,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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..2bd1be1b97 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.
@@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssmode similarly to sslmode - we can stil handle "disable" and
+ * "prefer".
+ */
+ {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * validate gssmode option
+ */
+ if (conn->gssmode)
+ {
+ if (strcmp(conn->gssmode, "disable") != 0 &&
+ strcmp(conn->gssmode, "prefer") != 0 &&
+ strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssmode value: \"%s\"\n"),
+ conn->gssmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssmode = strdup(DefaultGSSMode);
+ if (!conn->gssmode)
+ goto oom_error;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn)
conn->wait_ssl_try = true;
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect, with protocol 3.0 as the first attempt.
*/
@@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2426,17 +2479,54 @@ 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 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 (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
+ {
+ 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->gssmode[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 (IS_AF_UNIX(conn->raddr.addr.ss_family))
- {
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
- }
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2629,6 +2719,97 @@ 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->gssmode[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->gssmode[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.
@@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
+#ifdef ENABLE_GSS
+ /*
+ * If gssmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssmode[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
/*
@@ -3361,6 +3560,11 @@ makeEmptyPGconn(void)
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+ initPQExpBuffer(&conn->gbuf);
+ initPQExpBuffer(&conn->gwritebuf);
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->gssmode)
+ free(conn->gssmode);
#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;
+ }
+ termPQExpBuffer(&conn->gbuf);
+ termPQExpBuffer(&conn->gwritebuf);
+#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 0000000000..35e195b7a6
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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 (GSS_ERROR(major))
+ {
+ *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 0000000000..58811df9f1
--- /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 0000000000..a71df69ff2
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * 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
+
+static ssize_t
+send_buffered_data(PGconn *conn, size_t len)
+{
+ ssize_t ret = pqsecure_raw_write(conn,
+ conn->gwritebuf.data + conn->gwritecurs,
+ conn->gwritebuf.len - conn->gwritecurs);
+ if (ret < 0)
+ return ret;
+
+ conn->gwritecurs += ret;
+
+ if (conn->gwritecurs == conn->gwritebuf.len)
+ {
+ /* entire request has now been written */
+ resetPQExpBuffer(&conn->gwritebuf);
+ conn->gwritecurs = 0;
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+
+ if (conn->gwritebuf.len != 0)
+ return send_buffered_data(conn, len);
+
+ /* encrypt the message */
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ goto cleanup;
+ }
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+
+ ret = send_buffered_data(conn, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* check for available data */
+ if (conn->gcursor < conn->gbuf.len)
+ {
+ /* clamp length */
+ if (len > conn->gbuf.len - conn->gcursor)
+ len = conn->gbuf.len - conn->gcursor;
+
+ memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+ conn->gcursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (conn->gcursor == conn->gbuf.len)
+ {
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ }
+
+ return ret;
+}
+
+static ssize_t
+load_packet_length(PGconn *conn)
+{
+ ssize_t ret;
+ if (conn->gbuf.len < 4)
+ {
+ ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "Failed to fit packet length in buffer\n"));
+ return -1;
+ }
+
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ 4 - conn->gbuf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(PGconn *conn, size_t len)
+{
+ ssize_t ret;
+
+ ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI encrypted packet length %ld too big\n"),
+ len);
+ return -1;
+ }
+
+ /* load any missing parts of the packet */
+ if (conn->gbuf.len - 4 < len)
+ {
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ len - conn->gbuf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+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;
+ int conf = 0;
+
+ /* handle any buffered data */
+ if (conn->gcursor != 0)
+ return read_from_buffer(conn, ptr, len);
+
+ /* load in the packet length, if not yet loaded */
+ ret = load_packet_length(conn);
+ if (ret < 0)
+ return ret;
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0)
+ return ret;
+
+ /* decrypt the packet */
+ input.value = conn->gbuf.data + 4;
+
+ major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* load decrypted packet into our buffer */
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI decrypted packet length %ld too big\n"),
+ output.length);
+ ret = -1;
+ goto cleanup;
+ }
+
+ memcpy(conn->gbuf.data, output.value, output.length);
+ conn->gbuf.len = output.length;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+
+ ret = read_from_buffer(conn, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ ssize_t ret;
+ OM_uint32 major, minor;
+ uint32 netlen;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER;
+
+ /* Send out any data we might have */
+ if (conn->gwritebuf.len != 0)
+ {
+ ret = send_buffered_data(conn, 1);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+ else if (ret == 1)
+ /* sent all data */
+ return PGRES_POLLING_READING;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Client sends first, and sending creates a context */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+ ret = load_packet_length(conn);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (conn->gbuf.data[0] == 'E')
+ {
+ /*
+ * We can taken an error here, and it's my least favorite thing.
+ * How long can error messages be? That's a good question, but
+ * backend's pg_gss_error() caps them at 256. Do a single read
+ * for that and call it a day. Cope with this here rather than in
+ * load_packet since expandPQExpBuffer will destroy any data in
+ * the buffer on failure.
+ */
+ load_packet(conn, 256);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ conn->gbuf.data + 1);
+ return PGRES_POLLING_FAILED;
+ }
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ input.value = conn->gbuf.data + 4;
+ }
+
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ 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);
+ resetPQExpBuffer(&conn->gbuf);
+ 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;
+ return PGRES_POLLING_OK;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ return PGRES_POLLING_WRITING;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..18155e4b5e 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, 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);
@@ -287,7 +294,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
* to determine whether to continue/retry after error.
*/
ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
@@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, 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);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..bdd5a10cd8 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..6cf3459a8c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -22,6 +22,7 @@
/* We assume libpq-fe.h has already been included. */
#include "libpq-events.h"
+#include "lib/stringinfo.h"
#include <time.h>
#ifndef WIN32
@@ -473,9 +474,19 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssmode; /* 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 */
+ PQExpBufferData gbuf; /* GSS encryption buffering */
+ size_t gcursor; /* GSS buffering position */
+ PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */
+ size_t gwritecurs; /* GSS write buffer position */
+ 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
@@ -651,7 +662,7 @@ extern void pqsecure_destroy(void);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -745,6 +756,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, 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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 593732fd95..e152d7056b 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -183,13 +183,19 @@ sub mkvcbuild
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
- # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
- # if building without OpenSSL
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # be-secure-openssl.c if building without OpenSSL, and
+ # be-gssapi-common.c and be-secure-gssapi.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$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');
@@ -243,9 +249,10 @@ sub mkvcbuild
'src/interfaces/libpq/libpq.rc');
$libpq->AddReference($libpgport);
- # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
- # building with OpenSSL.
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # fe-secure-openssl.c and sha2_openssl.c if building without OpenSSL, and
+ # remove sha2.c if building with OpenSSL. Also remove
+ # fe-gssapi-common.c and fe-secure-gssapi.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
@@ -256,6 +263,11 @@ sub mkvcbuild
{
$libpq->RemoveFile('src/common/sha2.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', '',
--
2.17.1
--=-=-=--
^ permalink raw reply [nested|flat] 20+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Robbie Harwood @ 2018-05-10 20:12 UTC (permalink / raw)
On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.
Add frontend and backend encryption support functions. Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.
In postmaster, pull GSSAPI authorization checking into a shared
function. Also share the initiator name between the encryption and
non-encryption codepaths.
Modify pqsecure_write() to take a non-const pointer. The pointer will
not be modified, but this change (or a const-discarding cast, or a
malloc()+memcpy()) is necessary for GSSAPI due to const/struct
interactions in C.
For HBA, add "hostgss" and "hostnogss" entries that behave similarly
to their SSL counterparts. "hostgss" requires either "gss", "trust",
or "reject" for its authentication.
Simiarly, add a "gssmode" parameter to libpq. Supported values are
"disable", "require", and "prefer". Notably, negotiation will only be
attempted if credentials can be acquired. Move credential acquisition
into its own function to support this behavior.
Finally, add documentation for everything new, and update existing
documentation on connection security.
Thanks to Michael Paquier for the Windows fixes.
---
doc/src/sgml/client-auth.sgml | 75 ++++--
doc/src/sgml/libpq.sgml | 57 +++-
doc/src/sgml/runtime.sgml | 77 +++++-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 103 +++----
src/backend/libpq/be-gssapi-common.c | 64 +++++
src/backend/libpq/be-gssapi-common.h | 26 ++
src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++
src/backend/libpq/be-secure.c | 16 ++
src/backend/libpq/hba.c | 51 +++-
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/postmaster.c | 64 ++++-
src/include/libpq/hba.h | 4 +-
src/include/libpq/libpq-be.h | 11 +-
src/include/libpq/libpq.h | 3 +
src/include/libpq/pqcomm.h | 5 +-
src/include/pgstat.h | 3 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-auth.c | 90 +------
src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++-
src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++
src/interfaces/libpq/fe-gssapi-common.h | 23 ++
src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-fe.h | 3 +-
src/interfaces/libpq/libpq-int.h | 30 ++-
src/tools/msvc/Mkvcbuild.pm | 20 +-
27 files changed, 1572 insertions(+), 202 deletions(-)
create mode 100644 src/backend/libpq/be-gssapi-common.c
create mode 100644 src/backend/libpq/be-gssapi-common.h
create mode 100644 src/backend/libpq/be-secure-gssapi.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.h
create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 656d5f9417..38cf32e3be 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
</synopsis>
The meaning of the fields is as follows:
@@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
This record matches connection attempts made using TCP/IP.
- <literal>host</literal> records match either
+ <literal>host</literal> records match
<acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection
- attempts.
+ attempts as well as <acronym>GSSAPI</acronym> or
+ non-<acronym>GSSAPI</acronym> connection attempts.
</para>
<note>
<para>
@@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>hostgss</literal></term>
+ <listitem>
+ <para>
+ This record matches connection attempts made using TCP/IP,
+ but only when the connection is made with <acronym>GSSAPI</acronym>
+ encryption.
+ </para>
+
+ <para>
+ To make use of this option the server must be built with
+ <acronym>GSSAPI</acronym> support. Otherwise,
+ the <literal>hostgss</literal> record is ignored except for logging a
+ warning that it cannot match any connections.
+ </para>
+
+ <para>
+ Note that the only supported <xref linkend="auth-methods"/> for use
+ with <acronym>GSSAPI</acronym> encryption
+ are <literal>gss</literal>, <literal>reject</literal>,
+ and <literal>trust</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>hostnogss</literal></term>
+ <listitem>
+ <para>
+ This record type has the opposite behavior of <literal>hostgss</literal>;
+ it only matches connection attempts made over
+ TCP/IP that do not use <acronym>GSSAPI</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>database</replaceable></term>
<listitem>
@@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
Use GSSAPI to authenticate the user. This is only
- available for TCP/IP connections. See <xref
- linkend="gssapi-auth"/> for details.
+ available for TCP/IP connections . See <xref
+ linkend="gssapi-auth"/> for details. It can be used in conjunction
+ with GSSAPI encryption.
</para>
</listitem>
</varlistentry>
@@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256
host all mike .example.com md5
host all all .example.com scram-sha-256
-# In the absence of preceding "host" lines, these two lines will
+# In the absence of preceding "host" lines, these three lines will
# reject all connections from 192.168.54.1 (since that entry will be
-# matched first), but allow GSSAPI connections from anywhere else
-# on the Internet. The zero mask causes no bits of the host IP
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet. Unencrypted GSSAPI connections are allowed from
+# 192.168.12.10 only. The zero mask causes no bits of the host IP
# address to be considered, so it matches any host.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 192.168.54.1/32 reject
-host all all 0.0.0.0/0 gss
+hostgss all all 0.0.0.0/0 gss
+host all all 192.168.12.10/32 gss
# Allow users from 192.168.x.x hosts to connect to any database, if
# they pass the ident check. If, for example, ident says the user is
@@ -1051,13 +1093,16 @@ omicron bryanh guest1
<para>
<productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in RFC 2743.
- <productname>PostgreSQL</productname> supports
- <productname>GSSAPI</productname> with <productname>Kerberos</productname>
- authentication according to RFC 1964. <productname>GSSAPI</productname>
- provides automatic authentication (single sign-on) for systems
- that support it. The authentication itself is secure, but the
- data sent over the database connection will be sent unencrypted unless
- <acronym>SSL</acronym> is used.
+
+ <productname>PostgreSQL</productname>
+ supports <productname>GSSAPI</productname> for use as either an encrypted,
+ authenticated layer, or as authentication
+ only. <productname>GSSAPI</productname> provides automatic authentication
+ (single sign-on) for systems that support it. The authentication itself is
+ secure. If <productname>GSSAPI</productname> encryption
+ (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are
+ used, the data sent along the database connection will be encrypted;
+ otherwise, it will not.
</para>
<para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 800e68a19e..73d233dc16 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode">
+ <term><literal>gssmode</literal></term>
+ <listitem>
+ <para>
+ This option determines whether or with what priority a secure
+ <acronym>GSS</acronym> TCP/IP connection will be negotiated with the
+ server. There are three modes:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>disable</literal></term>
+ <listitem>
+ <para>
+ only try a non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>prefer</literal> (default)</term>
+ <listitem>
+ <para>
+ if there are <acronym>GSSAPI</acronym> credentials present (i.e.,
+ in a credentials cache), first try
+ a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or
+ there are no credentials, try a
+ non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>require</literal></term>
+ <listitem>
+ <para>
+ only try a <acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ <literal>gssmode</literal> is ignored for Unix domain socket
+ communication. If <productname>PostgreSQL</productname> is compiled
+ without GSSAPI support, using the <literal>require</literal> option
+ will cause an error, while <literal>prefer</literal> will be accepted
+ but <application>libpq</application> will not actually attempt
+ a <acronym>GSSAPI</acronym>-encrypted
+ connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with
+ libpq</secondary></indexterm>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
<term><literal>sslmode</literal></term>
<listitem>
@@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- For a connection to be known secure, SSL usage must be configured
+ For a connection to be known SSL-secured, SSL usage must be configured
on <emphasis>both the client and the server</emphasis> before the connection
is made. If it is only configured on the server, the client may end up
sending sensitive information (e.g. passwords) before
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 330e38a29e..06bf56153c 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To prevent spoofing on TCP connections, the best solution is to use
- SSL certificates and make sure that clients check the server's certificate.
- To do that, the server
+ To prevent spoofing on TCP connections, either use
+ SSL certificates and make sure that clients check the server's certificate,
+ or use GSSAPI encryption (or both, if they're on separate connections).
+ </para>
+
+ <para>
+ To prevent spoofing with SSL, the server
must be configured to accept only <literal>hostssl</literal> connections (<xref
linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files
(<xref linkend="ssl-tcp"/>). The TCP client must connect using
@@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<literal>verify-full</literal> and have the appropriate root certificate
file installed (<xref linkend="libq-ssl-certificates"/>).
</para>
+
+ <para>
+ To prevent spoofing with GSSAPI, the server must be configured to accept
+ only <literal>hostgss</literal> connections
+ (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal>
+ authentication with them. The TCP client must connect
+ using <literal>gssmode=require</literal>.
+ </para>
</sect1>
<sect1 id="encryption-options">
@@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
which hosts can use non-encrypted connections (<literal>host</literal>)
and which require SSL-encrypted connections
(<literal>hostssl</literal>). Also, clients can specify that they
- connect to servers only via SSL. <application>Stunnel</application> or
- <application>SSH</application> can also be used to encrypt transmissions.
+ connect to servers only via SSL.
+ </para>
+
+ <para>
+ GSSAPI-encrypted connections encrypt all data sent across the network,
+ including queries and data returned. (No password is sent across the
+ network.) The <filename>pg_hba.conf</filename> file allows
+ administrators to specify which hosts can use non-encrypted connections
+ (<literal>host</literal>) and which require GSSAPI-encrypted connections
+ (<literal>hostgss</literal>). Also, clients can specify that they
+ connect to servers only on GSSAPI-encrypted connections
+ (<literal>gssmode=require</literal>).
+ </para>
+
+ <para>
+ <application>Stunnel</application> or
+ <application>SSH</application> can also be used to encrypt
+ transmissions.
</para>
</listitem>
</varlistentry>
@@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</sect1>
+ <sect1 id="gssapi-enc">
+ <title>Secure TCP/IP Connections with GSSAPI encryption</title>
+
+ <indexterm zone="gssapi-enc">
+ <primary>gssapi</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> also has native support for
+ using <acronym>GSSAPI</acronym> to encrypt client/server communications for
+ increased security. Support requires that a <acronym>GSSAPI</acronym>
+ implementation (such as MIT krb5) is installed on both client and server
+ systems, and that support in <productname>PostgreSQL</productname> is
+ enabled at build time (see <xref linkend="installation"/>).
+ </para>
+
+ <sect2 id="gssapi-setup">
+ <title>Basic Setup</title>
+
+ <para>
+ The <productname>PostgreSQL</productname> server will listen for both
+ normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP
+ port, and will negotiate with any connecting client on whether to
+ use <acronym>GSSAPI</acronym> for encryption (and for authentication). By
+ default, this decision is up to the client (which means it can be
+ downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about
+ setting up the server to require the use of <acronym>GSSAPI</acronym> for
+ some or all conections.
+ </para>
+
+ <para>
+ Other than configuration of the negotiation
+ behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond
+ that which necessary for GSSAPI authentication. (For more information on
+ configuring that, see <xref linkend="gssapi-auth"/>.)
+ </para>
+ </sect2>
+ </sect1>
+
<sect1 id="ssh-tunnels">
<title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..47efef0682 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 3014b17a7c..6e4cd66a90 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -173,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 */
@@ -384,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 cannot be combined with non-GSSAPI authentication")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -524,8 +532,13 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ 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
@@ -1044,64 +1057,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
-
-
-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)
{
@@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat, min_stat;
+ 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.
*/
@@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI realm (%s) and configured realm (%s) don't match",
cp, port->hba->krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
}
@@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
pg_krb_caseins_users);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return ret;
}
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 0000000000..78d9f5d325
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+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")));
+}
+
+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 0000000000..c020051d2e
--- /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 0000000000..d860f71b71
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,319 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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>
+
+static ssize_t
+send_buffered_data(Port *port, size_t len)
+{
+ ssize_t ret = secure_raw_write(
+ port,
+ port->gss->writebuf.data + port->gss->writebuf.cursor,
+ port->gss->writebuf.len - port->gss->writebuf.cursor);
+ if (ret < 0)
+ return ret;
+
+ /* update and possibly clear buffer state */
+ port->gss->writebuf.cursor += ret;
+
+ if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+ {
+ /* entire request has now been written */
+ resetStringInfo(&port->gss->writebuf);
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->writebuf.len != 0)
+ return send_buffered_data(port, len);
+
+ /* encrypt the message */
+ output.value = NULL;
+ output.length = 0;
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor);
+ goto cleanup;
+ } else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ goto cleanup;
+ }
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf, output.value, output.length);
+
+ ret = send_buffered_data(port, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* load up any available data */
+ if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len)
+ {
+ /* clamp length */
+ if (len > gss->buf.len - gss->buf.cursor)
+ len = gss->buf.len - gss->buf.cursor;
+
+ memcpy(ptr, gss->buf.data + gss->buf.cursor, len);
+ gss->buf.cursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (gss->buf.cursor == gss->buf.len)
+ resetStringInfo(&gss->buf);
+
+ return ret;
+}
+
+static ssize_t
+load_packetlen(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ ssize_t ret;
+
+ if (gss->buf.len < 4)
+ {
+ enlargeStringInfo(&gss->buf, 4 - gss->buf.len);
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ 4 - gss->buf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(Port *port, size_t len)
+{
+ ssize_t ret;
+ pg_gssinfo *gss = port->gss;
+
+ enlargeStringInfo(&gss->buf, len - gss->buf.len + 4);
+
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ len - gss->buf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->buf.cursor > 0)
+ return read_from_buffer(gss, ptr, len);
+
+ /* load length if not present */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ return ret;
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ return ret;
+
+ /* decrypt the packet */
+ output.value = NULL;
+ output.length = 0;
+ input.value = gss->buf.data + 4;
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* put the decrypted packet in the buffer */
+ resetStringInfo(&gss->buf);
+ enlargeStringInfo(&gss->buf, output.length);
+
+ memcpy(gss->buf.data, output.value, output.length);
+ gss->buf.len = output.length;
+ gss->buf.data[gss->buf.len] = '\0';
+
+ ret = read_from_buffer(gss, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ bool complete_next = false;
+
+ /*
+ * 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)
+ {
+ OM_uint32 major, minor;
+ size_t ret;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+
+ /* Handle any outgoing data */
+ if (gss->writebuf.len != 0)
+ {
+ ret = send_buffered_data(port, 1);
+ if (ret != 1)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ }
+
+ if (complete_next)
+ break;
+
+ /* Load incoming data */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ input.value = gss->buf.data + 4;
+
+ /* 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);
+ resetStringInfo(&gss->buf);
+ 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;
+ }
+
+ if (output.length != 0)
+ {
+ /* Queue packet for writing */
+ uint32 netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf,
+ output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ continue;
+ }
+
+ /* We're done - woohoo! */
+ break;
+ }
+ port->gss->enc = true;
+ return 0;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..7dd1cf7090 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -152,6 +152,14 @@ retry:
n = be_tls_read(port, ptr, len, &waitfor);
}
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);
@@ -255,6 +263,14 @@ retry:
n = be_tls_write(port, ptr, len, &waitfor);
}
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);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..9a1aa4e5cc 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, "hostgss") == 0 ||
+ strcmp(token->string, "hostnogss") == 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') /* "hostgss" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgss 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 = "hostgss 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)
@@ -2060,6 +2088,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)
{
@@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgss";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogss";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 084573e77c..72ce147308 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3562,6 +3562,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 a4b53b33cd..87dbb33a88 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask)
* if we detect a communications failure.)
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes((char *) &len, 4) == EOF)
{
/*
- * EOF after SSLdone probably means the client didn't like our
+ * EOF after secure_done probably means the client didn't like our
* response to NEGOTIATE_SSL_CODE. That's not an error condition, so
* don't clutter the log with a complaint.
*/
- if (!SSLdone)
+ if (!secure_done)
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("incomplete startup packet")));
@@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -1994,6 +1994,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 */
@@ -2413,6 +2439,17 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
#endif
return port;
@@ -2429,7 +2466,15 @@ ConnFree(Port *conn)
secure_close(conn);
#endif
if (conn->gss)
+ {
+#ifdef ENABLE_GSS
+ if (conn->gss->buf.data)
+ pfree(conn->gss->buf.data);
+ if (conn->gss->writebuf.data)
+ pfree(conn->gss->writebuf.data);
+#endif
free(conn->gss);
+ }
free(conn);
}
@@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[])
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
/*
* If appropriate, physically re-attach to shared memory segment. We want
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..830ddaa25a 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 struct HbaLine
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..3e46ac437a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -66,7 +66,7 @@ typedef struct
#include "datatype/timestamp.h"
#include "libpq/hba.h"
#include "libpq/pqcomm.h"
-
+#include "lib/stringinfo.h"
typedef enum CAC_state
{
@@ -86,6 +86,9 @@ typedef struct
gss_cred_id_t cred; /* GSSAPI connection cred's */
gss_ctx_id_t ctx; /* GSSAPI connection context */
gss_name_t name; /* GSSAPI client name */
+ bool enc; /* GSSAPI encryption in use */
+ StringInfoData buf; /* GSSAPI encryption data buffering */
+ StringInfoData writebuf; /* GSSAPI nonblocking write buffering */
#endif
} pg_gssinfo;
#endif
@@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len);
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
+#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
+
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 7bf06c65e9..e34f552ef7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -92,6 +92,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 cc0e0b32c7..ade1190096 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 be2f59239b..4f06f7a2bc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -787,7 +787,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;
/* ----------
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..c814e5e35a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ else
OBJS += sha2.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/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3b2073a47f..af2ef78d76 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,18 +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;
- char *host = PQhost(conn);
-
- if (!(host && host[0] != '\0'))
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("host name must be specified\n"));
- return STATUS_ERROR;
- }
+ int ret;
if (conn->gctx)
{
@@ -215,33 +159,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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..23270f7f6b 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.
@@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssmode similarly to sslmode - we can stil handle "disable" and
+ * "prefer".
+ */
+ {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * validate gssmode option
+ */
+ if (conn->gssmode)
+ {
+ if (strcmp(conn->gssmode, "disable") != 0 &&
+ strcmp(conn->gssmode, "prefer") != 0 &&
+ strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssmode value: \"%s\"\n"),
+ conn->gssmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssmode = strdup(DefaultGSSMode);
+ if (!conn->gssmode)
+ goto oom_error;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn)
conn->wait_ssl_try = true;
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect, with protocol 3.0 as the first attempt.
*/
@@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2426,17 +2479,54 @@ 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 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 (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
+ {
+ 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->gssmode[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 (IS_AF_UNIX(conn->raddr.addr.ss_family))
- {
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
- }
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2629,6 +2719,97 @@ 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->gssmode[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->gssmode[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.
@@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
+#ifdef ENABLE_GSS
+ /*
+ * If gssmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssmode[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
/*
@@ -3361,6 +3560,11 @@ makeEmptyPGconn(void)
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+ initPQExpBuffer(&conn->gbuf);
+ initPQExpBuffer(&conn->gwritebuf);
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->gssmode)
+ free(conn->gssmode);
#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;
+ }
+ termPQExpBuffer(&conn->gbuf);
+ termPQExpBuffer(&conn->gwritebuf);
+#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 0000000000..35e195b7a6
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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 (GSS_ERROR(major))
+ {
+ *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 0000000000..58811df9f1
--- /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 0000000000..a71df69ff2
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * 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
+
+static ssize_t
+send_buffered_data(PGconn *conn, size_t len)
+{
+ ssize_t ret = pqsecure_raw_write(conn,
+ conn->gwritebuf.data + conn->gwritecurs,
+ conn->gwritebuf.len - conn->gwritecurs);
+ if (ret < 0)
+ return ret;
+
+ conn->gwritecurs += ret;
+
+ if (conn->gwritecurs == conn->gwritebuf.len)
+ {
+ /* entire request has now been written */
+ resetPQExpBuffer(&conn->gwritebuf);
+ conn->gwritecurs = 0;
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+
+ if (conn->gwritebuf.len != 0)
+ return send_buffered_data(conn, len);
+
+ /* encrypt the message */
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ goto cleanup;
+ }
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+
+ ret = send_buffered_data(conn, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* check for available data */
+ if (conn->gcursor < conn->gbuf.len)
+ {
+ /* clamp length */
+ if (len > conn->gbuf.len - conn->gcursor)
+ len = conn->gbuf.len - conn->gcursor;
+
+ memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+ conn->gcursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (conn->gcursor == conn->gbuf.len)
+ {
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ }
+
+ return ret;
+}
+
+static ssize_t
+load_packet_length(PGconn *conn)
+{
+ ssize_t ret;
+ if (conn->gbuf.len < 4)
+ {
+ ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "Failed to fit packet length in buffer\n"));
+ return -1;
+ }
+
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ 4 - conn->gbuf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(PGconn *conn, size_t len)
+{
+ ssize_t ret;
+
+ ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI encrypted packet length %ld too big\n"),
+ len);
+ return -1;
+ }
+
+ /* load any missing parts of the packet */
+ if (conn->gbuf.len - 4 < len)
+ {
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ len - conn->gbuf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+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;
+ int conf = 0;
+
+ /* handle any buffered data */
+ if (conn->gcursor != 0)
+ return read_from_buffer(conn, ptr, len);
+
+ /* load in the packet length, if not yet loaded */
+ ret = load_packet_length(conn);
+ if (ret < 0)
+ return ret;
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0)
+ return ret;
+
+ /* decrypt the packet */
+ input.value = conn->gbuf.data + 4;
+
+ major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* load decrypted packet into our buffer */
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI decrypted packet length %ld too big\n"),
+ output.length);
+ ret = -1;
+ goto cleanup;
+ }
+
+ memcpy(conn->gbuf.data, output.value, output.length);
+ conn->gbuf.len = output.length;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+
+ ret = read_from_buffer(conn, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ ssize_t ret;
+ OM_uint32 major, minor;
+ uint32 netlen;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER;
+
+ /* Send out any data we might have */
+ if (conn->gwritebuf.len != 0)
+ {
+ ret = send_buffered_data(conn, 1);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+ else if (ret == 1)
+ /* sent all data */
+ return PGRES_POLLING_READING;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Client sends first, and sending creates a context */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+ ret = load_packet_length(conn);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (conn->gbuf.data[0] == 'E')
+ {
+ /*
+ * We can taken an error here, and it's my least favorite thing.
+ * How long can error messages be? That's a good question, but
+ * backend's pg_gss_error() caps them at 256. Do a single read
+ * for that and call it a day. Cope with this here rather than in
+ * load_packet since expandPQExpBuffer will destroy any data in
+ * the buffer on failure.
+ */
+ load_packet(conn, 256);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ conn->gbuf.data + 1);
+ return PGRES_POLLING_FAILED;
+ }
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ input.value = conn->gbuf.data + 4;
+ }
+
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ 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);
+ resetPQExpBuffer(&conn->gbuf);
+ 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;
+ return PGRES_POLLING_OK;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ return PGRES_POLLING_WRITING;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..18155e4b5e 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, 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);
@@ -287,7 +294,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
* to determine whether to continue/retry after error.
*/
ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
@@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, 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);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..bdd5a10cd8 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..6cf3459a8c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -22,6 +22,7 @@
/* We assume libpq-fe.h has already been included. */
#include "libpq-events.h"
+#include "lib/stringinfo.h"
#include <time.h>
#ifndef WIN32
@@ -473,9 +474,19 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssmode; /* 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 */
+ PQExpBufferData gbuf; /* GSS encryption buffering */
+ size_t gcursor; /* GSS buffering position */
+ PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */
+ size_t gwritecurs; /* GSS write buffer position */
+ 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
@@ -651,7 +662,7 @@ extern void pqsecure_destroy(void);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -745,6 +756,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, 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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 593732fd95..ca5c7cea40 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -183,13 +183,18 @@ sub mkvcbuild
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
- # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
- # if building without OpenSSL
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # be-secure-openssl.c if building without OpenSSL, and
+ # be-gssapi-common.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$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');
+ }
my $snowball = $solution->AddProject('dict_snowball', 'dll', '',
'src/backend/snowball');
@@ -243,9 +248,10 @@ sub mkvcbuild
'src/interfaces/libpq/libpq.rc');
$libpq->AddReference($libpgport);
- # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
- # building with OpenSSL.
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # fe-secure-openssl.c and sha2_openssl.c if building without
+ # OpenSSL, and remove sha2.c if building with OpenSSL. Also
+ # remove fe-gssapi-common.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
@@ -256,6 +262,10 @@ sub mkvcbuild
{
$libpq->RemoveFile('src/common/sha2.c');
}
+ if (!$solution->{options}->{gss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
+ }
my $libpqwalreceiver =
$solution->AddProject('libpqwalreceiver', 'dll', '',
--
2.17.0
--=-=-=--
^ permalink raw reply [nested|flat] 20+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Robbie Harwood @ 2018-05-10 20:12 UTC (permalink / raw)
On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.
Add frontend and backend encryption support functions. Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.
In postmaster, pull GSSAPI authorization checking into a shared
function. Also share the initiator name between the encryption and
non-encryption codepaths.
Modify pqsecure_write() to take a non-const pointer. The pointer will
not be modified, but this change (or a const-discarding cast, or a
malloc()+memcpy()) is necessary for GSSAPI due to const/struct
interactions in C.
For HBA, add "hostgss" and "hostnogss" entries that behave similarly
to their SSL counterparts. "hostgss" requires either "gss", "trust",
or "reject" for its authentication.
Simiarly, add a "gssmode" parameter to libpq. Supported values are
"disable", "require", and "prefer". Notably, negotiation will only be
attempted if credentials can be acquired. Move credential acquisition
into its own function to support this behavior.
Finally, add documentation for everything new, and update existing
documentation on connection security.
Thanks to Michael Paquier for the Windows fixes.
---
doc/src/sgml/client-auth.sgml | 75 ++++--
doc/src/sgml/libpq.sgml | 57 +++-
doc/src/sgml/runtime.sgml | 77 +++++-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 103 +++----
src/backend/libpq/be-gssapi-common.c | 64 +++++
src/backend/libpq/be-gssapi-common.h | 26 ++
src/backend/libpq/be-secure-gssapi.c | 321 ++++++++++++++++++++++
src/backend/libpq/be-secure.c | 16 ++
src/backend/libpq/hba.c | 51 +++-
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/postmaster.c | 64 ++++-
src/include/libpq/hba.h | 4 +-
src/include/libpq/libpq-be.h | 11 +-
src/include/libpq/libpq.h | 3 +
src/include/libpq/pqcomm.h | 5 +-
src/include/pgstat.h | 3 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-auth.c | 90 +------
src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++-
src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++
src/interfaces/libpq/fe-gssapi-common.h | 23 ++
src/interfaces/libpq/fe-secure-gssapi.c | 345 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-fe.h | 3 +-
src/interfaces/libpq/libpq-int.h | 30 ++-
src/tools/msvc/Mkvcbuild.pm | 22 +-
27 files changed, 1578 insertions(+), 202 deletions(-)
create mode 100644 src/backend/libpq/be-gssapi-common.c
create mode 100644 src/backend/libpq/be-gssapi-common.h
create mode 100644 src/backend/libpq/be-secure-gssapi.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.h
create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 656d5f9417..38cf32e3be 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
</synopsis>
The meaning of the fields is as follows:
@@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
This record matches connection attempts made using TCP/IP.
- <literal>host</literal> records match either
+ <literal>host</literal> records match
<acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection
- attempts.
+ attempts as well as <acronym>GSSAPI</acronym> or
+ non-<acronym>GSSAPI</acronym> connection attempts.
</para>
<note>
<para>
@@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>hostgss</literal></term>
+ <listitem>
+ <para>
+ This record matches connection attempts made using TCP/IP,
+ but only when the connection is made with <acronym>GSSAPI</acronym>
+ encryption.
+ </para>
+
+ <para>
+ To make use of this option the server must be built with
+ <acronym>GSSAPI</acronym> support. Otherwise,
+ the <literal>hostgss</literal> record is ignored except for logging a
+ warning that it cannot match any connections.
+ </para>
+
+ <para>
+ Note that the only supported <xref linkend="auth-methods"/> for use
+ with <acronym>GSSAPI</acronym> encryption
+ are <literal>gss</literal>, <literal>reject</literal>,
+ and <literal>trust</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>hostnogss</literal></term>
+ <listitem>
+ <para>
+ This record type has the opposite behavior of <literal>hostgss</literal>;
+ it only matches connection attempts made over
+ TCP/IP that do not use <acronym>GSSAPI</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>database</replaceable></term>
<listitem>
@@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
Use GSSAPI to authenticate the user. This is only
- available for TCP/IP connections. See <xref
- linkend="gssapi-auth"/> for details.
+ available for TCP/IP connections . See <xref
+ linkend="gssapi-auth"/> for details. It can be used in conjunction
+ with GSSAPI encryption.
</para>
</listitem>
</varlistentry>
@@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256
host all mike .example.com md5
host all all .example.com scram-sha-256
-# In the absence of preceding "host" lines, these two lines will
+# In the absence of preceding "host" lines, these three lines will
# reject all connections from 192.168.54.1 (since that entry will be
-# matched first), but allow GSSAPI connections from anywhere else
-# on the Internet. The zero mask causes no bits of the host IP
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet. Unencrypted GSSAPI connections are allowed from
+# 192.168.12.10 only. The zero mask causes no bits of the host IP
# address to be considered, so it matches any host.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 192.168.54.1/32 reject
-host all all 0.0.0.0/0 gss
+hostgss all all 0.0.0.0/0 gss
+host all all 192.168.12.10/32 gss
# Allow users from 192.168.x.x hosts to connect to any database, if
# they pass the ident check. If, for example, ident says the user is
@@ -1051,13 +1093,16 @@ omicron bryanh guest1
<para>
<productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in RFC 2743.
- <productname>PostgreSQL</productname> supports
- <productname>GSSAPI</productname> with <productname>Kerberos</productname>
- authentication according to RFC 1964. <productname>GSSAPI</productname>
- provides automatic authentication (single sign-on) for systems
- that support it. The authentication itself is secure, but the
- data sent over the database connection will be sent unencrypted unless
- <acronym>SSL</acronym> is used.
+
+ <productname>PostgreSQL</productname>
+ supports <productname>GSSAPI</productname> for use as either an encrypted,
+ authenticated layer, or as authentication
+ only. <productname>GSSAPI</productname> provides automatic authentication
+ (single sign-on) for systems that support it. The authentication itself is
+ secure. If <productname>GSSAPI</productname> encryption
+ (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are
+ used, the data sent along the database connection will be encrypted;
+ otherwise, it will not.
</para>
<para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 800e68a19e..73d233dc16 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1320,6 +1320,61 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode">
+ <term><literal>gssmode</literal></term>
+ <listitem>
+ <para>
+ This option determines whether or with what priority a secure
+ <acronym>GSS</acronym> TCP/IP connection will be negotiated with the
+ server. There are three modes:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>disable</literal></term>
+ <listitem>
+ <para>
+ only try a non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>prefer</literal> (default)</term>
+ <listitem>
+ <para>
+ if there are <acronym>GSSAPI</acronym> credentials present (i.e.,
+ in a credentials cache), first try
+ a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or
+ there are no credentials, try a
+ non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>require</literal></term>
+ <listitem>
+ <para>
+ only try a <acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ <literal>gssmode</literal> is ignored for Unix domain socket
+ communication. If <productname>PostgreSQL</productname> is compiled
+ without GSSAPI support, using the <literal>require</literal> option
+ will cause an error, while <literal>prefer</literal> will be accepted
+ but <application>libpq</application> will not actually attempt
+ a <acronym>GSSAPI</acronym>-encrypted
+ connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with
+ libpq</secondary></indexterm>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
<term><literal>sslmode</literal></term>
<listitem>
@@ -7872,7 +7927,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- For a connection to be known secure, SSL usage must be configured
+ For a connection to be known SSL-secured, SSL usage must be configured
on <emphasis>both the client and the server</emphasis> before the connection
is made. If it is only configured on the server, the client may end up
sending sensitive information (e.g. passwords) before
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 330e38a29e..06bf56153c 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To prevent spoofing on TCP connections, the best solution is to use
- SSL certificates and make sure that clients check the server's certificate.
- To do that, the server
+ To prevent spoofing on TCP connections, either use
+ SSL certificates and make sure that clients check the server's certificate,
+ or use GSSAPI encryption (or both, if they're on separate connections).
+ </para>
+
+ <para>
+ To prevent spoofing with SSL, the server
must be configured to accept only <literal>hostssl</literal> connections (<xref
linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files
(<xref linkend="ssl-tcp"/>). The TCP client must connect using
@@ -2018,6 +2022,14 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<literal>verify-full</literal> and have the appropriate root certificate
file installed (<xref linkend="libq-ssl-certificates"/>).
</para>
+
+ <para>
+ To prevent spoofing with GSSAPI, the server must be configured to accept
+ only <literal>hostgss</literal> connections
+ (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal>
+ authentication with them. The TCP client must connect
+ using <literal>gssmode=require</literal>.
+ </para>
</sect1>
<sect1 id="encryption-options">
@@ -2114,8 +2126,24 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
which hosts can use non-encrypted connections (<literal>host</literal>)
and which require SSL-encrypted connections
(<literal>hostssl</literal>). Also, clients can specify that they
- connect to servers only via SSL. <application>Stunnel</application> or
- <application>SSH</application> can also be used to encrypt transmissions.
+ connect to servers only via SSL.
+ </para>
+
+ <para>
+ GSSAPI-encrypted connections encrypt all data sent across the network,
+ including queries and data returned. (No password is sent across the
+ network.) The <filename>pg_hba.conf</filename> file allows
+ administrators to specify which hosts can use non-encrypted connections
+ (<literal>host</literal>) and which require GSSAPI-encrypted connections
+ (<literal>hostgss</literal>). Also, clients can specify that they
+ connect to servers only on GSSAPI-encrypted connections
+ (<literal>gssmode=require</literal>).
+ </para>
+
+ <para>
+ <application>Stunnel</application> or
+ <application>SSH</application> can also be used to encrypt
+ transmissions.
</para>
</listitem>
</varlistentry>
@@ -2505,6 +2533,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</sect1>
+ <sect1 id="gssapi-enc">
+ <title>Secure TCP/IP Connections with GSSAPI encryption</title>
+
+ <indexterm zone="gssapi-enc">
+ <primary>gssapi</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> also has native support for
+ using <acronym>GSSAPI</acronym> to encrypt client/server communications for
+ increased security. Support requires that a <acronym>GSSAPI</acronym>
+ implementation (such as MIT krb5) is installed on both client and server
+ systems, and that support in <productname>PostgreSQL</productname> is
+ enabled at build time (see <xref linkend="installation"/>).
+ </para>
+
+ <sect2 id="gssapi-setup">
+ <title>Basic Setup</title>
+
+ <para>
+ The <productname>PostgreSQL</productname> server will listen for both
+ normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP
+ port, and will negotiate with any connecting client on whether to
+ use <acronym>GSSAPI</acronym> for encryption (and for authentication). By
+ default, this decision is up to the client (which means it can be
+ downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about
+ setting up the server to require the use of <acronym>GSSAPI</acronym> for
+ some or all conections.
+ </para>
+
+ <para>
+ Other than configuration of the negotiation
+ behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond
+ that which necessary for GSSAPI authentication. (For more information on
+ configuring that, see <xref linkend="gssapi-auth"/>.)
+ </para>
+ </sect2>
+ </sect1>
+
<sect1 id="ssh-tunnels">
<title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..47efef0682 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 3014b17a7c..6e4cd66a90 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -173,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 */
@@ -384,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 cannot be combined with non-GSSAPI authentication")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -524,8 +532,13 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ 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
@@ -1044,64 +1057,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
-
-
-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)
{
@@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat, min_stat;
+ 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.
*/
@@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI realm (%s) and configured realm (%s) don't match",
cp, port->hba->krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
}
@@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
pg_krb_caseins_users);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return ret;
}
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 0000000000..78d9f5d325
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+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")));
+}
+
+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 0000000000..c020051d2e
--- /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 0000000000..2b70db2232
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,321 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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>
+
+static ssize_t
+send_buffered_data(Port *port, size_t len)
+{
+ ssize_t ret = secure_raw_write(
+ port,
+ port->gss->writebuf.data + port->gss->writebuf.cursor,
+ port->gss->writebuf.len - port->gss->writebuf.cursor);
+ if (ret < 0)
+ return ret;
+
+ /* update and possibly clear buffer state */
+ port->gss->writebuf.cursor += ret;
+
+ if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+ {
+ /* entire request has now been written */
+ resetStringInfo(&port->gss->writebuf);
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->writebuf.len != 0)
+ return send_buffered_data(port, len);
+
+ /* encrypt the message */
+ output.value = NULL;
+ output.length = 0;
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (major != GSS_S_COMPLETE)
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor);
+ goto cleanup;
+ } else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ goto cleanup;
+ }
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf, output.value, output.length);
+
+ ret = send_buffered_data(port, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* load up any available data */
+ if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len)
+ {
+ /* clamp length */
+ if (len > gss->buf.len - gss->buf.cursor)
+ len = gss->buf.len - gss->buf.cursor;
+
+ memcpy(ptr, gss->buf.data + gss->buf.cursor, len);
+ gss->buf.cursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (gss->buf.cursor == gss->buf.len)
+ resetStringInfo(&gss->buf);
+
+ return ret;
+}
+
+static ssize_t
+load_packetlen(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ ssize_t ret;
+
+ if (gss->buf.len < 4)
+ {
+ enlargeStringInfo(&gss->buf, 4 - gss->buf.len);
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ 4 - gss->buf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len < 4)
+ {
+ /* partial read from secure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(Port *port, size_t len)
+{
+ ssize_t ret;
+ pg_gssinfo *gss = port->gss;
+
+ enlargeStringInfo(&gss->buf, len - gss->buf.len + 4);
+
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ len - gss->buf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len - 4 < len)
+ {
+ /* partial read from secure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->buf.cursor > 0)
+ return read_from_buffer(gss, ptr, len);
+
+ /* load length if not present */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ return ret;
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ return ret;
+
+ /* decrypt the packet */
+ output.value = NULL;
+ output.length = 0;
+ input.value = gss->buf.data + 4;
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (major != GSS_S_COMPLETE)
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* put the decrypted packet in the buffer */
+ resetStringInfo(&gss->buf);
+ enlargeStringInfo(&gss->buf, output.length);
+
+ memcpy(gss->buf.data, output.value, output.length);
+ gss->buf.len = output.length;
+ gss->buf.data[gss->buf.len] = '\0';
+
+ ret = read_from_buffer(gss, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ bool complete_next = false;
+
+ /*
+ * 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)
+ {
+ OM_uint32 major, minor;
+ size_t ret;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+
+ /* Handle any outgoing data */
+ if (gss->writebuf.len != 0)
+ {
+ ret = send_buffered_data(port, 1);
+ if (ret != 1)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ }
+
+ if (complete_next)
+ break;
+
+ /* Load incoming data */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ input.value = gss->buf.data + 4;
+
+ /* 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);
+ resetStringInfo(&gss->buf);
+ 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;
+ }
+
+ if (output.length != 0)
+ {
+ /* Queue packet for writing */
+ uint32 netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf,
+ output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ continue;
+ }
+
+ /* We're done - woohoo! */
+ break;
+ }
+ port->gss->enc = true;
+ return 0;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..7dd1cf7090 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -152,6 +152,14 @@ retry:
n = be_tls_read(port, ptr, len, &waitfor);
}
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);
@@ -255,6 +263,14 @@ retry:
n = be_tls_write(port, ptr, len, &waitfor);
}
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);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..9a1aa4e5cc 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, "hostgss") == 0 ||
+ strcmp(token->string, "hostnogss") == 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') /* "hostgss" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgss 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 = "hostgss 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)
@@ -2060,6 +2088,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)
{
@@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgss";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogss";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 084573e77c..72ce147308 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3562,6 +3562,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 a4b53b33cd..87dbb33a88 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask)
* if we detect a communications failure.)
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes((char *) &len, 4) == EOF)
{
/*
- * EOF after SSLdone probably means the client didn't like our
+ * EOF after secure_done probably means the client didn't like our
* response to NEGOTIATE_SSL_CODE. That's not an error condition, so
* don't clutter the log with a complaint.
*/
- if (!SSLdone)
+ if (!secure_done)
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("incomplete startup packet")));
@@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -1994,6 +1994,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 */
@@ -2413,6 +2439,17 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
#endif
return port;
@@ -2429,7 +2466,15 @@ ConnFree(Port *conn)
secure_close(conn);
#endif
if (conn->gss)
+ {
+#ifdef ENABLE_GSS
+ if (conn->gss->buf.data)
+ pfree(conn->gss->buf.data);
+ if (conn->gss->writebuf.data)
+ pfree(conn->gss->writebuf.data);
+#endif
free(conn->gss);
+ }
free(conn);
}
@@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[])
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
/*
* If appropriate, physically re-attach to shared memory segment. We want
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..830ddaa25a 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 struct HbaLine
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..3e46ac437a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -66,7 +66,7 @@ typedef struct
#include "datatype/timestamp.h"
#include "libpq/hba.h"
#include "libpq/pqcomm.h"
-
+#include "lib/stringinfo.h"
typedef enum CAC_state
{
@@ -86,6 +86,9 @@ typedef struct
gss_cred_id_t cred; /* GSSAPI connection cred's */
gss_ctx_id_t ctx; /* GSSAPI connection context */
gss_name_t name; /* GSSAPI client name */
+ bool enc; /* GSSAPI encryption in use */
+ StringInfoData buf; /* GSSAPI encryption data buffering */
+ StringInfoData writebuf; /* GSSAPI nonblocking write buffering */
#endif
} pg_gssinfo;
#endif
@@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len);
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
+#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
+
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 7bf06c65e9..e34f552ef7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -92,6 +92,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 cc0e0b32c7..ade1190096 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 be2f59239b..4f06f7a2bc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -787,7 +787,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;
/* ----------
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..c814e5e35a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ else
OBJS += sha2.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/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3b2073a47f..af2ef78d76 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,18 +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;
- char *host = PQhost(conn);
-
- if (!(host && host[0] != '\0'))
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("host name must be specified\n"));
- return STATUS_ERROR;
- }
+ int ret;
if (conn->gctx)
{
@@ -215,33 +159,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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..2bd1be1b97 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.
@@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssmode similarly to sslmode - we can stil handle "disable" and
+ * "prefer".
+ */
+ {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * validate gssmode option
+ */
+ if (conn->gssmode)
+ {
+ if (strcmp(conn->gssmode, "disable") != 0 &&
+ strcmp(conn->gssmode, "prefer") != 0 &&
+ strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssmode value: \"%s\"\n"),
+ conn->gssmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssmode, "require") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssmode = strdup(DefaultGSSMode);
+ if (!conn->gssmode)
+ goto oom_error;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn)
conn->wait_ssl_try = true;
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect, with protocol 3.0 as the first attempt.
*/
@@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2426,17 +2479,54 @@ 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 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 (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
+ {
+ 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->gssmode[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 (IS_AF_UNIX(conn->raddr.addr.ss_family))
- {
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
- }
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2629,6 +2719,97 @@ 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->gssmode[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->gssmode[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.
@@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
+#ifdef ENABLE_GSS
+ /*
+ * If gssmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssmode[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
/*
@@ -3361,6 +3560,11 @@ makeEmptyPGconn(void)
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+ initPQExpBuffer(&conn->gbuf);
+ initPQExpBuffer(&conn->gwritebuf);
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->gssmode)
+ free(conn->gssmode);
#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;
+ }
+ termPQExpBuffer(&conn->gbuf);
+ termPQExpBuffer(&conn->gwritebuf);
+#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 0000000000..ce71b11aea
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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 0000000000..58811df9f1
--- /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 0000000000..427651ec33
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,345 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * 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
+
+static ssize_t
+send_buffered_data(PGconn *conn, size_t len)
+{
+ ssize_t ret = pqsecure_raw_write(conn,
+ conn->gwritebuf.data + conn->gwritecurs,
+ conn->gwritebuf.len - conn->gwritecurs);
+ if (ret < 0)
+ return ret;
+
+ conn->gwritecurs += ret;
+
+ if (conn->gwritecurs == conn->gwritebuf.len)
+ {
+ /* entire request has now been written */
+ resetPQExpBuffer(&conn->gwritebuf);
+ conn->gwritecurs = 0;
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+
+ if (conn->gwritebuf.len != 0)
+ return send_buffered_data(conn, len);
+
+ /* encrypt the message */
+ input.value = ptr;
+ input.length = len;
+
+ 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;
+ }
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+
+ ret = send_buffered_data(conn, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* check for available data */
+ if (conn->gcursor < conn->gbuf.len)
+ {
+ /* clamp length */
+ if (len > conn->gbuf.len - conn->gcursor)
+ len = conn->gbuf.len - conn->gcursor;
+
+ memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+ conn->gcursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (conn->gcursor == conn->gbuf.len)
+ {
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ }
+
+ return ret;
+}
+
+static ssize_t
+load_packet_length(PGconn *conn)
+{
+ ssize_t ret;
+ if (conn->gbuf.len < 4)
+ {
+ ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "Failed to fit packet length in buffer\n"));
+ return -1;
+ }
+
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ 4 - conn->gbuf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len < 4)
+ {
+ /* partial read from pqsecure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(PGconn *conn, size_t len)
+{
+ ssize_t ret;
+
+ ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI encrypted packet length %ld too big\n"),
+ len);
+ return -1;
+ }
+
+ /* load any missing parts of the packet */
+ if (conn->gbuf.len - 4 < len)
+ {
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ len - conn->gbuf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len - 4 < len)
+ {
+ /* partial read from pqsecure_raw_read() */
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+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;
+ int conf = 0;
+
+ /* handle any buffered data */
+ if (conn->gcursor != 0)
+ return read_from_buffer(conn, ptr, len);
+
+ /* load in the packet length, if not yet loaded */
+ ret = load_packet_length(conn);
+ if (ret < 0)
+ return ret;
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0)
+ return ret;
+
+ /* decrypt the packet */
+ input.value = conn->gbuf.data + 4;
+
+ 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;
+ }
+
+ /* load decrypted packet into our buffer */
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI decrypted packet length %ld too big\n"),
+ output.length);
+ ret = -1;
+ goto cleanup;
+ }
+
+ memcpy(conn->gbuf.data, output.value, output.length);
+ conn->gbuf.len = output.length;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+
+ ret = read_from_buffer(conn, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ ssize_t ret;
+ OM_uint32 major, minor;
+ uint32 netlen;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER;
+
+ /* Send out any data we might have */
+ if (conn->gwritebuf.len != 0)
+ {
+ ret = send_buffered_data(conn, 1);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+ else if (ret == 1)
+ /* sent all data */
+ return PGRES_POLLING_READING;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Client sends first, and sending creates a context */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+ ret = load_packet_length(conn);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (conn->gbuf.data[0] == 'E')
+ {
+ /*
+ * We can taken an error here, and it's my least favorite thing.
+ * How long can error messages be? That's a good question, but
+ * backend's pg_gss_error() caps them at 256. Do a single read
+ * for that and call it a day. Cope with this here rather than in
+ * load_packet since expandPQExpBuffer will destroy any data in
+ * the buffer on failure.
+ */
+ load_packet(conn, 256);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ conn->gbuf.data + 1);
+ return PGRES_POLLING_FAILED;
+ }
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ input.value = conn->gbuf.data + 4;
+ }
+
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ 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);
+ resetPQExpBuffer(&conn->gbuf);
+ 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;
+ return PGRES_POLLING_OK;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ return PGRES_POLLING_WRITING;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..18155e4b5e 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, 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);
@@ -287,7 +294,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
* to determine whether to continue/retry after error.
*/
ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
@@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, 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);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..bdd5a10cd8 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..6cf3459a8c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -22,6 +22,7 @@
/* We assume libpq-fe.h has already been included. */
#include "libpq-events.h"
+#include "lib/stringinfo.h"
#include <time.h>
#ifndef WIN32
@@ -473,9 +474,19 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssmode; /* 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 */
+ PQExpBufferData gbuf; /* GSS encryption buffering */
+ size_t gcursor; /* GSS buffering position */
+ PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */
+ size_t gwritecurs; /* GSS write buffer position */
+ 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
@@ -651,7 +662,7 @@ extern void pqsecure_destroy(void);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -745,6 +756,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, 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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 593732fd95..e152d7056b 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -183,13 +183,19 @@ sub mkvcbuild
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
- # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
- # if building without OpenSSL
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # be-secure-openssl.c if building without OpenSSL, and
+ # be-gssapi-common.c and be-secure-gssapi.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$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');
@@ -243,9 +249,10 @@ sub mkvcbuild
'src/interfaces/libpq/libpq.rc');
$libpq->AddReference($libpgport);
- # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
- # building with OpenSSL.
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # fe-secure-openssl.c and sha2_openssl.c if building without OpenSSL, and
+ # remove sha2.c if building with OpenSSL. Also remove
+ # fe-gssapi-common.c and fe-secure-gssapi.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
@@ -256,6 +263,11 @@ sub mkvcbuild
{
$libpq->RemoveFile('src/common/sha2.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', '',
--
2.17.1
--=-=-=--
^ permalink raw reply [nested|flat] 20+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Robbie Harwood @ 2018-05-10 20:12 UTC (permalink / raw)
On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.
Add frontend and backend encryption support functions. Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.
In postmaster, pull GSSAPI authorization checking into a shared
function. Also share the initiator name between the encryption and
non-encryption codepaths.
Modify pqsecure_write() to take a non-const pointer. The pointer will
not be modified, but this change (or a const-discarding cast, or a
malloc()+memcpy()) is necessary for GSSAPI due to const/struct
interactions in C.
For HBA, add "hostgss" and "hostnogss" entries that behave similarly
to their SSL counterparts. "hostgss" requires either "gss", "trust",
or "reject" for its authentication.
Simiarly, add a "gssmode" parameter to libpq. Supported values are
"disable", "require", and "prefer". Notably, negotiation will only be
attempted if credentials can be acquired. Move credential acquisition
into its own function to support this behavior.
Finally, add documentation for everything new, and update existing
documentation on connection security.
Thanks to Michael Paquier for the Windows fixes.
---
doc/src/sgml/client-auth.sgml | 75 ++++--
doc/src/sgml/libpq.sgml | 60 ++++-
doc/src/sgml/runtime.sgml | 80 +++++-
src/backend/libpq/Makefile | 4 +
src/backend/libpq/auth.c | 103 +++----
src/backend/libpq/be-gssapi-common.c | 64 +++++
src/backend/libpq/be-gssapi-common.h | 26 ++
src/backend/libpq/be-secure-gssapi.c | 319 ++++++++++++++++++++++
src/backend/libpq/be-secure.c | 16 ++
src/backend/libpq/hba.c | 51 +++-
src/backend/postmaster/pgstat.c | 3 +
src/backend/postmaster/postmaster.c | 64 ++++-
src/include/libpq/hba.h | 4 +-
src/include/libpq/libpq-be.h | 11 +-
src/include/libpq/libpq.h | 3 +
src/include/libpq/pqcomm.h | 5 +-
src/include/pgstat.h | 3 +-
src/interfaces/libpq/Makefile | 4 +
src/interfaces/libpq/fe-auth.c | 90 +------
src/interfaces/libpq/fe-connect.c | 232 +++++++++++++++-
src/interfaces/libpq/fe-gssapi-common.c | 128 +++++++++
src/interfaces/libpq/fe-gssapi-common.h | 23 ++
src/interfaces/libpq/fe-secure-gssapi.c | 343 ++++++++++++++++++++++++
src/interfaces/libpq/fe-secure.c | 16 +-
src/interfaces/libpq/libpq-fe.h | 3 +-
src/interfaces/libpq/libpq-int.h | 30 ++-
src/tools/msvc/Mkvcbuild.pm | 20 +-
27 files changed, 1578 insertions(+), 202 deletions(-)
create mode 100644 src/backend/libpq/be-gssapi-common.c
create mode 100644 src/backend/libpq/be-gssapi-common.h
create mode 100644 src/backend/libpq/be-secure-gssapi.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.c
create mode 100644 src/interfaces/libpq/fe-gssapi-common.h
create mode 100644 src/interfaces/libpq/fe-secure-gssapi.c
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 656d5f9417..38cf32e3be 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -108,6 +108,8 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
host <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostssl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostgss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
+hostnogss <replaceable>database</replaceable> <replaceable>user</replaceable> <replaceable>IP-address</replaceable> <replaceable>IP-mask</replaceable> <replaceable>auth-method</replaceable> <optional><replaceable>auth-options</replaceable></optional>
</synopsis>
The meaning of the fields is as follows:
@@ -128,9 +130,10 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
This record matches connection attempts made using TCP/IP.
- <literal>host</literal> records match either
+ <literal>host</literal> records match
<acronym>SSL</acronym> or non-<acronym>SSL</acronym> connection
- attempts.
+ attempts as well as <acronym>GSSAPI</acronym> or
+ non-<acronym>GSSAPI</acronym> connection attempts.
</para>
<note>
<para>
@@ -176,6 +179,42 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>hostgss</literal></term>
+ <listitem>
+ <para>
+ This record matches connection attempts made using TCP/IP,
+ but only when the connection is made with <acronym>GSSAPI</acronym>
+ encryption.
+ </para>
+
+ <para>
+ To make use of this option the server must be built with
+ <acronym>GSSAPI</acronym> support. Otherwise,
+ the <literal>hostgss</literal> record is ignored except for logging a
+ warning that it cannot match any connections.
+ </para>
+
+ <para>
+ Note that the only supported <xref linkend="auth-methods"/> for use
+ with <acronym>GSSAPI</acronym> encryption
+ are <literal>gss</literal>, <literal>reject</literal>,
+ and <literal>trust</literal>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>hostnogss</literal></term>
+ <listitem>
+ <para>
+ This record type has the opposite behavior of <literal>hostgss</literal>;
+ it only matches connection attempts made over
+ TCP/IP that do not use <acronym>GSSAPI</acronym>.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable>database</replaceable></term>
<listitem>
@@ -450,8 +489,9 @@ hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable>
<listitem>
<para>
Use GSSAPI to authenticate the user. This is only
- available for TCP/IP connections. See <xref
- linkend="gssapi-auth"/> for details.
+ available for TCP/IP connections . See <xref
+ linkend="gssapi-auth"/> for details. It can be used in conjunction
+ with GSSAPI encryption.
</para>
</listitem>
</varlistentry>
@@ -696,15 +736,17 @@ host postgres all 192.168.12.10/32 scram-sha-256
host all mike .example.com md5
host all all .example.com scram-sha-256
-# In the absence of preceding "host" lines, these two lines will
+# In the absence of preceding "host" lines, these three lines will
# reject all connections from 192.168.54.1 (since that entry will be
-# matched first), but allow GSSAPI connections from anywhere else
-# on the Internet. The zero mask causes no bits of the host IP
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet. Unencrypted GSSAPI connections are allowed from
+# 192.168.12.10 only. The zero mask causes no bits of the host IP
# address to be considered, so it matches any host.
#
# TYPE DATABASE USER ADDRESS METHOD
host all all 192.168.54.1/32 reject
-host all all 0.0.0.0/0 gss
+hostgss all all 0.0.0.0/0 gss
+host all all 192.168.12.10/32 gss
# Allow users from 192.168.x.x hosts to connect to any database, if
# they pass the ident check. If, for example, ident says the user is
@@ -1051,13 +1093,16 @@ omicron bryanh guest1
<para>
<productname>GSSAPI</productname> is an industry-standard protocol
for secure authentication defined in RFC 2743.
- <productname>PostgreSQL</productname> supports
- <productname>GSSAPI</productname> with <productname>Kerberos</productname>
- authentication according to RFC 1964. <productname>GSSAPI</productname>
- provides automatic authentication (single sign-on) for systems
- that support it. The authentication itself is secure, but the
- data sent over the database connection will be sent unencrypted unless
- <acronym>SSL</acronym> is used.
+
+ <productname>PostgreSQL</productname>
+ supports <productname>GSSAPI</productname> for use as either an encrypted,
+ authenticated layer, or as authentication
+ only. <productname>GSSAPI</productname> provides automatic authentication
+ (single sign-on) for systems that support it. The authentication itself is
+ secure. If <productname>GSSAPI</productname> encryption
+ (see <literal>hostgss</literal>) or <acronym>SSL</acronym> encryption are
+ used, the data sent along the database connection will be encrypted;
+ otherwise, it will not.
</para>
<para>
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 800e68a19e..24bf6d3f44 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1320,6 +1320,64 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-gssmode" xreflabel="gssmode">
+ <term><literal>gssmode</literal></term>
+ <listitem>
+ <para>
+ This option determines whether or with what priority a secure
+ <acronym>GSS</acronym> TCP/IP connection will be negotiated with the
+ server. There are three modes:
+
+ <variablelist>
+ <varlistentry>
+ <term><literal>disable</literal></term>
+ <listitem>
+ <para>
+ only try a non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>prefer</literal> (default)</term>
+ <listitem>
+ <para>
+ if there are <acronym>GSSAPI</acronym> credentials present (i.e.,
+ in a credentials cache), first try
+ a <acronym>GSSAPI</acronym>-encrypted connection; if that fails or
+ there are no credentials, try a
+ non-<acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>require</literal></term>
+ <listitem>
+ <para>
+ only try a <acronym>GSSAPI</acronym>-encrypted connection
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ See <xref linkend="libpq-gss"/> for a detailed description of how
+ these options work.
+ </para>
+
+ <para>
+ <literal>gssmode</literal> is ignored for Unix domain socket
+ communication. If <productname>PostgreSQL</productname> is compiled
+ without GSSAPI support, using the <literal>require</literal> option
+ will cause an error, while <literal>prefer</literal> will be accepted
+ but <application>libpq</application> will not actually attempt
+ a <acronym>GSSAPI</acronym>-encrypted
+ connection.<indexterm><primary>GSSAPI</primary><secondary sortas="libpq">with
+ libpq</secondary></indexterm>
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-sslmode" xreflabel="sslmode">
<term><literal>sslmode</literal></term>
<listitem>
@@ -7872,7 +7930,7 @@ ldap://ldap.acme.com/cn=dbserver,cn=hosts?pgconnectinfo?base?(objectclass=*)
</para>
<para>
- For a connection to be known secure, SSL usage must be configured
+ For a connection to be known SSL-secured, SSL usage must be configured
on <emphasis>both the client and the server</emphasis> before the connection
is made. If it is only configured on the server, the client may end up
sending sensitive information (e.g. passwords) before
diff --git a/doc/src/sgml/runtime.sgml b/doc/src/sgml/runtime.sgml
index 330e38a29e..4e26644b37 100644
--- a/doc/src/sgml/runtime.sgml
+++ b/doc/src/sgml/runtime.sgml
@@ -2008,9 +2008,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
</para>
<para>
- To prevent spoofing on TCP connections, the best solution is to use
- SSL certificates and make sure that clients check the server's certificate.
- To do that, the server
+ To prevent spoofing on TCP connections, either use
+ SSL certificates and make sure that clients check the server's certificate,
+ or use GSSAPI encryption (or both, if they're on separate connections).
+ </para>
+
+ <para>
+ To prevent spoofing with SSL, the server
must be configured to accept only <literal>hostssl</literal> connections (<xref
linkend="auth-pg-hba-conf"/>) and have SSL key and certificate files
(<xref linkend="ssl-tcp"/>). The TCP client must connect using
@@ -2018,6 +2022,13 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
<literal>verify-full</literal> and have the appropriate root certificate
file installed (<xref linkend="libq-ssl-certificates"/>).
</para>
+
+ <para>
+ To prevent spoofing with GSSAPI, the server must be configured to accept
+ only <literal>hostgss</literal> connections
+ (<xref linkend="auth-pg-hba.conf"/>) and use <literal>gss</literal>
+ authentication with them. The TCP client must connect
+ using <literal>gssmode=require</literal>.
</sect1>
<sect1 id="encryption-options">
@@ -2114,8 +2125,28 @@ pg_dumpall -p 5432 | psql -d postgres -p 5433
which hosts can use non-encrypted connections (<literal>host</literal>)
and which require SSL-encrypted connections
(<literal>hostssl</literal>). Also, clients can specify that they
- connect to servers only via SSL. <application>Stunnel</application> or
- <application>SSH</application> can also be used to encrypt transmissions.
+ connect to servers only via SSL.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ GSSAPI-encrypted connections encrypt all data sent across the network,
+ including queries and data returned. (No password is sent across the
+ network.) The <filename>pg_hba.conf</filename> file allows
+ administrators to specify which hosts can use non-encrypted connections
+ (<literal>host</literal>) and which require GSSAPI-encrypted connections
+ (<literal>hostgss</literal>). Also, clients can specify that they
+ connect to servers only on GSSAPI-encrypted connections
+ (<literal>gssmode=require</literal>).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <application>Stunnel</application> or
+ <application>SSH</application> can also be used to encrypt
+ transmissions.
</para>
</listitem>
</varlistentry>
@@ -2505,6 +2536,45 @@ openssl x509 -req -in server.csr -text -days 365 \
</sect1>
+ <sect1 id="gssapi-enc">
+ <title>Secure TCP/IP Connections with GSSAPI encryption</title>
+
+ <indexterm zone="gssapi-enc">
+ <primary>gssapi</primary>
+ </indexterm>
+
+ <para>
+ <productname>PostgreSQL</productname> also has native support for
+ using <acronym>GSSAPI</acronym> to encrypt client/server communications for
+ increased security. Support requires that a <acronym>GSSAPI</acronym>
+ implementation (such as MIT krb5) is installed on both client and server
+ systems, and that support in <productname>PostgreSQL</productname> is
+ enabled at build time (see <xref linked="installation"/>).
+ </para>
+
+ <sect2 id="gssapi-setup">
+ <title>Basic Setup</title>
+
+ <para>
+ The <productname>PostgreSQL</productname> server will listen for both
+ normal and <acronym>GSSAPI</acronym>-encrypted connections on the same TCP
+ port, and will negotiate with any connecting client on whether to
+ use <acronym>GSSAPI</acronym> for encryption (and for authentication). By
+ default, this decision is up to the client (which means it can be
+ downgraded by an attacker); see <xref linkend="auth-pg-hba-conf"/> about
+ setting up the server to require the use of <acronym>GSSAPI</acronym> for
+ some or all conections.
+ </para>
+
+ <para>
+ Other than configuration of the negotiation
+ behavior, <acronym>GSSAPI</acronym> encryption requires no setup beyond
+ that which necessary for GSSAPI authentication. (For more information on
+ configuring that, see <xref linkend="gssapi-auth"/>.)
+ </para>
+ </sect2>
+ </sect1>
+
<sect1 id="ssh-tunnels">
<title>Secure TCP/IP Connections with <application>SSH</application> Tunnels</title>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 3dbec23e30..47efef0682 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 3014b17a7c..6e4cd66a90 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -173,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 */
@@ -384,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 cannot be combined with non-GSSAPI authentication")));
+ }
+#endif
+
/*
* Now proceed to do the actual authentication check
*/
@@ -524,8 +532,13 @@ ClientAuthentication(Port *port)
case uaGSS:
#ifdef ENABLE_GSS
- sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
- status = pg_GSS_recvauth(port);
+ 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
@@ -1044,64 +1057,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
-
-
-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)
{
@@ -1110,7 +1065,6 @@ pg_GSS_recvauth(Port *port)
lmin_s,
gflags;
int mtype;
- int ret;
StringInfoData buf;
gss_buffer_desc gbuf;
@@ -1263,10 +1217,17 @@ pg_GSS_recvauth(Port *port)
*/
gss_release_cred(&min_stat, &port->gss->cred);
}
+ return pg_GSS_checkauth(port);
+}
+
+static int
+pg_GSS_checkauth(Port *port)
+{
+ int ret;
+ OM_uint32 maj_stat, min_stat;
+ 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.
*/
@@ -1308,7 +1269,7 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI realm (%s) and configured realm (%s) don't match",
cp, port->hba->krb_realm);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
}
@@ -1318,14 +1279,14 @@ pg_GSS_recvauth(Port *port)
elog(DEBUG2,
"GSSAPI did not return realm but realm matching was requested");
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return STATUS_ERROR;
}
ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
pg_krb_caseins_users);
- gss_release_buffer(&lmin_s, &gbuf);
+ gss_release_buffer(&min_stat, &gbuf);
return ret;
}
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644
index 0000000000..78d9f5d325
--- /dev/null
+++ b/src/backend/libpq/be-gssapi-common.c
@@ -0,0 +1,64 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+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")));
+}
+
+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 0000000000..c020051d2e
--- /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 0000000000..d860f71b71
--- /dev/null
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -0,0 +1,319 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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>
+
+static ssize_t
+send_buffered_data(Port *port, size_t len)
+{
+ ssize_t ret = secure_raw_write(
+ port,
+ port->gss->writebuf.data + port->gss->writebuf.cursor,
+ port->gss->writebuf.len - port->gss->writebuf.cursor);
+ if (ret < 0)
+ return ret;
+
+ /* update and possibly clear buffer state */
+ port->gss->writebuf.cursor += ret;
+
+ if (port->gss->writebuf.cursor == port->gss->writebuf.len)
+ {
+ /* entire request has now been written */
+ resetStringInfo(&port->gss->writebuf);
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->writebuf.len != 0)
+ return send_buffered_data(port, len);
+
+ /* encrypt the message */
+ output.value = NULL;
+ output.length = 0;
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI wrap error"), major, minor);
+ goto cleanup;
+ } else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ goto cleanup;
+ }
+
+ /* 4 network-order length bytes, then payload */
+ netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf, output.value, output.length);
+
+ ret = send_buffered_data(port, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(pg_gssinfo *gss, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* load up any available data */
+ if (gss->buf.len > 4 && gss->buf.cursor < gss->buf.len)
+ {
+ /* clamp length */
+ if (len > gss->buf.len - gss->buf.cursor)
+ len = gss->buf.len - gss->buf.cursor;
+
+ memcpy(ptr, gss->buf.data + gss->buf.cursor, len);
+ gss->buf.cursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (gss->buf.cursor == gss->buf.len)
+ resetStringInfo(&gss->buf);
+
+ return ret;
+}
+
+static ssize_t
+load_packetlen(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ ssize_t ret;
+
+ if (gss->buf.len < 4)
+ {
+ enlargeStringInfo(&gss->buf, 4 - gss->buf.len);
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ 4 - gss->buf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(Port *port, size_t len)
+{
+ ssize_t ret;
+ pg_gssinfo *gss = port->gss;
+
+ enlargeStringInfo(&gss->buf, len - gss->buf.len + 4);
+
+ ret = secure_raw_read(port, gss->buf.data + gss->buf.len,
+ len - gss->buf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ gss->buf.len += ret;
+ gss->buf.data[gss->buf.len] = '\0';
+ if (gss->buf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ return 0;
+}
+
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output;
+ ssize_t ret;
+ int conf = 0;
+ pg_gssinfo *gss = port->gss;
+
+ if (gss->buf.cursor > 0)
+ return read_from_buffer(gss, ptr, len);
+
+ /* load length if not present */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ return ret;
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ return ret;
+
+ /* decrypt the packet */
+ output.value = NULL;
+ output.length = 0;
+ input.value = gss->buf.data + 4;
+
+ major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(ERROR, gettext_noop("GSSAPI unwrap error"),
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* put the decrypted packet in the buffer */
+ resetStringInfo(&gss->buf);
+ enlargeStringInfo(&gss->buf, output.length);
+
+ memcpy(gss->buf.data, output.value, output.length);
+ gss->buf.len = output.length;
+ gss->buf.data[gss->buf.len] = '\0';
+
+ ret = read_from_buffer(gss, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+ssize_t
+secure_open_gssapi(Port *port)
+{
+ pg_gssinfo *gss = port->gss;
+ bool complete_next = false;
+
+ /*
+ * 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)
+ {
+ OM_uint32 major, minor;
+ size_t ret;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+
+ /* Handle any outgoing data */
+ if (gss->writebuf.len != 0)
+ {
+ ret = send_buffered_data(port, 1);
+ if (ret != 1)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_WRITEABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ }
+
+ if (complete_next)
+ break;
+
+ /* Load incoming data */
+ ret = load_packetlen(port);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+
+ input.length = ntohl(*(uint32*)gss->buf.data);
+ ret = load_packet(port, input.length);
+ if (ret != 0)
+ {
+ WaitLatchOrSocket(MyLatch, WL_SOCKET_READABLE, port->sock, 0,
+ WAIT_EVENT_GSS_OPEN_SERVER);
+ continue;
+ }
+ input.value = gss->buf.data + 4;
+
+ /* 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);
+ resetStringInfo(&gss->buf);
+ 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;
+ }
+
+ if (output.length != 0)
+ {
+ /* Queue packet for writing */
+ uint32 netlen = htonl(output.length);
+ appendBinaryStringInfo(&gss->writebuf, (char *)&netlen, 4);
+ appendBinaryStringInfo(&gss->writebuf,
+ output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ continue;
+ }
+
+ /* We're done - woohoo! */
+ break;
+ }
+ port->gss->enc = true;
+ return 0;
+}
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edfe2c0751..7dd1cf7090 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -152,6 +152,14 @@ retry:
n = be_tls_read(port, ptr, len, &waitfor);
}
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);
@@ -255,6 +263,14 @@ retry:
n = be_tls_write(port, ptr, len, &waitfor);
}
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);
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index acf625e4ec..9a1aa4e5cc 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, "hostgss") == 0 ||
+ strcmp(token->string, "hostnogss") == 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') /* "hostgss" */
{
- parsedline->conntype = ctHostNoSSL;
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgss 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 = "hostgss 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)
@@ -2060,6 +2088,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)
{
@@ -2393,6 +2432,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
case ctHostNoSSL:
typestr = "hostnossl";
break;
+ case ctHostGSS:
+ typestr = "hostgss";
+ break;
+ case ctHostNoGSS:
+ typestr = "hostnogss";
+ break;
}
if (typestr)
values[index++] = CStringGetTextDatum(typestr);
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 084573e77c..72ce147308 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -3562,6 +3562,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 a4b53b33cd..87dbb33a88 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1894,7 +1894,7 @@ initMasks(fd_set *rmask)
* if we detect a communications failure.)
*/
static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
{
int32 len;
void *buf;
@@ -1905,11 +1905,11 @@ ProcessStartupPacket(Port *port, bool SSLdone)
if (pq_getbytes((char *) &len, 4) == EOF)
{
/*
- * EOF after SSLdone probably means the client didn't like our
+ * EOF after secure_done probably means the client didn't like our
* response to NEGOTIATE_SSL_CODE. That's not an error condition, so
* don't clutter the log with a complaint.
*/
- if (!SSLdone)
+ if (!secure_done)
ereport(COMMERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("incomplete startup packet")));
@@ -1961,7 +1961,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
return STATUS_ERROR;
}
- if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+ if (proto == NEGOTIATE_SSL_CODE && !secure_done)
{
char SSLok;
@@ -1994,6 +1994,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 */
@@ -2413,6 +2439,17 @@ ConnCreate(int serverFd)
ExitPostmaster(1);
}
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
#endif
return port;
@@ -2429,7 +2466,15 @@ ConnFree(Port *conn)
secure_close(conn);
#endif
if (conn->gss)
+ {
+#ifdef ENABLE_GSS
+ if (conn->gss->buf.data)
+ pfree(conn->gss->buf.data);
+ if (conn->gss->writebuf.data)
+ pfree(conn->gss->writebuf.data);
+#endif
free(conn->gss);
+ }
free(conn);
}
@@ -4761,6 +4806,17 @@ SubPostmasterMain(int argc, char *argv[])
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
#endif
+#ifdef ENABLE_GSS
+ {
+ MemoryContext save = CurrentMemoryContext;
+ CurrentMemoryContext = TopMemoryContext;
+
+ initStringInfo(&port->gss->buf);
+ initStringInfo(&port->gss->writebuf);
+
+ CurrentMemoryContext = save;
+ }
+#endif
/*
* If appropriate, physically re-attach to shared memory segment. We want
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 5f68f4c666..830ddaa25a 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 struct HbaLine
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7698cd1f88..3e46ac437a 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -66,7 +66,7 @@ typedef struct
#include "datatype/timestamp.h"
#include "libpq/hba.h"
#include "libpq/pqcomm.h"
-
+#include "lib/stringinfo.h"
typedef enum CAC_state
{
@@ -86,6 +86,9 @@ typedef struct
gss_cred_id_t cred; /* GSSAPI connection cred's */
gss_ctx_id_t ctx; /* GSSAPI connection context */
gss_name_t name; /* GSSAPI client name */
+ bool enc; /* GSSAPI encryption in use */
+ StringInfoData buf; /* GSSAPI encryption data buffering */
+ StringInfoData writebuf; /* GSSAPI nonblocking write buffering */
#endif
} pg_gssinfo;
#endif
@@ -278,6 +281,12 @@ extern char *be_tls_get_peer_finished(Port *port, size_t *len);
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
#endif
+#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
+
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 7bf06c65e9..e34f552ef7 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -92,6 +92,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 cc0e0b32c7..ade1190096 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 be2f59239b..4f06f7a2bc 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -787,7 +787,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;
/* ----------
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index abe0a50e98..c814e5e35a 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -57,6 +57,10 @@ else
OBJS += sha2.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/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 3b2073a47f..af2ef78d76 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,18 +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;
- char *host = PQhost(conn);
-
- if (!(host && host[0] != '\0'))
- {
- printfPQExpBuffer(&conn->errorMessage,
- libpq_gettext("host name must be specified\n"));
- return STATUS_ERROR;
- }
+ int ret;
if (conn->gctx)
{
@@ -215,33 +159,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
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a7e969d7c1..23270f7f6b 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.
@@ -303,6 +309,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
+ /*
+ * Expose gssmode similarly to sslmode - we can stil handle "disable" and
+ * "prefer".
+ */
+ {"gssmode", "PGGSSMODE", DefaultGSSMode, NULL,
+ "GSS-Mode", "", 7, /* sizeof("disable") == 7 */
+ offsetof(struct pg_conn, gssmode)},
+
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1166,6 +1180,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
+ /*
+ * validate gssmode option
+ */
+ if (conn->gssmode)
+ {
+ if (strcmp(conn->gssmode, "disable") != 0 &&
+ strcmp(conn->gssmode, "prefer") != 0 &&
+ strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("invalid gssmode value: \"%s\"\n"),
+ conn->gssmode);
+ return false;
+ }
+#ifndef ENABLE_GSS
+ if (strcmp(conn->gssmode, "require") != 0)
+ {
+ conn->status = CONNECTION_BAD;
+ printfPQExpBuffer(
+ &conn->errorMessage,
+ libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+ return false;
+ }
+#endif
+ }
+ else
+ {
+ conn->gssmode = strdup(DefaultGSSMode);
+ if (!conn->gssmode)
+ goto oom_error;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -1804,6 +1851,11 @@ connectDBStart(PGconn *conn)
conn->wait_ssl_try = true;
#endif
+#ifdef ENABLE_GSS
+ if (conn->gssmode[0] == 'd') /* "disable" */
+ conn->try_gss = false;
+#endif
+
/*
* Set up to try to connect, with protocol 3.0 as the first attempt.
*/
@@ -2051,6 +2103,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
+ case CONNECTION_GSS_STARTUP:
break;
default:
@@ -2426,17 +2479,54 @@ 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 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 (conn->try_gss && !conn->gctx)
+ conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+ if (conn->try_gss && !conn->gctx)
+ {
+ 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->gssmode[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 (IS_AF_UNIX(conn->raddr.addr.ss_family))
- {
- /* Don't bother requesting SSL over a Unix socket */
- conn->allow_ssl_try = false;
- }
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@@ -2629,6 +2719,97 @@ 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->gssmode[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->gssmode[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.
@@ -2781,6 +2962,24 @@ keep_going: /* We will come back to here until there is
/* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor;
+#ifdef ENABLE_GSS
+ /*
+ * If gssmode is "prefer" and we're using GSSAPI, retry
+ * without it.
+ */
+ if (conn->gssenc && conn->gssmode[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
/*
@@ -3361,6 +3560,11 @@ makeEmptyPGconn(void)
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
+#ifdef ENABLE_GSS
+ conn->try_gss = true;
+ initPQExpBuffer(&conn->gbuf);
+ initPQExpBuffer(&conn->gwritebuf);
+#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@@ -3492,10 +3696,28 @@ freePGconn(PGconn *conn)
free(conn->sslcompression);
if (conn->requirepeer)
free(conn->requirepeer);
+ if (conn->gssmode)
+ free(conn->gssmode);
#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;
+ }
+ termPQExpBuffer(&conn->gbuf);
+ termPQExpBuffer(&conn->gwritebuf);
+#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 0000000000..35e195b7a6
--- /dev/null
+++ b/src/interfaces/libpq/fe-gssapi-common.c
@@ -0,0 +1,128 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ */
+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 (GSS_ERROR(major))
+ {
+ *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 0000000000..58811df9f1
--- /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 0000000000..a71df69ff2
--- /dev/null
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -0,0 +1,343 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/*
+ * 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
+
+static ssize_t
+send_buffered_data(PGconn *conn, size_t len)
+{
+ ssize_t ret = pqsecure_raw_write(conn,
+ conn->gwritebuf.data + conn->gwritecurs,
+ conn->gwritebuf.len - conn->gwritecurs);
+ if (ret < 0)
+ return ret;
+
+ conn->gwritecurs += ret;
+
+ if (conn->gwritecurs == conn->gwritebuf.len)
+ {
+ /* entire request has now been written */
+ resetPQExpBuffer(&conn->gwritebuf);
+ conn->gwritecurs = 0;
+ return len;
+ }
+
+ /* need to be called again */
+ errno = EWOULDBLOCK;
+ return -1;
+}
+
+ssize_t
+pg_GSS_write(PGconn *conn, void *ptr, size_t len)
+{
+ OM_uint32 major, minor;
+ gss_buffer_desc input, output = GSS_C_EMPTY_BUFFER;
+ ssize_t ret = -1;
+ int conf = 0;
+ uint32 netlen;
+
+ if (conn->gwritebuf.len != 0)
+ return send_buffered_data(conn, len);
+
+ /* encrypt the message */
+ input.value = ptr;
+ input.length = len;
+
+ major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+ &input, &conf, &output);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ goto cleanup;
+ }
+
+ /* 4 network-order bytes of length, then payload */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+
+ ret = send_buffered_data(conn, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+static ssize_t
+read_from_buffer(PGconn *conn, void *ptr, size_t len)
+{
+ ssize_t ret = 0;
+
+ /* check for available data */
+ if (conn->gcursor < conn->gbuf.len)
+ {
+ /* clamp length */
+ if (len > conn->gbuf.len - conn->gcursor)
+ len = conn->gbuf.len - conn->gcursor;
+
+ memcpy(ptr, conn->gbuf.data + conn->gcursor, len);
+ conn->gcursor += len;
+ ret = len;
+ }
+
+ /* reset buffer if all data has been read */
+ if (conn->gcursor == conn->gbuf.len)
+ {
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ }
+
+ return ret;
+}
+
+static ssize_t
+load_packet_length(PGconn *conn)
+{
+ ssize_t ret;
+ if (conn->gbuf.len < 4)
+ {
+ ret = enlargePQExpBuffer(&conn->gbuf, 4 - conn->gbuf.len);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "Failed to fit packet length in buffer\n"));
+ return -1;
+ }
+
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ 4 - conn->gbuf.len);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len < 4)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static ssize_t
+load_packet(PGconn *conn, size_t len)
+{
+ ssize_t ret;
+
+ ret = enlargePQExpBuffer(&conn->gbuf, len - conn->gbuf.len + 4);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI encrypted packet length %ld too big\n"),
+ len);
+ return -1;
+ }
+
+ /* load any missing parts of the packet */
+ if (conn->gbuf.len - 4 < len)
+ {
+ ret = pqsecure_raw_read(conn, conn->gbuf.data + conn->gbuf.len,
+ len - conn->gbuf.len + 4);
+ if (ret < 0)
+ return ret;
+
+ /* update buffer state */
+ conn->gbuf.len += ret;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+ if (conn->gbuf.len - 4 < len)
+ {
+ errno = EWOULDBLOCK;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+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;
+ int conf = 0;
+
+ /* handle any buffered data */
+ if (conn->gcursor != 0)
+ return read_from_buffer(conn, ptr, len);
+
+ /* load in the packet length, if not yet loaded */
+ ret = load_packet_length(conn);
+ if (ret < 0)
+ return ret;
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0)
+ return ret;
+
+ /* decrypt the packet */
+ input.value = conn->gbuf.data + 4;
+
+ major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+ if (GSS_ERROR(major))
+ {
+ pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
+ major, minor);
+ ret = -1;
+ goto cleanup;
+ }
+ else if (conf == 0)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI did not provide confidentiality\n"));
+ ret = -1;
+ goto cleanup;
+ }
+
+ /* load decrypted packet into our buffer */
+ conn->gcursor = 0;
+ resetPQExpBuffer(&conn->gbuf);
+ ret = enlargePQExpBuffer(&conn->gbuf, output.length);
+ if (ret != 1)
+ {
+ printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+ "GSSAPI decrypted packet length %ld too big\n"),
+ output.length);
+ ret = -1;
+ goto cleanup;
+ }
+
+ memcpy(conn->gbuf.data, output.value, output.length);
+ conn->gbuf.len = output.length;
+ conn->gbuf.data[conn->gbuf.len] = '\0';
+
+ ret = read_from_buffer(conn, ptr, len);
+cleanup:
+ if (output.value != NULL)
+ gss_release_buffer(&minor, &output);
+ return ret;
+}
+
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+ ssize_t ret;
+ OM_uint32 major, minor;
+ uint32 netlen;
+ gss_buffer_desc input = GSS_C_EMPTY_BUFFER, output = GSS_C_EMPTY_BUFFER;
+
+ /* Send out any data we might have */
+ if (conn->gwritebuf.len != 0)
+ {
+ ret = send_buffered_data(conn, 1);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_WRITING;
+ else if (ret == 1)
+ /* sent all data */
+ return PGRES_POLLING_READING;
+ return PGRES_POLLING_FAILED;
+ }
+
+ /* Client sends first, and sending creates a context */
+ if (conn->gctx)
+ {
+ /* Process any incoming data we might have */
+ ret = load_packet_length(conn);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ if (conn->gbuf.data[0] == 'E')
+ {
+ /*
+ * We can taken an error here, and it's my least favorite thing.
+ * How long can error messages be? That's a good question, but
+ * backend's pg_gss_error() caps them at 256. Do a single read
+ * for that and call it a day. Cope with this here rather than in
+ * load_packet since expandPQExpBuffer will destroy any data in
+ * the buffer on failure.
+ */
+ load_packet(conn, 256);
+ printfPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Server error: %s"),
+ conn->gbuf.data + 1);
+ return PGRES_POLLING_FAILED;
+ }
+
+ input.length = ntohl(*(uint32 *)conn->gbuf.data);
+ ret = load_packet(conn, input.length);
+ if (ret < 0 && errno == EWOULDBLOCK)
+ return PGRES_POLLING_READING;
+ else if (ret < 0)
+ return PGRES_POLLING_FAILED;
+
+ input.value = conn->gbuf.data + 4;
+ }
+
+ ret = pg_GSS_load_servicename(conn);
+ if (ret != STATUS_OK)
+ return PGRES_POLLING_FAILED;
+
+ 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);
+ resetPQExpBuffer(&conn->gbuf);
+ 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;
+ return PGRES_POLLING_OK;
+ }
+
+ /* Queue the token for writing */
+ netlen = htonl(output.length);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, (char *)&netlen, 4);
+ appendBinaryPQExpBuffer(&conn->gwritebuf, output.value, output.length);
+ gss_release_buffer(&minor, &output);
+ return PGRES_POLLING_WRITING;
+}
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index f7dc249bf0..18155e4b5e 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, 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);
@@ -287,7 +294,7 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
* to determine whether to continue/retry after error.
*/
ssize_t
-pqsecure_write(PGconn *conn, const void *ptr, size_t len)
+pqsecure_write(PGconn *conn, void *ptr, size_t len)
{
ssize_t n;
@@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, 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);
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..bdd5a10cd8 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
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9a586ff25a..6cf3459a8c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -22,6 +22,7 @@
/* We assume libpq-fe.h has already been included. */
#include "libpq-events.h"
+#include "lib/stringinfo.h"
#include <time.h>
#ifndef WIN32
@@ -473,9 +474,19 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
+ char *gssmode; /* 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 */
+ PQExpBufferData gbuf; /* GSS encryption buffering */
+ size_t gcursor; /* GSS buffering position */
+ PQExpBufferData gwritebuf; /* GSS nonblocking write buffering */
+ size_t gwritecurs; /* GSS write buffer position */
+ 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
@@ -651,7 +662,7 @@ extern void pqsecure_destroy(void);
extern PostgresPollingStatusType pqsecure_open_client(PGconn *);
extern void pqsecure_close(PGconn *);
extern ssize_t pqsecure_read(PGconn *, void *ptr, size_t len);
-extern ssize_t pqsecure_write(PGconn *, const void *ptr, size_t len);
+extern ssize_t pqsecure_write(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_read(PGconn *, void *ptr, size_t len);
extern ssize_t pqsecure_raw_write(PGconn *, const void *ptr, size_t len);
@@ -745,6 +756,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, 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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 593732fd95..b38a41843d 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -183,13 +183,18 @@ sub mkvcbuild
$postgres->AddLibrary('wldap32.lib') if ($solution->{options}->{ldap});
$postgres->FullExportDLL('postgres.lib');
- # The OBJS scraper doesn't know about ifdefs, so remove be-secure-openssl.c
- # if building without OpenSSL
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # be-secure-openssl.c if building without OpenSSL, and
+ # be-gssapi-common.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$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/backennd/libpq/be-gssapi-common.c');
+ }
my $snowball = $solution->AddProject('dict_snowball', 'dll', '',
'src/backend/snowball');
@@ -243,9 +248,10 @@ sub mkvcbuild
'src/interfaces/libpq/libpq.rc');
$libpq->AddReference($libpgport);
- # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
- # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
- # building with OpenSSL.
+ # The OBJS scraper doesn't know about ifdefs, so remove
+ # fe-secure-openssl.c and sha2_openssl.c if building without
+ # OpenSSL, and remove sha2.c if building with OpenSSL. Also
+ # remove fe-gssapi-common.c when building with GSSAPI.
if (!$solution->{options}->{openssl})
{
$libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
@@ -256,6 +262,10 @@ sub mkvcbuild
{
$libpq->RemoveFile('src/common/sha2.c');
}
+ if (!$solution->{options}->{gss})
+ {
+ $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
+ }
my $libpqwalreceiver =
$solution->AddProject('libpqwalreceiver', 'dll', '',
--
2.17.0
--=-=-=--
^ permalink raw reply [nested|flat] 20+ messages in thread
* [PATCH 4/7] Add explain(MACHINE) to hide machine-dependent output..
@ 2020-02-23 00:45 Justin Pryzby <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Justin Pryzby @ 2020-02-23 00:45 UTC (permalink / raw)
This new option hides some output that has traditionally been shown; the option
is enabled by regression mode to hide unstable output.
This allows EXPLAIN ANALYZE to be used in regression tests with stable output.
This is like a "quiet" mode, or negative verbosity.
Also add regression tests for HashAgg and Bitmap scan, which previously had no
tests with explain(analyze).
This does not handle variations in "Workers Launched", or other parallel worker
bits which are handled by force_parallel_mode=regress.
Also add tests for show_hashagg_info and tidbitmap, for which there's no other
test.
---
src/backend/commands/explain.c | 77 +++++++++++++------
src/include/commands/explain.h | 1 +
src/test/isolation/expected/horizons.out | 40 +++++-----
src/test/isolation/specs/horizons.spec | 2 +-
src/test/regress/expected/explain.out | 55 +++++++++++++
.../regress/expected/incremental_sort.out | 4 +-
src/test/regress/expected/memoize.out | 35 ++++-----
src/test/regress/expected/merge.out | 22 +++---
src/test/regress/expected/partition_prune.out | 4 +-
src/test/regress/expected/select_parallel.out | 32 +++-----
src/test/regress/expected/subselect.out | 21 +----
src/test/regress/sql/explain.sql | 16 ++++
src/test/regress/sql/memoize.sql | 4 +-
src/test/regress/sql/select_parallel.sql | 20 +----
src/test/regress/sql/subselect.sql | 19 +----
15 files changed, 192 insertions(+), 160 deletions(-)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 6d39cbe8fb2..7c5e0ad2c1e 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -175,6 +175,7 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
bool summary_set = false;
bool costs_set = false;
bool buffers_set = false;
+ bool machine_set = false;
/* Parse options list. */
foreach(lc, stmt->options)
@@ -210,6 +211,11 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
summary_set = true;
es->summary = defGetBoolean(opt);
}
+ else if (strcmp(opt->defname, "machine") == 0)
+ {
+ machine_set = true;
+ es->machine = defGetBoolean(opt);
+ }
else if (strcmp(opt->defname, "format") == 0)
{
char *p = defGetString(opt);
@@ -260,6 +266,9 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt,
/* if the buffers option was not set explicitly, set default value */
es->buffers = (buffers_set) ? es->buffers : !explain_regress;
+ /* if the machine option was not set explicitly, set default value */
+ es->machine = (machine_set) ? es->machine : !explain_regress;
+
query = castNode(Query, stmt->query);
if (IsQueryIdEnabled())
jstate = JumbleQuery(query, pstate->p_sourcetext);
@@ -627,7 +636,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
* generated by regression test suites.
*/
if (es->verbose && plannedstmt->queryId != UINT64CONST(0) &&
- compute_query_id != COMPUTE_QUERY_ID_REGRESS)
+ compute_query_id != COMPUTE_QUERY_ID_REGRESS && es->machine)
{
/*
* Output the queryid as an int64 rather than a uint64 so we match
@@ -638,7 +647,7 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es,
}
/* Show buffer usage in planning */
- if (bufusage)
+ if (bufusage && es->buffers)
{
ExplainOpenGroup("Planning", "Planning", true, es);
show_buffer_usage(es, bufusage, true);
@@ -1803,7 +1812,7 @@ ExplainNode(PlanState *planstate, List *ancestors,
if (plan->qual)
show_instrumentation_count("Rows Removed by Filter", 1,
planstate, es);
- if (es->analyze)
+ if (es->analyze && es->machine)
ExplainPropertyFloat("Heap Fetches", NULL,
planstate->instrument->ntuples2, 0, es);
break;
@@ -2785,8 +2794,12 @@ show_sort_info(SortState *sortstate, ExplainState *es)
if (es->format == EXPLAIN_FORMAT_TEXT)
{
ExplainIndentText(es);
- appendStringInfo(es->str, "Sort Method: %s %s: " INT64_FORMAT "kB\n",
- sortMethod, spaceType, spaceUsed);
+ appendStringInfo(es->str, "Sort Method: %s",
+ sortMethod);
+ if (es->machine)
+ appendStringInfo(es->str, " %s: " INT64_FORMAT "kB",
+ spaceType, spaceUsed);
+ appendStringInfoString(es->str, "\n");
}
else
{
@@ -2830,8 +2843,12 @@ show_sort_info(SortState *sortstate, ExplainState *es)
{
ExplainIndentText(es);
appendStringInfo(es->str,
- "Sort Method: %s %s: " INT64_FORMAT "kB\n",
- sortMethod, spaceType, spaceUsed);
+ "Sort Method: %s",
+ sortMethod);
+ if (es->machine)
+ appendStringInfo(es->str, " %s: " INT64_FORMAT "kB", spaceType, spaceUsed);
+
+ appendStringInfoString(es->str, "\n");
}
else
{
@@ -3119,25 +3136,26 @@ show_hash_info(HashState *hashstate, ExplainState *es)
ExplainPropertyInteger("Peak Memory Usage", "kB",
spacePeakKb, es);
}
- else if (hinstrument.nbatch_original != hinstrument.nbatch ||
- hinstrument.nbuckets_original != hinstrument.nbuckets)
+ else
{
ExplainIndentText(es);
- appendStringInfo(es->str,
- "Buckets: %d (originally %d) Batches: %d (originally %d) Memory Usage: %ldkB\n",
+ if (hinstrument.nbatch_original != hinstrument.nbatch ||
+ hinstrument.nbuckets_original != hinstrument.nbuckets)
+ appendStringInfo(es->str,
+ "Buckets: %d (originally %d) Batches: %d (originally %d)",
hinstrument.nbuckets,
hinstrument.nbuckets_original,
hinstrument.nbatch,
- hinstrument.nbatch_original,
- spacePeakKb);
- }
- else
- {
- ExplainIndentText(es);
- appendStringInfo(es->str,
- "Buckets: %d Batches: %d Memory Usage: %ldkB\n",
- hinstrument.nbuckets, hinstrument.nbatch,
- spacePeakKb);
+ hinstrument.nbatch_original);
+ else
+ appendStringInfo(es->str,
+ "Buckets: %d Batches: %d",
+ hinstrument.nbuckets, hinstrument.nbatch);
+
+ if (es->machine)
+ appendStringInfo(es->str, " Memory Usage: %ldkB", spacePeakKb);
+
+ appendStringInfoChar(es->str, '\n');
}
}
}
@@ -3221,12 +3239,16 @@ show_memoize_info(MemoizeState *mstate, List *ancestors, ExplainState *es)
{
ExplainIndentText(es);
appendStringInfo(es->str,
- "Hits: " UINT64_FORMAT " Misses: " UINT64_FORMAT " Evictions: " UINT64_FORMAT " Overflows: " UINT64_FORMAT " Memory Usage: " INT64_FORMAT "kB\n",
+ "Hits: " UINT64_FORMAT " Misses: " UINT64_FORMAT " Evictions: " UINT64_FORMAT " Overflows: " UINT64_FORMAT,
mstate->stats.cache_hits,
mstate->stats.cache_misses,
mstate->stats.cache_evictions,
- mstate->stats.cache_overflows,
+ mstate->stats.cache_overflows);
+ if (es->machine)
+ appendStringInfo(es->str, " Memory Usage: " INT64_FORMAT "kB",
memPeakKb);
+
+ appendStringInfoChar(es->str, '\n');
}
}
@@ -3295,13 +3317,16 @@ show_hashagg_info(AggState *aggstate, ExplainState *es)
Agg *agg = (Agg *) aggstate->ss.ps.plan;
int64 memPeakKb = (aggstate->hash_mem_peak + 1023) / 1024;
+ /* XXX: there's nothing portable we can show here ? */
+ if (!es->machine)
+ return;
+
if (agg->aggstrategy != AGG_HASHED &&
agg->aggstrategy != AGG_MIXED)
return;
if (es->format != EXPLAIN_FORMAT_TEXT)
{
-
if (es->costs)
ExplainPropertyInteger("Planned Partitions", NULL,
aggstate->hash_planned_partitions, es);
@@ -3414,6 +3439,10 @@ show_hashagg_info(AggState *aggstate, ExplainState *es)
static void
show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es)
{
+ /* XXX: there's nothing portable we can show here ? */
+ if (!es->machine)
+ return;
+
if (es->format != EXPLAIN_FORMAT_TEXT)
{
ExplainPropertyInteger("Exact Heap Blocks", NULL,
diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h
index 912bc9484ef..8e62761ff70 100644
--- a/src/include/commands/explain.h
+++ b/src/include/commands/explain.h
@@ -46,6 +46,7 @@ typedef struct ExplainState
bool timing; /* print detailed node timing */
bool summary; /* print total planning and execution timing */
bool settings; /* print modified settings */
+ bool machine; /* print memory/disk and other machine-specific output */
ExplainFormat format; /* output format */
/* state for output formatting --- not reset for each new plan tree */
int indent; /* current indentation level */
diff --git a/src/test/isolation/expected/horizons.out b/src/test/isolation/expected/horizons.out
index 4150b2dee64..ee3e495a646 100644
--- a/src/test/isolation/expected/horizons.out
+++ b/src/test/isolation/expected/horizons.out
@@ -24,7 +24,7 @@ Index Only Scan using horizons_tst_data_key on horizons_tst
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -34,7 +34,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -47,7 +47,7 @@ step pruner_delete:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -57,7 +57,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -94,7 +94,7 @@ Index Only Scan using horizons_tst_data_key on horizons_tst
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -104,7 +104,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -117,7 +117,7 @@ step pruner_delete:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -127,7 +127,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -156,7 +156,7 @@ step ll_start:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -166,7 +166,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -180,7 +180,7 @@ step pruner_delete:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -190,7 +190,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -220,7 +220,7 @@ step ll_start:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -230,7 +230,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -246,7 +246,7 @@ step pruner_vacuum:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -256,7 +256,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -285,7 +285,7 @@ step ll_start:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -295,7 +295,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -311,7 +311,7 @@ step pruner_vacuum:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
@@ -321,7 +321,7 @@ step pruner_query:
step pruner_query:
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
?column?
diff --git a/src/test/isolation/specs/horizons.spec b/src/test/isolation/specs/horizons.spec
index d5239ff2287..082205d36ba 100644
--- a/src/test/isolation/specs/horizons.spec
+++ b/src/test/isolation/specs/horizons.spec
@@ -82,7 +82,7 @@ step pruner_vacuum
step pruner_query
{
SELECT explain_json($$
- EXPLAIN (FORMAT json, BUFFERS, ANALYZE)
+ EXPLAIN (FORMAT json, BUFFERS, ANALYZE, machine)
SELECT * FROM horizons_tst ORDER BY data;$$)->0->'Plan'->'Heap Fetches';
}
diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out
index 4abc5a346c6..0e2cc7cc4f6 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -291,6 +291,61 @@ select explain_filter('explain (analyze, buffers, format json) select * from int
(1 row)
set track_io_timing = off;
+-- HashAgg
+begin;
+SET work_mem='64kB';
+select explain_filter('explain (analyze) SELECT a, COUNT(1) FROM generate_series(1,9999)a GROUP BY 1');
+ explain_filter
+----------------------------------------------------------------------------------------------------------------
+ HashAggregate (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Group Key: a
+ Batches: N Memory Usage: NkB Disk Usage: NkB
+ -> Function Scan on generate_series a (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(6 rows)
+
+select explain_filter('explain (analyze, machine off) SELECT a, COUNT(1) FROM generate_series(1,9999)a GROUP BY 1');
+ explain_filter
+----------------------------------------------------------------------------------------------------------------
+ HashAggregate (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Group Key: a
+ -> Function Scan on generate_series a (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(5 rows)
+
+rollback;
+-- Bitmap scan
+begin;
+SET enable_indexscan=no;
+CREATE TABLE explainbitmap AS SELECT i AS a FROM generate_series(1,999) AS i;
+ANALYZE explainbitmap;
+CREATE INDEX ON explainbitmap(a);
+select explain_filter('explain (analyze) SELECT * FROM explainbitmap WHERE a<9');
+ explain_filter
+----------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on explainbitmap (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Recheck Cond: (a < N)
+ Heap Blocks: exact=N
+ -> Bitmap Index Scan on explainbitmap_a_idx (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Index Cond: (a < N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(7 rows)
+
+select explain_filter('explain (analyze, machine off) SELECT * FROM explainbitmap WHERE a<9');
+ explain_filter
+----------------------------------------------------------------------------------------------------------------------
+ Bitmap Heap Scan on explainbitmap (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Recheck Cond: (a < N)
+ -> Bitmap Index Scan on explainbitmap_a_idx (cost=N.N..N.N rows=N width=N) (actual time=N.N..N.N rows=N loops=N)
+ Index Cond: (a < N)
+ Planning Time: N.N ms
+ Execution Time: N.N ms
+(6 rows)
+
+rollback;
-- SETTINGS option
-- We have to ignore other settings that might be imposed by the environment,
-- so printing the whole Settings field unfortunately won't do.
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 0d8d77140a4..050e6cbfec7 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -542,7 +542,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
Full-sort Groups: 2 Sort Methods: top-N heapsort, quicksort Average Memory: NNkB Peak Memory: NNkB
-> Sort (actual rows=101 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: NNkB
+ Sort Method: quicksort
-> Seq Scan on t (actual rows=1000 loops=1)
(9 rows)
@@ -745,7 +745,7 @@ select explain_analyze_without_memory('select * from (select * from t order by a
Pre-sorted Groups: 5 Sort Methods: top-N heapsort, quicksort Average Memory: NNkB Peak Memory: NNkB
-> Sort (actual rows=1000 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: NNkB
+ Sort Method: quicksort
-> Seq Scan on t (actual rows=1000 loops=1)
(10 rows)
diff --git a/src/test/regress/expected/memoize.out b/src/test/regress/expected/memoize.out
index 00438eb1ea0..1b1557ce9fc 100644
--- a/src/test/regress/expected/memoize.out
+++ b/src/test/regress/expected/memoize.out
@@ -21,9 +21,7 @@ begin
end if;
ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
- ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
- ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
- ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+ ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
return next ln;
end loop;
end;
@@ -45,11 +43,10 @@ WHERE t2.unique1 < 1000;', false);
-> Memoize (actual rows=1 loops=N)
Cache Key: t2.twenty
Cache Mode: logical
- Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ Hits: 980 Misses: 20 Evictions: Zero Overflows: 0
-> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
Index Cond: (unique1 = t2.twenty)
- Heap Fetches: N
-(12 rows)
+(11 rows)
-- And check we get the expected results.
SELECT COUNT(*),AVG(t1.unique1) FROM tenk1 t1
@@ -75,11 +72,10 @@ WHERE t1.unique1 < 1000;', false);
-> Memoize (actual rows=1 loops=N)
Cache Key: t1.twenty
Cache Mode: logical
- Hits: 980 Misses: 20 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ Hits: 980 Misses: 20 Evictions: Zero Overflows: 0
-> Index Only Scan using tenk1_unique1 on tenk1 t2 (actual rows=1 loops=N)
Index Cond: (unique1 = t1.twenty)
- Heap Fetches: N
-(12 rows)
+(11 rows)
-- And check we get the expected results.
SELECT COUNT(*),AVG(t2.unique1) FROM tenk1 t1,
@@ -111,11 +107,10 @@ WHERE t2.unique1 < 1200;', true);
-> Memoize (actual rows=1 loops=N)
Cache Key: t2.thousand
Cache Mode: logical
- Hits: N Misses: N Evictions: N Overflows: 0 Memory Usage: NkB
+ Hits: N Misses: N Evictions: N Overflows: 0
-> Index Only Scan using tenk1_unique1 on tenk1 t1 (actual rows=1 loops=N)
Index Cond: (unique1 = t2.thousand)
- Heap Fetches: N
-(12 rows)
+(11 rows)
CREATE TABLE flt (f float);
CREATE INDEX flt_f_idx ON flt (f);
@@ -129,15 +124,13 @@ SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f = f2.f;', false);
-------------------------------------------------------------------------------
Nested Loop (actual rows=4 loops=N)
-> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
- Heap Fetches: N
-> Memoize (actual rows=2 loops=N)
Cache Key: f1.f
Cache Mode: logical
- Hits: 1 Misses: 1 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ Hits: 1 Misses: 1 Evictions: Zero Overflows: 0
-> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
Index Cond: (f = f1.f)
- Heap Fetches: N
-(10 rows)
+(8 rows)
-- Ensure memoize operates in binary mode
SELECT explain_memoize('
@@ -146,15 +139,13 @@ SELECT * FROM flt f1 INNER JOIN flt f2 ON f1.f >= f2.f;', false);
-------------------------------------------------------------------------------
Nested Loop (actual rows=4 loops=N)
-> Index Only Scan using flt_f_idx on flt f1 (actual rows=2 loops=N)
- Heap Fetches: N
-> Memoize (actual rows=2 loops=N)
Cache Key: f1.f
Cache Mode: binary
- Hits: 0 Misses: 2 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ Hits: 0 Misses: 2 Evictions: Zero Overflows: 0
-> Index Only Scan using flt_f_idx on flt f2 (actual rows=2 loops=N)
Index Cond: (f <= f1.f)
- Heap Fetches: N
-(10 rows)
+(8 rows)
DROP TABLE flt;
-- Exercise Memoize in binary mode with a large fixed width type and a
@@ -176,7 +167,7 @@ SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.n >= s2.n;', false);
-> Memoize (actual rows=4 loops=N)
Cache Key: s1.n
Cache Mode: binary
- Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ Hits: 3 Misses: 3 Evictions: Zero Overflows: 0
-> Index Scan using strtest_n_idx on strtest s2 (actual rows=4 loops=N)
Index Cond: (n <= s1.n)
(8 rows)
@@ -191,7 +182,7 @@ SELECT * FROM strtest s1 INNER JOIN strtest s2 ON s1.t >= s2.t;', false);
-> Memoize (actual rows=4 loops=N)
Cache Key: s1.t
Cache Mode: binary
- Hits: 3 Misses: 3 Evictions: Zero Overflows: 0 Memory Usage: NkB
+ Hits: 3 Misses: 3 Evictions: Zero Overflows: 0
-> Index Scan using strtest_t_idx on strtest s2 (actual rows=4 loops=N)
Index Cond: (t <= s1.t)
(8 rows)
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index af670e28e7f..cebf011969d 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -1334,11 +1334,11 @@ WHEN MATCHED THEN
Merge Cond: (t.a = s.a)
-> Sort (actual rows=50 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_mtarget t (actual rows=50 loops=1)
-> Sort (actual rows=100 loops=1)
Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
(12 rows)
@@ -1355,11 +1355,11 @@ WHEN MATCHED AND t.a < 10 THEN
Merge Cond: (t.a = s.a)
-> Sort (actual rows=50 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_mtarget t (actual rows=50 loops=1)
-> Sort (actual rows=100 loops=1)
Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
(12 rows)
@@ -1378,11 +1378,11 @@ WHEN MATCHED AND t.a >= 10 AND t.a <= 20 THEN
Merge Cond: (t.a = s.a)
-> Sort (actual rows=50 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_mtarget t (actual rows=50 loops=1)
-> Sort (actual rows=100 loops=1)
Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
(12 rows)
@@ -1399,11 +1399,11 @@ WHEN NOT MATCHED AND s.a < 10 THEN
Merge Cond: (s.a = t.a)
-> Sort (actual rows=100 loops=1)
Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
-> Sort (actual rows=45 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_mtarget t (actual rows=45 loops=1)
(12 rows)
@@ -1424,11 +1424,11 @@ WHEN NOT MATCHED AND s.a < 20 THEN
Merge Cond: (s.a = t.a)
-> Sort (actual rows=100 loops=1)
Sort Key: s.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_msource s (actual rows=100 loops=1)
-> Sort (actual rows=49 loops=1)
Sort Key: t.a
- Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
-> Seq Scan on ex_mtarget t (actual rows=49 loops=1)
(12 rows)
@@ -1444,7 +1444,7 @@ WHEN MATCHED AND t.a < 10 THEN
Hash Cond: (s.a = t.a)
-> Seq Scan on ex_msource s (actual rows=1 loops=1)
-> Hash (actual rows=0 loops=1)
- Buckets: xxx Batches: xxx Memory Usage: xxx
+ Buckets: xxx Batches: xxx
-> Seq Scan on ex_mtarget t (actual rows=0 loops=1)
Filter: (a < '-1000'::integer)
Rows Removed by Filter: 54
diff --git a/src/test/regress/expected/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 7555764c779..cabadd48b81 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2479,7 +2479,6 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b2 ab_a1_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
- Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
@@ -2494,14 +2493,13 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b2 ab_2 (actual rows=1 loops=1)
Recheck Cond: (a = 1)
- Heap Blocks: exact=1
-> Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-> Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
Recheck Cond: (a = 1)
-> Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
Index Cond: (a = 1)
-(34 rows)
+(32 rows)
table ab;
a | b
diff --git a/src/test/regress/expected/select_parallel.out b/src/test/regress/expected/select_parallel.out
index 4ea1aa7dfd4..9f36c627419 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -562,24 +562,11 @@ explain (analyze, timing off, summary off, costs off)
alter table tenk2 reset (parallel_workers);
reset work_mem;
-create function explain_parallel_sort_stats() returns setof text
-language plpgsql as
-$$
-declare ln text;
-begin
- for ln in
- explain (analyze, timing off, summary off, costs off)
- select * from
+explain (analyze)
+select * from
(select ten from tenk1 where ten < 100 order by ten) ss
- right join (values (1),(2),(3)) v(x) on true
- loop
- ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
- return next ln;
- end loop;
-end;
-$$;
-select * from explain_parallel_sort_stats();
- explain_parallel_sort_stats
+ right join (values (1),(2),(3)) v(x) on true;
+ QUERY PLAN
--------------------------------------------------------------------------
Nested Loop Left Join (actual rows=30000 loops=1)
-> Values Scan on "*VALUES*" (actual rows=3 loops=1)
@@ -588,11 +575,11 @@ select * from explain_parallel_sort_stats();
Workers Launched: 4
-> Sort (actual rows=2000 loops=15)
Sort Key: tenk1.ten
- Sort Method: quicksort Memory: xxx
- Worker 0: Sort Method: quicksort Memory: xxx
- Worker 1: Sort Method: quicksort Memory: xxx
- Worker 2: Sort Method: quicksort Memory: xxx
- Worker 3: Sort Method: quicksort Memory: xxx
+ Sort Method: quicksort
+ Worker 0: Sort Method: quicksort
+ Worker 1: Sort Method: quicksort
+ Worker 2: Sort Method: quicksort
+ Worker 3: Sort Method: quicksort
-> Parallel Seq Scan on tenk1 (actual rows=2000 loops=15)
Filter: (ten < 100)
(14 rows)
@@ -603,7 +590,6 @@ reset enable_mergejoin;
reset enable_material;
reset effective_io_concurrency;
drop table bmscantest;
-drop function explain_parallel_sort_stats();
-- test parallel merge join path.
set enable_hashjoin to off;
set enable_nestloop to off;
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index 45c75eecc5f..527f04e122b 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1550,27 +1550,15 @@ insert into sq_limit values
(6, 2, 2),
(7, 3, 3),
(8, 4, 4);
-create function explain_sq_limit() returns setof text language plpgsql as
-$$
-declare ln text;
-begin
- for ln in
- explain (analyze, summary off, timing off, costs off)
- select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3
- loop
- ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
- return next ln;
- end loop;
-end;
-$$;
-select * from explain_sq_limit();
- explain_sq_limit
+explain (analyze)
+ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
+ QUERY PLAN
----------------------------------------------------------------
Limit (actual rows=3 loops=1)
-> Subquery Scan on x (actual rows=3 loops=1)
-> Sort (actual rows=3 loops=1)
Sort Key: sq_limit.c1, sq_limit.pk
- Sort Method: top-N heapsort Memory: xxx
+ Sort Method: top-N heapsort
-> Seq Scan on sq_limit (actual rows=8 loops=1)
(6 rows)
@@ -1582,7 +1570,6 @@ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
2 | 2
(3 rows)
-drop function explain_sq_limit();
drop table sq_limit;
--
-- Ensure that backward scan direction isn't propagated into
diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql
index dbb3799d5e2..d7207209d51 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -79,6 +79,22 @@ select explain_filter('explain (buffers, format json) select * from int8_tbl i8'
set track_io_timing = on;
select explain_filter('explain (analyze, buffers, format json) select * from int8_tbl i8');
set track_io_timing = off;
+-- HashAgg
+begin;
+SET work_mem='64kB';
+select explain_filter('explain (analyze) SELECT a, COUNT(1) FROM generate_series(1,9999)a GROUP BY 1');
+select explain_filter('explain (analyze, machine off) SELECT a, COUNT(1) FROM generate_series(1,9999)a GROUP BY 1');
+rollback;
+
+-- Bitmap scan
+begin;
+SET enable_indexscan=no;
+CREATE TABLE explainbitmap AS SELECT i AS a FROM generate_series(1,999) AS i;
+ANALYZE explainbitmap;
+CREATE INDEX ON explainbitmap(a);
+select explain_filter('explain (analyze) SELECT * FROM explainbitmap WHERE a<9');
+select explain_filter('explain (analyze, machine off) SELECT * FROM explainbitmap WHERE a<9');
+rollback;
-- SETTINGS option
-- We have to ignore other settings that might be imposed by the environment,
diff --git a/src/test/regress/sql/memoize.sql b/src/test/regress/sql/memoize.sql
index 0979bcdf768..5d3e37f92de 100644
--- a/src/test/regress/sql/memoize.sql
+++ b/src/test/regress/sql/memoize.sql
@@ -22,9 +22,7 @@ begin
end if;
ln := regexp_replace(ln, 'Evictions: 0', 'Evictions: Zero');
ln := regexp_replace(ln, 'Evictions: \d+', 'Evictions: N');
- ln := regexp_replace(ln, 'Memory Usage: \d+', 'Memory Usage: N');
- ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N');
- ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
+ ln := regexp_replace(ln, 'loops=\d+', 'loops=N');
return next ln;
end loop;
end;
diff --git a/src/test/regress/sql/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index f9247312484..fc586161235 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -221,23 +221,10 @@ explain (analyze, timing off, summary off, costs off)
alter table tenk2 reset (parallel_workers);
reset work_mem;
-create function explain_parallel_sort_stats() returns setof text
-language plpgsql as
-$$
-declare ln text;
-begin
- for ln in
- explain (analyze, timing off, summary off, costs off)
- select * from
+explain (analyze)
+select * from
(select ten from tenk1 where ten < 100 order by ten) ss
- right join (values (1),(2),(3)) v(x) on true
- loop
- ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
- return next ln;
- end loop;
-end;
-$$;
-select * from explain_parallel_sort_stats();
+ right join (values (1),(2),(3)) v(x) on true;
reset enable_indexscan;
reset enable_hashjoin;
@@ -245,7 +232,6 @@ reset enable_mergejoin;
reset enable_material;
reset effective_io_concurrency;
drop table bmscantest;
-drop function explain_parallel_sort_stats();
-- test parallel merge join path.
set enable_hashjoin to off;
diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql
index 94ba91f5bb3..2311c9c0ed8 100644
--- a/src/test/regress/sql/subselect.sql
+++ b/src/test/regress/sql/subselect.sql
@@ -807,26 +807,11 @@ insert into sq_limit values
(7, 3, 3),
(8, 4, 4);
-create function explain_sq_limit() returns setof text language plpgsql as
-$$
-declare ln text;
-begin
- for ln in
- explain (analyze, summary off, timing off, costs off)
- select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3
- loop
- ln := regexp_replace(ln, 'Memory: \S*', 'Memory: xxx');
- return next ln;
- end loop;
-end;
-$$;
-
-select * from explain_sq_limit();
+explain (analyze)
+ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
-drop function explain_sq_limit();
-
drop table sq_limit;
--
--
2.17.1
--LQ77YLfPrO/qF/pM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="0005-f-Rows-removed-by-filter.patch"
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
@ 2024-08-23 12:40 Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Peter Eisentraut @ 2024-08-23 12:40 UTC (permalink / raw)
To: Dean Rasheed <[email protected]>; PostgreSQL Hackers <[email protected]>
On 01.07.24 12:33, Dean Rasheed wrote:
> Attached is a patch adding support for the gamma and log-gamma
> functions. See, for example:
>
> https://en.wikipedia.org/wiki/Gamma_function
>
> I think these are very useful general-purpose mathematical functions.
> They're part of C99, and they're commonly included in other
> mathematical libraries, such as the python math module, so I think
> it's useful to make them available from SQL.
What are examples of where this would be useful in a database context?
> The error-handling for these functions seems to be a little trickier
> than most, so that might need further discussion.
Yeah, this is quite something.
I'm not sure why you are doing the testing for special values (NaN etc.)
yourself when the C library function already does it. For example, if I
remove the isnan(arg1) check in your dlgamma(), then it still behaves
the same in all tests. However, the same does not happen in your
dgamma(). The overflow checks after the C library call are written
differently for the two functions. dgamma() does not check errno for
ERANGE for example. It might also be good if dgamma() checked errno for
EDOM, because other the result of gamma(-1) being "overflow" seems a bit
wrong.
I'm also not clear why you are turning a genuine result overflow into
infinity in lgamma().
I think this could use some kind of chart or something about all the
possible behaviors and how the C library reports them and what we intend
to do with them.
Btw., I'm reading that lgamma() in the C library is not necessarily
thread-safe. Is that a possible problem?
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
@ 2024-09-04 17:55 ` Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Dean Rasheed @ 2024-09-04 17:55 UTC (permalink / raw)
To: Peter Eisentraut <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
On Fri, 23 Aug 2024 at 13:40, Peter Eisentraut <[email protected]> wrote:
>
> What are examples of where this would be useful in a database context?
gamma() and lgamma() are the kinds of functions that are generally
useful for a variety of tasks like statistical analysis and
combinatorial computations, and having them in the database allows
those sort of computations to be performed without the need to export
the data to an external tool. We discussed adding them in a thread
last year [1], and there has been at least a little prior interest
[2].
[1] https://www.postgresql.org/message-id/CA%2BhUKGKJAcB8Q5qziKTTSnkA4Mnv_6f%2B7-_XUgbh9jFjSdEFQg%40mail...
[2] https://stackoverflow.com/questions/58884066/how-can-i-run-the-equivalent-of-excels-gammaln-function...
Of course, there's a somewhat fuzzy line between what is generally
useful enough, and what is too specialised for core Postgres, but I
would argue that these qualify, since they are part of C99, and
commonly appear in other general-purpose math libraries like the
Python math module.
> > The error-handling for these functions seems to be a little trickier
> > than most, so that might need further discussion.
>
> Yeah, this is quite something.
>
> I'm not sure why you are doing the testing for special values (NaN etc.)
> yourself when the C library function already does it. For example, if I
> remove the isnan(arg1) check in your dlgamma(), then it still behaves
> the same in all tests.
It's useful to do that so that we don't need to assume that every
platform conforms to the POSIX standard, and because it simplifies the
later overflow checks. This is consistent with the approach taken in
other functions, such as dexp(), dsin(), dcos(), etc.
> The overflow checks after the C library call are written
> differently for the two functions. dgamma() does not check errno for
> ERANGE for example. It might also be good if dgamma() checked errno for
> EDOM, because other the result of gamma(-1) being "overflow" seems a bit
> wrong.
They're intentionally different because the functions themselves are
different. In this case:
select gamma(-1);
ERROR: value out of range: overflow
it is correct to throw an error, because gamma(-1) is undefined (it
goes to -Inf as x goes to -1 from above, and +Inf as x goes to -1 from
below, so there is no well-defined limit).
I've updated the patch to give a more specific error message for
negative integer inputs, as opposed to other overflow cases.
Relying on errno being ERANGE or EDOM doesn't seem possible though,
because the docs indicate that, while its behaviour is one thing
today, per POSIX, that will change in the future.
By contrast, lgamma() does not raise an error for such inputs:
select lgamma(-1);
lgamma
----------
Infinity
This is correct because lgamma() is the log of the absolute value of
the gamma function, so the limit is +Inf as x goes to -1 from both
sides.
> I'm also not clear why you are turning a genuine result overflow into
> infinity in lgamma().
OK, I've changed that to only return infinity if the input is
infinite, zero, or a negative integer. Otherwise, it now throws an
overflow error.
> Btw., I'm reading that lgamma() in the C library is not necessarily
> thread-safe. Is that a possible problem?
It's not quite clear what to do about that. Some platforms may define
the lgamma_r() function, but not all. Do we have a standard pattern
for dealing with functions for which there is no thread-safe
alternative?
Regards,
Dean
Attachments:
[text/x-patch] v2-gamma-and-lgamma.patch (7.9K, ../../CAEZATCXL4b08tgGz3_SnEewZQY1nHZSRcJji6GScM+LqcXwHUw@mail.gmail.com/2-v2-gamma-and-lgamma.patch)
download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 461fc3f..b2a3368
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1399,6 +1399,27 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FRO
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
+ <primary>gamma</primary>
+ </indexterm>
+ <function>gamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Gamma function
+ </para>
+ <para>
+ <literal>gamma(0.5)</literal>
+ <returnvalue>1.772453850905516</returnvalue>
+ </para>
+ <para>
+ <literal>gamma(6)</literal>
+ <returnvalue>120</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
<primary>gcd</primary>
</indexterm>
<function>gcd</function> ( <replaceable>numeric_type</replaceable>, <replaceable>numeric_type</replaceable> )
@@ -1436,6 +1457,23 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FRO
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>lgamma</primary>
+ </indexterm>
+ <function>lgamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Natural logarithm of the absolute value of the gamma function
+ </para>
+ <para>
+ <literal>lgamma(1000)</literal>
+ <returnvalue>5905.220423209181</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index f709c21..4694ea2
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -2787,6 +2787,97 @@ derfc(PG_FUNCTION_ARGS)
}
+/* ========== GAMMA FUNCTIONS ========== */
+
+
+/*
+ * dgamma - returns the gamma function of arg1
+ */
+Datum
+dgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /*
+ * Per the POSIX spec, return NaN if the input is NaN, +/-infinity if the
+ * input is +/-0, NaN if the input is -infinity, and infinity if the input
+ * is infinity.
+ */
+ if (isnan(arg1))
+ PG_RETURN_FLOAT8(get_float8_nan());
+ if (arg1 == 0)
+ {
+ if (signbit(arg1))
+ PG_RETURN_FLOAT8(-get_float8_infinity());
+ else
+ PG_RETURN_FLOAT8(get_float8_infinity());
+ }
+ if (isinf(arg1))
+ {
+ if (arg1 < 0)
+ PG_RETURN_FLOAT8(get_float8_nan());
+ else
+ PG_RETURN_FLOAT8(get_float8_infinity());
+ }
+
+ /*
+ * Note that the POSIX/C99 gamma function is called "tgamma", not "gamma".
+ *
+ * For negative integer inputs, it may raise a domain error or a pole
+ * error, or return NaN or +/-infinity (the POSIX spec indicates that this
+ * may change in future implementations). Try to distinguish these cases
+ * from other overflow cases, and give a more specific error message.
+ */
+ errno = 0;
+ result = tgamma(arg1);
+ if (errno != 0 || isinf(result) || isnan(result))
+ {
+ if (arg1 < 0 && floor(arg1) == arg1)
+ ereport(ERROR,
+ errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("the gamma function is undefined for negative integer inputs"));
+ float_overflow_error();
+ }
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ * dlgamma - natural logarithm of absolute value of gamma of arg1
+ */
+Datum
+dlgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /* Per the POSIX spec, return NaN if the input is NaN */
+ if (isnan(arg1))
+ PG_RETURN_FLOAT8(get_float8_nan());
+
+ errno = 0;
+ result = lgamma(arg1);
+
+ /*
+ * If an ERANGE error occurs, it means there is an overflow. This can
+ * happen if the input is 0, a negative integer, -infinity, or infinity.
+ * In each of those cases, the correct result is infinity. Otherwise, it
+ * is a genuine overflow error.
+ */
+ if (unlikely(errno == ERANGE))
+ {
+ if (isinf(arg1) || (arg1 <= 0 && floor(arg1) == arg1))
+ result = get_float8_infinity();
+ else
+ float_overflow_error();
+ }
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
/*
* =========================
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
new file mode 100644
index ff5436a..cee8398
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3490,6 +3490,13 @@
proname => 'erfc', prorettype => 'float8', proargtypes => 'float8',
prosrc => 'derfc' },
+{ oid => '8702', descr => 'gamma function',
+ proname => 'gamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dgamma' },
+{ oid => '8703', descr => 'natural logarithm of absolute value of gamma function',
+ proname => 'lgamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dlgamma' },
+
{ oid => '1618',
proname => 'interval_mul', prorettype => 'interval',
proargtypes => 'interval float8', prosrc => 'interval_mul' },
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 344d6b7..a31624b
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -830,6 +830,53 @@ FROM (VALUES (float8 '-infinity'),
(22 rows)
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (float8 '-infinity'),
+ ('-0'), (0), (0.5), (1), (2), (3), (4), (5),
+ (float8 'infinity'), (float8 'nan')) AS t(x);
+ x | gamma | lgamma
+-----------+-----------------+------------------
+ -Infinity | NaN | Infinity
+ -0 | -Infinity | Infinity
+ 0 | Infinity | Infinity
+ 0.5 | 1.7724538509055 | 0.5723649429247
+ 1 | 1 | 0
+ 2 | 1 | 0
+ 3 | 2 | 0.69314718055995
+ 4 | 6 | 1.7917594692281
+ 5 | 24 | 3.1780538303479
+ Infinity | Infinity | Infinity
+ NaN | NaN | NaN
+(11 rows)
+
+-- invalid inputs for gamma()
+SELECT gamma(-1);
+ERROR: the gamma function is undefined for negative integer inputs
+SELECT gamma(1000000);
+ERROR: value out of range: overflow
+-- valid for lgamma()
+SELECT lgamma(-1);
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT lgamma(1000000);
+ lgamma
+-----------------
+ 12815504.569148
+(1 row)
+
+-- inavlid input for lgamma()
+SELECT lgamma(1e308);
+ERROR: value out of range: overflow
+RESET extra_float_digits;
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
ERROR: "10e400" is out of range for type double precision
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 98e9926..de2656a
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -245,6 +245,27 @@ FROM (VALUES (float8 '-infinity'),
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (float8 '-infinity'),
+ ('-0'), (0), (0.5), (1), (2), (3), (4), (5),
+ (float8 'infinity'), (float8 'nan')) AS t(x);
+-- invalid inputs for gamma()
+SELECT gamma(-1);
+SELECT gamma(1000000);
+-- valid for lgamma()
+SELECT lgamma(-1);
+SELECT lgamma(1000000);
+-- inavlid input for lgamma()
+SELECT lgamma(1e308);
+
+RESET extra_float_digits;
+
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
@ 2024-09-04 18:21 ` Tom Lane <[email protected]>
2024-09-04 18:34 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
0 siblings, 2 replies; 20+ messages in thread
From: Tom Lane @ 2024-09-04 18:21 UTC (permalink / raw)
To: Dean Rasheed <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>
Dean Rasheed <[email protected]> writes:
> On Fri, 23 Aug 2024 at 13:40, Peter Eisentraut <[email protected]> wrote:
>> What are examples of where this would be useful in a database context?
> Of course, there's a somewhat fuzzy line between what is generally
> useful enough, and what is too specialised for core Postgres, but I
> would argue that these qualify, since they are part of C99, and
> commonly appear in other general-purpose math libraries like the
> Python math module.
Yeah, I think any math function that's part of C99 or POSIX is
arguably of general interest.
>> I'm not sure why you are doing the testing for special values (NaN etc.)
>> yourself when the C library function already does it. For example, if I
>> remove the isnan(arg1) check in your dlgamma(), then it still behaves
>> the same in all tests.
> It's useful to do that so that we don't need to assume that every
> platform conforms to the POSIX standard, and because it simplifies the
> later overflow checks. This is consistent with the approach taken in
> other functions, such as dexp(), dsin(), dcos(), etc.
dexp() and those other functions date from the late stone age, before
it was safe to assume that libm's behavior matched the POSIX specs.
Today I think we can assume that, at least till proven differently.
There's not necessarily anything wrong with what you've coded, but
I don't buy this argument for it.
>> Btw., I'm reading that lgamma() in the C library is not necessarily
>> thread-safe. Is that a possible problem?
> It's not quite clear what to do about that.
Per the Linux man page, the reason lgamma() isn't thread-safe is
The lgamma(), lgammaf(), and lgammal() functions return the natural
logarithm of the absolute value of the Gamma function. The sign of the
Gamma function is returned in the external integer signgam declared in
<math.h>. It is 1 when the Gamma function is positive or zero, -1 when
it is negative.
AFAICS this patch doesn't inspect signgam, so whether it gets
overwritten by a concurrent thread wouldn't matter. However,
it'd be a good idea to add a comment noting the hazard.
(Presumably, the reason POSIX says "need not be thread-safe"
is that an implementation could make it thread-safe by
making signgam thread-local, but the standard doesn't wish
to mandate that.)
regards, tom lane
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
@ 2024-09-04 18:34 ` Tom Lane <[email protected]>
1 sibling, 0 replies; 20+ messages in thread
From: Tom Lane @ 2024-09-04 18:34 UTC (permalink / raw)
To: Dean Rasheed <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>
I wrote:
> AFAICS this patch doesn't inspect signgam, so whether it gets
> overwritten by a concurrent thread wouldn't matter. However,
> it'd be a good idea to add a comment noting the hazard.
Further to that ... I looked at POSIX issue 8 (I had been reading 7)
and found this illuminating discussion:
Earlier versions of this standard did not require lgamma(),
lgammaf(), and lgammal() to be thread-safe because signgam was a
global variable. They are now required to be thread-safe to align
with the ISO C standard (which, since the introduction of threads
in 2011, requires that they avoid data races), with the exception
that they need not avoid data races when storing a value in the
signgam variable. Since signgam is not specified by the ISO C
standard, this exception is not a conflict with that standard.
So the other reason to avoid using signgam is that it might
not exist at all in some libraries.
regards, tom lane
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
@ 2024-09-06 09:42 ` Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
1 sibling, 1 reply; 20+ messages in thread
From: Dean Rasheed @ 2024-09-06 09:42 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, 4 Sept 2024 at 19:21, Tom Lane <[email protected]> wrote:
>
> >> I'm not sure why you are doing the testing for special values (NaN etc.)
> >> yourself when the C library function already does it. For example, if I
> >> remove the isnan(arg1) check in your dlgamma(), then it still behaves
> >> the same in all tests.
>
> > It's useful to do that so that we don't need to assume that every
> > platform conforms to the POSIX standard, and because it simplifies the
> > later overflow checks. This is consistent with the approach taken in
> > other functions, such as dexp(), dsin(), dcos(), etc.
>
> dexp() and those other functions date from the late stone age, before
> it was safe to assume that libm's behavior matched the POSIX specs.
> Today I think we can assume that, at least till proven differently.
> There's not necessarily anything wrong with what you've coded, but
> I don't buy this argument for it.
>
OK, thinking about this some more, I think we should reserve overflow
errors for genuine overflows, which I take to mean cases where the
exact mathematical result should be finite, but is too large to be
represented in a double.
In particular, this means that zero and negative integer inputs are
not genuine overflows, but should return NaN or +/-Inf, as described
in the POSIX spec.
Doing that, and assuming that tgamma() and lgamma() behave according
to spec, leads to the attached, somewhat simpler patch.
Regards,
Dean
Attachments:
[text/x-patch] v3-gamma-and-lgamma.patch (8.0K, ../../CAEZATCX5Bj6M=RACfyF973SiDim7iqk_JgEuOqkLQZccZ+=5Tg@mail.gmail.com/2-v3-gamma-and-lgamma.patch)
download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 461fc3f..b2a3368
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1399,6 +1399,27 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FRO
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
+ <primary>gamma</primary>
+ </indexterm>
+ <function>gamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Gamma function
+ </para>
+ <para>
+ <literal>gamma(0.5)</literal>
+ <returnvalue>1.772453850905516</returnvalue>
+ </para>
+ <para>
+ <literal>gamma(6)</literal>
+ <returnvalue>120</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
<primary>gcd</primary>
</indexterm>
<function>gcd</function> ( <replaceable>numeric_type</replaceable>, <replaceable>numeric_type</replaceable> )
@@ -1436,6 +1457,23 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FRO
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>lgamma</primary>
+ </indexterm>
+ <function>lgamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Natural logarithm of the absolute value of the gamma function
+ </para>
+ <para>
+ <literal>lgamma(1000)</literal>
+ <returnvalue>5905.220423209181</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index f709c21..00da60b
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -2787,6 +2787,63 @@ derfc(PG_FUNCTION_ARGS)
}
+/* ========== GAMMA FUNCTIONS ========== */
+
+
+/*
+ * dgamma - returns the gamma function of arg1
+ */
+Datum
+dgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /* note: the POSIX/C99 gamma function is called "tgamma", not "gamma" */
+ errno = 0;
+ result = tgamma(arg1);
+
+ /*
+ * If an ERANGE error occurs, it means there is an overflow. This may also
+ * happen if the input is +/-0, which is not a genuine overflow, and the
+ * result should be +/-infinity.
+ */
+ if (errno == ERANGE && arg1 != 0)
+ float_overflow_error();
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ * dlgamma - natural logarithm of absolute value of gamma of arg1
+ */
+Datum
+dlgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /*
+ * Note: lgamma may not be thread-safe because it may write to a global
+ * variable signgam, which may not be thread-local. However, this doesn't
+ * matter to us, since we don't use signgam.
+ */
+ errno = 0;
+ result = lgamma(arg1);
+
+ /*
+ * If an ERANGE error occurs, it means there is an overflow. This also
+ * happens if the input is zero or a negative integer, which are not
+ * genuine overflows, and the result should be infinity.
+ */
+ if (errno == ERANGE && !(arg1 <= 0 && floor(arg1) == arg1))
+ float_overflow_error();
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
/*
* =========================
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
new file mode 100644
index ff5436a..cee8398
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3490,6 +3490,13 @@
proname => 'erfc', prorettype => 'float8', proargtypes => 'float8',
prosrc => 'derfc' },
+{ oid => '8702', descr => 'gamma function',
+ proname => 'gamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dgamma' },
+{ oid => '8703', descr => 'natural logarithm of absolute value of gamma function',
+ proname => 'lgamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dlgamma' },
+
{ oid => '1618',
proname => 'interval_mul', prorettype => 'interval',
proargtypes => 'interval float8', prosrc => 'interval_mul' },
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 344d6b7..d7052e3
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -830,6 +830,128 @@ FROM (VALUES (float8 '-infinity'),
(22 rows)
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (0.5), (1), (2), (3), (4), (5)) AS t(x);
+ x | gamma | lgamma
+-----+-----------------+------------------
+ 0.5 | 1.7724538509055 | 0.5723649429247
+ 1 | 1 | 0
+ 2 | 1 | 0
+ 3 | 2 | 0.69314718055995
+ 4 | 6 | 1.7917594692281
+ 5 | 24 | 3.1780538303479
+(6 rows)
+
+-- test special cases for gamma functions
+SELECT gamma(float8 '-infinity');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 '-infinity');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-1000000.5');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '-1000000.5');
+ lgamma
+------------------
+ -12815524.147684
+(1 row)
+
+SELECT gamma(float8 '-1000000');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 '-1000000');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-1');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 '-1');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-0');
+ gamma
+-----------
+ -Infinity
+(1 row)
+
+SELECT lgamma(float8 '-0');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '0');
+ gamma
+----------
+ Infinity
+(1 row)
+
+SELECT lgamma(float8 '0');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '1000000');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '1000000');
+ lgamma
+-----------------
+ 12815504.569148
+(1 row)
+
+SELECT lgamma(float8 '1e308');
+ERROR: value out of range: overflow
+SELECT gamma(float8 'infinity');
+ gamma
+----------
+ Infinity
+(1 row)
+
+SELECT lgamma(float8 'infinity');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 'nan');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 'nan');
+ lgamma
+--------
+ NaN
+(1 row)
+
+RESET extra_float_digits;
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
ERROR: "10e400" is out of range for type double precision
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 98e9926..36fd7b8
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -245,6 +245,36 @@ FROM (VALUES (float8 '-infinity'),
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (0.5), (1), (2), (3), (4), (5)) AS t(x);
+-- test special cases for gamma functions
+SELECT gamma(float8 '-infinity');
+SELECT lgamma(float8 '-infinity');
+SELECT gamma(float8 '-1000000.5');
+SELECT lgamma(float8 '-1000000.5');
+SELECT gamma(float8 '-1000000');
+SELECT lgamma(float8 '-1000000');
+SELECT gamma(float8 '-1');
+SELECT lgamma(float8 '-1');
+SELECT gamma(float8 '-0');
+SELECT lgamma(float8 '-0');
+SELECT gamma(float8 '0');
+SELECT lgamma(float8 '0');
+SELECT gamma(float8 '1000000');
+SELECT lgamma(float8 '1000000');
+SELECT lgamma(float8 '1e308');
+SELECT gamma(float8 'infinity');
+SELECT lgamma(float8 'infinity');
+SELECT gamma(float8 'nan');
+SELECT lgamma(float8 'nan');
+RESET extra_float_digits;
+
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
@ 2024-09-06 11:58 ` Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Dean Rasheed @ 2024-09-06 11:58 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Peter Eisentraut <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, 6 Sept 2024 at 10:42, Dean Rasheed <[email protected]> wrote:
>
> ... assuming that tgamma() and lgamma() behave according to spec ...
Nope, that was too much to hope for. Let's see if this fares any better.
Regards,
Dean
Attachments:
[text/x-patch] v4-gamma-and-lgamma.patch (8.6K, ../../CAEZATCXL6SBvFeN_WeWFGbkS_cGSS4R+vkEoo626iuMFFSXYzg@mail.gmail.com/2-v4-gamma-and-lgamma.patch)
download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
new file mode 100644
index 461fc3f..b2a3368
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1399,6 +1399,27 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FRO
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
+ <primary>gamma</primary>
+ </indexterm>
+ <function>gamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Gamma function
+ </para>
+ <para>
+ <literal>gamma(0.5)</literal>
+ <returnvalue>1.772453850905516</returnvalue>
+ </para>
+ <para>
+ <literal>gamma(6)</literal>
+ <returnvalue>120</returnvalue>
+ </para></entry>
+ </row>
+
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
<primary>gcd</primary>
</indexterm>
<function>gcd</function> ( <replaceable>numeric_type</replaceable>, <replaceable>numeric_type</replaceable> )
@@ -1436,6 +1457,23 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FRO
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>lgamma</primary>
+ </indexterm>
+ <function>lgamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Natural logarithm of the absolute value of the gamma function
+ </para>
+ <para>
+ <literal>lgamma(1000)</literal>
+ <returnvalue>5905.220423209181</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
new file mode 100644
index f709c21..6e3252e
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -2787,6 +2787,80 @@ derfc(PG_FUNCTION_ARGS)
}
+/* ========== GAMMA FUNCTIONS ========== */
+
+
+/*
+ * dgamma - returns the gamma function of arg1
+ */
+Datum
+dgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /* note: the POSIX/C99 gamma function is called "tgamma", not "gamma" */
+ errno = 0;
+ result = tgamma(arg1);
+
+ /*
+ * If an ERANGE error occurs, it means there is an overflow. This may also
+ * happen if the input is +/-0, which is not a genuine overflow, and the
+ * result should be +/-infinity.
+ *
+ * On some platforms, tgamma() will not set errno but just return infinity
+ * or zero to report overflow/underflow; therefore, test both cases (note
+ * that, like the exponential function, the gamma function has no zeros).
+ */
+ if (errno == ERANGE && arg1 != 0)
+ {
+ if (result != 0.0)
+ float_overflow_error();
+ else
+ float_underflow_error();
+ }
+ else if (isinf(result) && arg1 != 0 && !isinf(arg1))
+ float_overflow_error();
+ else if (result == 0.0)
+ float_underflow_error();
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ * dlgamma - natural logarithm of absolute value of gamma of arg1
+ */
+Datum
+dlgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /*
+ * Note: lgamma may not be thread-safe because it may write to a global
+ * variable signgam, which may not be thread-local. However, this doesn't
+ * matter to us, since we don't use signgam.
+ */
+ errno = 0;
+ result = lgamma(arg1);
+
+ /*
+ * If an ERANGE error occurs, it means there is an overflow. This also
+ * happens if the input is zero or a negative integer, which are not
+ * genuine overflows, and the result should be infinity.
+ *
+ * On some platforms, lgamma() will not set errno but just return infinity
+ * to report overflow, but it should never underflow.
+ */
+ if ((errno == ERANGE || isinf(result)) && !isinf(arg1) &&
+ !(arg1 <= 0 && floor(arg1) == arg1))
+ float_overflow_error();
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
/*
* =========================
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
new file mode 100644
index ff5436a..cee8398
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3490,6 +3490,13 @@
proname => 'erfc', prorettype => 'float8', proargtypes => 'float8',
prosrc => 'derfc' },
+{ oid => '8702', descr => 'gamma function',
+ proname => 'gamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dgamma' },
+{ oid => '8703', descr => 'natural logarithm of absolute value of gamma function',
+ proname => 'lgamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dlgamma' },
+
{ oid => '1618',
proname => 'interval_mul', prorettype => 'interval',
proargtypes => 'interval float8', prosrc => 'interval_mul' },
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
new file mode 100644
index 344d6b7..5c91ef1
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -830,6 +830,128 @@ FROM (VALUES (float8 '-infinity'),
(22 rows)
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (0.5), (1), (2), (3), (4), (5)) AS t(x);
+ x | gamma | lgamma
+-----+-----------------+------------------
+ 0.5 | 1.7724538509055 | 0.5723649429247
+ 1 | 1 | 0
+ 2 | 1 | 0
+ 3 | 2 | 0.69314718055995
+ 4 | 6 | 1.7917594692281
+ 5 | 24 | 3.1780538303479
+(6 rows)
+
+-- test special cases for gamma functions
+SELECT gamma(float8 '-infinity');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 '-infinity');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-1000000.5');
+ERROR: value out of range: underflow
+SELECT lgamma(float8 '-1000000.5');
+ lgamma
+------------------
+ -12815524.147684
+(1 row)
+
+SELECT gamma(float8 '-1000000');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 '-1000000');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-1');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 '-1');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-0');
+ gamma
+-----------
+ -Infinity
+(1 row)
+
+SELECT lgamma(float8 '-0');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '0');
+ gamma
+----------
+ Infinity
+(1 row)
+
+SELECT lgamma(float8 '0');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '1000000');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '1000000');
+ lgamma
+-----------------
+ 12815504.569148
+(1 row)
+
+SELECT lgamma(float8 '1e308');
+ERROR: value out of range: overflow
+SELECT gamma(float8 'infinity');
+ gamma
+----------
+ Infinity
+(1 row)
+
+SELECT lgamma(float8 'infinity');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 'nan');
+ gamma
+-------
+ NaN
+(1 row)
+
+SELECT lgamma(float8 'nan');
+ lgamma
+--------
+ NaN
+(1 row)
+
+RESET extra_float_digits;
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
ERROR: "10e400" is out of range for type double precision
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
new file mode 100644
index 98e9926..36fd7b8
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -245,6 +245,36 @@ FROM (VALUES (float8 '-infinity'),
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (0.5), (1), (2), (3), (4), (5)) AS t(x);
+-- test special cases for gamma functions
+SELECT gamma(float8 '-infinity');
+SELECT lgamma(float8 '-infinity');
+SELECT gamma(float8 '-1000000.5');
+SELECT lgamma(float8 '-1000000.5');
+SELECT gamma(float8 '-1000000');
+SELECT lgamma(float8 '-1000000');
+SELECT gamma(float8 '-1');
+SELECT lgamma(float8 '-1');
+SELECT gamma(float8 '-0');
+SELECT lgamma(float8 '-0');
+SELECT gamma(float8 '0');
+SELECT lgamma(float8 '0');
+SELECT gamma(float8 '1000000');
+SELECT lgamma(float8 '1000000');
+SELECT lgamma(float8 '1e308');
+SELECT gamma(float8 'infinity');
+SELECT lgamma(float8 'infinity');
+SELECT gamma(float8 'nan');
+SELECT lgamma(float8 'nan');
+RESET extra_float_digits;
+
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
@ 2024-11-14 16:28 ` Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Dmitry Koval @ 2024-11-14 16:28 UTC (permalink / raw)
To: [email protected]
Hi!
I think having gamma() and lgamma() functions in PostgreSQL would be
useful for some users, this is asked [1].
I have a question regarding the current implementation of gamma()
function. Code block:
+ if (errno == ERANGE && arg1 != 0)
+ {
+ if (result != 0.0)
+ float_overflow_error();
+ else
+ float_underflow_error();
+ }
+ else if (isinf(result) && arg1 != 0 && !isinf(arg1))
+ float_overflow_error();
+ else if (result == 0.0)
+ float_underflow_error();
Why in some cases (if arg1 is close to 0, but not 0) an error
(float_overflow_error) will be returned, but in the case of "arg1 = 0"
the value 'Infinity' will be returned?
For example:
>SELECT gamma(float8 '1e-320');
ERROR: value out of range: overflow
>SELECT gamma(float8 '0');
gamma
----------
Infinity
(1 row)
Perhaps it would be logical if the behavior in these cases was the same
(either ERROR or 'Infinity')?
Links:
[1]
https://stackoverflow.com/questions/58884066/how-can-i-run-the-equivalent-of-excels-gammaln-function...
--
With best regards,
Dmitry Koval
Postgres Professional: http://postgrespro.com
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
@ 2024-11-14 22:35 ` Dean Rasheed <[email protected]>
2025-03-02 10:29 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Dean Rasheed @ 2024-11-14 22:35 UTC (permalink / raw)
To: Dmitry Koval <[email protected]>; +Cc: [email protected]
On Thu, 14 Nov 2024 at 16:28, Dmitry Koval <[email protected]> wrote:
>
> I think having gamma() and lgamma() functions in PostgreSQL would be
> useful for some users, this is asked [1].
>
Thanks for looking.
> >SELECT gamma(float8 '1e-320');
> ERROR: value out of range: overflow
>
> >SELECT gamma(float8 '0');
> gamma
> ----------
> Infinity
> (1 row)
>
That's correct since gamma(1e-320) is roughly 1e320, which overflows
the double precision type, but it's not actually infinite, whereas
gamma(0) is infinite.
> Perhaps it would be logical if the behavior in these cases was the same
> (either ERROR or 'Infinity')?
>
In general, I think that having gamma() throw overflow errors is
useful for spotting real problems in user code. In my experience, it's
uncommon to pass negative or even close-to-zero inputs to gamma(), but
it is common to fail to appreciate just how quickly the result grows
for positive inputs (it overflows for inputs just over 171.6), so it
seems better to be made aware of such problems (which might be solved
by using lgamma() instead).
So I think throwing overflow errors is the most useful thing to do
from a practical point of view, and if we do that for too-large
inputs, it makes sense to do the same for too-close-to-zero inputs.
OTOH, gamma(+/-0) = +/-Infinity is right from a mathematical point of
view, and consistent with the POSIX spec, and it's also consistent
with the functions cot() and cotd().
Regards,
Dean
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
@ 2025-03-02 10:29 ` Dean Rasheed <[email protected]>
2025-03-25 05:01 ` Re: gamma() and lgamma() functions Alexandra Wang <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Dean Rasheed @ 2025-03-02 10:29 UTC (permalink / raw)
To: Dmitry Koval <[email protected]>; +Cc: [email protected]
On Thu, 14 Nov 2024 at 22:35, Dean Rasheed <[email protected]> wrote:
>
> On Thu, 14 Nov 2024 at 16:28, Dmitry Koval <[email protected]> wrote:
> >
> > >SELECT gamma(float8 '1e-320');
> > ERROR: value out of range: overflow
> >
> > >SELECT gamma(float8 '0');
> > gamma
> > ----------
> > Infinity
> > (1 row)
> >
> > Perhaps it would be logical if the behavior in these cases was the same
> > (either ERROR or 'Infinity')?
>
> In general, I think that having gamma() throw overflow errors is
> useful for spotting real problems in user code.
>
Thinking about this afresh, I remain of the opinion that having the
gamma function throw overflow errors is useful for inputs that are too
large, like gamma(200). But then, if we're going to do that, maybe we
should just do the same for other invalid inputs (zero, negative
integers, and -Inf). That resolves the consistency issue with inputs
very close to zero, and seems like a practical solution.
Attached is an update doing that.
Regards,
Dean
Attachments:
[text/x-patch] v5-0001-Add-support-for-gamma-and-lgamma-functions.patch (9.0K, ../../CAEZATCWm7NdTiCN9CbaVEkgcEb0UT7enxnxwck5BHvVR28658g@mail.gmail.com/2-v5-0001-Add-support-for-gamma-and-lgamma-functions.patch)
download | inline diff:
From 4613ddc7267343e74703991267a1adb45eff3403 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <[email protected]>
Date: Sun, 2 Mar 2025 09:23:20 +0000
Subject: [PATCH v5] Add support for gamma() and lgamma() functions.
These are useful general-purpose math functions which are included in
POSIX and C99, and are commonly included in other math libraries, so
expose them as SQL-callable functions.
Author: Dean Rasheed <[email protected]>
Reviewed-by: Stepan Neretin <[email protected]>
Reviewed-by: Tom Lane <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: Dmitry Koval <[email protected]>
Discussion: https://postgr.es/m/CAEZATCXpGyfjXCirFk9au+FvM0y2Ah+2-0WSJx7MO368ysNUPA@mail.gmail.com
---
doc/src/sgml/func.sgml | 38 ++++++++++++
src/backend/utils/adt/float.c | 87 ++++++++++++++++++++++++++++
src/include/catalog/pg_proc.dat | 7 +++
src/test/regress/expected/float8.out | 57 ++++++++++++++++++
src/test/regress/sql/float8.sql | 23 ++++++++
5 files changed, 212 insertions(+)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0e6c534965..143031e03e 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1396,6 +1396,27 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>gamma</primary>
+ </indexterm>
+ <function>gamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Gamma function
+ </para>
+ <para>
+ <literal>gamma(0.5)</literal>
+ <returnvalue>1.772453850905516</returnvalue>
+ </para>
+ <para>
+ <literal>gamma(6)</literal>
+ <returnvalue>120</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
@@ -1436,6 +1457,23 @@ SELECT NOT(ROW(table.*) IS NOT NULL) FROM TABLE; -- detect at least one null in
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>lgamma</primary>
+ </indexterm>
+ <function>lgamma</function> ( <type>double precision</type> )
+ <returnvalue>double precision</returnvalue>
+ </para>
+ <para>
+ Natural logarithm of the absolute value of the gamma function
+ </para>
+ <para>
+ <literal>lgamma(1000)</literal>
+ <returnvalue>5905.220423209181</returnvalue>
+ </para></entry>
+ </row>
+
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
diff --git a/src/backend/utils/adt/float.c b/src/backend/utils/adt/float.c
index 2bc31eabf2..a46ebffae4 100644
--- a/src/backend/utils/adt/float.c
+++ b/src/backend/utils/adt/float.c
@@ -2786,6 +2786,93 @@ derfc(PG_FUNCTION_ARGS)
}
+/* ========== GAMMA FUNCTIONS ========== */
+
+
+/*
+ * dgamma - returns the gamma function of arg1
+ */
+Datum
+dgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /*
+ * Handle NaN and Inf cases explicitly. This simplifies the overflow
+ * checks on platforms that do not set errno.
+ */
+ if (isnan(arg1))
+ result = arg1;
+ else if (isinf(arg1))
+ {
+ /* Per POSIX, an input of -Inf causes a domain error */
+ if (arg1 < 0)
+ {
+ float_overflow_error();
+ result = get_float8_nan(); /* keep compiler quiet */
+ }
+ else
+ result = arg1;
+ }
+ else
+ {
+ /*
+ * Note: the POSIX/C99 gamma function is called "tgamma", not "gamma".
+ *
+ * On some platforms, tgamma() will not set errno but just return Inf,
+ * NaN, or zero to report overflow/underflow; therefore, test those
+ * cases explicitly (note that, like the exponential function, the
+ * gamma function has no zeros).
+ */
+ errno = 0;
+ result = tgamma(arg1);
+
+ if (errno != 0 || isinf(result) || isnan(result))
+ {
+ if (result != 0.0)
+ float_overflow_error();
+ else
+ float_underflow_error();
+ }
+ else if (result == 0.0)
+ float_underflow_error();
+ }
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
+/*
+ * dlgamma - natural logarithm of absolute value of gamma of arg1
+ */
+Datum
+dlgamma(PG_FUNCTION_ARGS)
+{
+ float8 arg1 = PG_GETARG_FLOAT8(0);
+ float8 result;
+
+ /*
+ * Note: lgamma may not be thread-safe because it may write to a global
+ * variable signgam, which may not be thread-local. However, this doesn't
+ * matter to us, since we don't use signgam.
+ */
+ errno = 0;
+ result = lgamma(arg1);
+
+ /*
+ * If an ERANGE error occurs, it means there is an overflow.
+ *
+ * On some platforms, lgamma() will not set errno but just return infinity
+ * to report overflow, but it should never underflow.
+ */
+ if (errno == ERANGE || (isinf(result) && !isinf(arg1)))
+ float_overflow_error();
+
+ PG_RETURN_FLOAT8(result);
+}
+
+
/*
* =========================
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index cd9422d0ba..2149fa4281 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -3542,6 +3542,13 @@
proname => 'erfc', prorettype => 'float8', proargtypes => 'float8',
prosrc => 'derfc' },
+{ oid => '8702', descr => 'gamma function',
+ proname => 'gamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dgamma' },
+{ oid => '8703', descr => 'natural logarithm of absolute value of gamma function',
+ proname => 'lgamma', prorettype => 'float8', proargtypes => 'float8',
+ prosrc => 'dlgamma' },
+
{ oid => '1618',
proname => 'interval_mul', prorettype => 'interval',
proargtypes => 'interval float8', prosrc => 'interval_mul' },
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 9ef9793fe9..10a5a6e1b6 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -829,6 +829,63 @@ FROM (VALUES (float8 '-infinity'),
NaN | NaN | NaN
(22 rows)
+RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (0.5), (1), (2), (3), (4), (5),
+ (float8 'infinity'), (float8 'nan')) AS t(x);
+ x | gamma | lgamma
+----------+-----------------+------------------
+ 0.5 | 1.7724538509055 | 0.5723649429247
+ 1 | 1 | 0
+ 2 | 1 | 0
+ 3 | 2 | 0.69314718055995
+ 4 | 6 | 1.7917594692281
+ 5 | 24 | 3.1780538303479
+ Infinity | Infinity | Infinity
+ NaN | NaN | NaN
+(8 rows)
+
+-- test overflow/underflow handling
+SELECT gamma(float8 '-infinity');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '-infinity');
+ lgamma
+----------
+ Infinity
+(1 row)
+
+SELECT gamma(float8 '-1000.5');
+ERROR: value out of range: underflow
+SELECT lgamma(float8 '-1000.5');
+ lgamma
+------------------
+ -5914.4377011169
+(1 row)
+
+SELECT gamma(float8 '-1');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '-1');
+ERROR: value out of range: overflow
+SELECT gamma(float8 '0');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '0');
+ERROR: value out of range: overflow
+SELECT gamma(float8 '1000');
+ERROR: value out of range: overflow
+SELECT lgamma(float8 '1000');
+ lgamma
+-----------------
+ 5905.2204232092
+(1 row)
+
+SELECT lgamma(float8 '1e308');
+ERROR: value out of range: overflow
RESET extra_float_digits;
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index 81a35e0bf1..db8d5724c2 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -245,6 +245,29 @@ FROM (VALUES (float8 '-infinity'),
RESET extra_float_digits;
+-- gamma functions
+-- we run these with extra_float_digits = -1, to get consistently rounded
+-- results on all platforms.
+SET extra_float_digits = -1;
+SELECT x,
+ gamma(x),
+ lgamma(x)
+FROM (VALUES (0.5), (1), (2), (3), (4), (5),
+ (float8 'infinity'), (float8 'nan')) AS t(x);
+-- test overflow/underflow handling
+SELECT gamma(float8 '-infinity');
+SELECT lgamma(float8 '-infinity');
+SELECT gamma(float8 '-1000.5');
+SELECT lgamma(float8 '-1000.5');
+SELECT gamma(float8 '-1');
+SELECT lgamma(float8 '-1');
+SELECT gamma(float8 '0');
+SELECT lgamma(float8 '0');
+SELECT gamma(float8 '1000');
+SELECT lgamma(float8 '1000');
+SELECT lgamma(float8 '1e308');
+RESET extra_float_digits;
+
-- test for over- and underflow
INSERT INTO FLOAT8_TBL(f1) VALUES ('10e400');
--
2.43.0
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-02 10:29 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
@ 2025-03-25 05:01 ` Alexandra Wang <[email protected]>
2025-03-26 09:49 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Alexandra Wang @ 2025-03-25 05:01 UTC (permalink / raw)
To: Dean Rasheed <[email protected]>; +Cc: Dmitry Koval <[email protected]>; [email protected]
Hi Dean,
Thanks for the patch!
I reviewed the patch and it looks good to me. I’ve run some tests, and
everything works as expected.
I have one minor thought:
On Sun, Mar 2, 2025 at 2:30 AM Dean Rasheed <[email protected]>
wrote:
> On Thu, 14 Nov 2024 at 22:35, Dean Rasheed <[email protected]>
> wrote:
> >
> > On Thu, 14 Nov 2024 at 16:28, Dmitry Koval <[email protected]>
> wrote:
> > >
> > > >SELECT gamma(float8 '1e-320');
> > > ERROR: value out of range: overflow
> > >
> > > >SELECT gamma(float8 '0');
> > > gamma
> > > ----------
> > > Infinity
> > > (1 row)
> > >
> > > Perhaps it would be logical if the behavior in these cases was the same
> > > (either ERROR or 'Infinity')?
> >
> > In general, I think that having gamma() throw overflow errors is
> > useful for spotting real problems in user code.
> >
>
> Thinking about this afresh, I remain of the opinion that having the
> gamma function throw overflow errors is useful for inputs that are too
> large, like gamma(200). But then, if we're going to do that, maybe we
> should just do the same for other invalid inputs (zero, negative
> integers, and -Inf). That resolves the consistency issue with inputs
> very close to zero, and seems like a practical solution.
>
Raising an error instead of silently returning Inf for invalid inputs
like zero, negative integers, and -Inf makes sense to me.
I also wondered whether we should distinguish domain errors
(e.g., zero or negative integers) from range errors (e.g., overflow).
I tested tgamma() and lgamma() on Ubuntu 24.04, macOS 15.3.2, and
Windows 11 (code attached).
Here’s a summary of return values, errnos, and exceptions on these
platforms: (errno=33 is EDOM and errno=34 is ERANGE)
tgamma(0):
Ubuntu : inf, errno=34, FE_DIVBYZERO
macOS : inf, errno=0, FE_DIVBYZERO
Windows: inf, errno=34, FE_DIVBYZERO
lgamma(0):
Ubuntu : inf, errno=34, FE_DIVBYZERO
macOS : inf, errno=0
Windows: inf, errno=34, FE_DIVBYZERO
tgamma(-1):
Ubuntu : nan, errno=33, FE_INVALID
macOS : nan, errno=0, FE_INVALID
Windows: nan, errno=33, FE_INVALID
lgamma(-1):
Ubuntu : inf, errno=34, FE_DIVBYZERO
macOS : inf, errno=0, FE_DIVBYZERO
Windows: inf, errno=34, FE_DIVBYZERO
tgamma(-1000.5):
Ubuntu : -0.0, errno=34, FE_UNDERFLOW
macOS : -0.0, errno=0
Windows: 0.0, errno=34
lgamma(-1000.5):
Ubuntu : -5914.44, errno=0
macOS : -5914.44, errno=0
Windows: -5914.44, errno=0
tgamma(1000):
Ubuntu : inf, errno=34, FE_OVERFLOW
macOS : inf, errno=0, FE_OVERFLOW
Windows: inf, errno=34, FE_OVERFLOW
lgamma(1000):
Ubuntu : 5905.22, errno=0
macOS : 5905.22, errno=0
Windows: 5905.22, errno=0
tgamma(+inf):
Ubuntu : inf, errno=0
macOS : inf, errno=0
Windows: inf, errno=0
lgamma(+inf):
Ubuntu : inf, errno=0
macOS : inf, errno=0
Windows: inf, errno=0
tgamma(-inf):
Ubuntu : nan, errno=33, FE_INVALID
macOS : nan, errno=0, FE_INVALID
Windows: nan, errno=33, FE_INVALID
lgamma(-inf):
Ubuntu : inf, errno=0
macOS : inf, errno=0
Windows: inf, errno=0
In the current implementation, float_overflow_error() is used for both
domain and range (overflow) errors. If we did want to distinguish
them, maybe we could use float_zero_divide_error() for domain errors?
Not sure it adds much value, though - just a thought, and I’m fine
either way.
OTOH I wondered if we could leverage fetestexcept(), but then I saw
this comment in dcos():
* check for errors. POSIX allows an error to be reported either via
> * errno or via fetestexcept(), but currently we only support checking
> * errno. (fetestexcept() is rumored to report underflow unreasonably
> * early on some platforms, so it's not clear that believing it would be a
> * net improvement anyway.)
So again, I’m totally fine with keeping the current semantics as-is.
That said, it might be helpful to update the comment in dlgamma():
+ /*
+ * If an ERANGE error occurs, it means there is an overflow.
+ *
to clarify that an ERANGE error can also indicate a pole error (e.g.,
input 0 or a negative integer), not just overflow.
Thanks,
Alex
Attachments:
[application/octet-stream] gamma_test.no-cfbot (1.6K, ../../CAK98qZ1g2YPHPbioD4PgHWe5Jgs7JuUEBMEmYbB5_1fgZ2X7_g@mail.gmail.com/3-gamma_test.no-cfbot)
download
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-02 10:29 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-25 05:01 ` Re: gamma() and lgamma() functions Alexandra Wang <[email protected]>
@ 2025-03-26 09:49 ` Dean Rasheed <[email protected]>
2025-03-26 11:58 ` Re: gamma() and lgamma() functions Marcos Pegoraro <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Dean Rasheed @ 2025-03-26 09:49 UTC (permalink / raw)
To: Alexandra Wang <[email protected]>; +Cc: Dmitry Koval <[email protected]>; [email protected]
On Tue, 25 Mar 2025 at 05:01, Alexandra Wang
<[email protected]> wrote:
>
> I reviewed the patch and it looks good to me. I’ve run some tests, and
> everything works as expected.
Thanks for the careful review and testing.
> Raising an error instead of silently returning Inf for invalid inputs
> like zero, negative integers, and -Inf makes sense to me.
>
> I also wondered whether we should distinguish domain errors
> (e.g., zero or negative integers) from range errors (e.g., overflow).
> I tested tgamma() and lgamma() on Ubuntu 24.04, macOS 15.3.2, and
> Windows 11 (code attached).
>
> Here’s a summary of return values, errnos, and exceptions on these
> platforms: (errno=33 is EDOM and errno=34 is ERANGE)
Thanks for that. It's very useful to see how tgamma() and lgamma()
behave on those different platforms.
> In the current implementation, float_overflow_error() is used for both
> domain and range (overflow) errors. If we did want to distinguish
> them, maybe we could use float_zero_divide_error() for domain errors?
> Not sure it adds much value, though - just a thought, and I’m fine
> either way.
Hmm, I don't think "division by zero" would be the right error to use,
if we did that. That's probably exposing some internal implementation
detail, but I think it would look odd to users. Perhaps "input is out
of range" would work, but I don't think it's really necessary to
distinguish between these different types of errors.
> So again, I’m totally fine with keeping the current semantics as-is.
> That said, it might be helpful to update the comment in dlgamma():
> + /*
> + * If an ERANGE error occurs, it means there is an overflow.
> + *
> to clarify that an ERANGE error can also indicate a pole error (e.g.,
> input 0 or a negative integer), not just overflow.
OK, thanks. I've updated that comment and committed it.
Regards,
Dean
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-02 10:29 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-25 05:01 ` Re: gamma() and lgamma() functions Alexandra Wang <[email protected]>
2025-03-26 09:49 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
@ 2025-03-26 11:58 ` Marcos Pegoraro <[email protected]>
2025-03-26 12:02 ` Re: gamma() and lgamma() functions Marcos Pegoraro <[email protected]>
0 siblings, 1 reply; 20+ messages in thread
From: Marcos Pegoraro @ 2025-03-26 11:58 UTC (permalink / raw)
To: Dean Rasheed <[email protected]>; +Cc: Alexandra Wang <[email protected]>; Dmitry Koval <[email protected]>; [email protected]
Em qua., 26 de mar. de 2025 às 06:50, Dean Rasheed <[email protected]>
escreveu:
> OK, thanks. I've updated that comment and committed it.
func.sgml doesn't mention lgamma function, so users will think just gamma
exists, is that correct ?
regards
Marcos
^ permalink raw reply [nested|flat] 20+ messages in thread
* Re: gamma() and lgamma() functions
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-02 10:29 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-25 05:01 ` Re: gamma() and lgamma() functions Alexandra Wang <[email protected]>
2025-03-26 09:49 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-26 11:58 ` Re: gamma() and lgamma() functions Marcos Pegoraro <[email protected]>
@ 2025-03-26 12:02 ` Marcos Pegoraro <[email protected]>
0 siblings, 0 replies; 20+ messages in thread
From: Marcos Pegoraro @ 2025-03-26 12:02 UTC (permalink / raw)
To: Dean Rasheed <[email protected]>; +Cc: Alexandra Wang <[email protected]>; Dmitry Koval <[email protected]>; [email protected]
Em qua., 26 de mar. de 2025 às 08:58, Marcos Pegoraro <[email protected]>
escreveu:
> func.sgml doesn't mention lgamma function, so users will think just gamma
> exists, is that correct ?
>
Sorry, it's my fault. I didn't read the entire file.
regards
Marcos
^ permalink raw reply [nested|flat] 20+ messages in thread
end of thread, other threads:[~2025-03-26 12:02 UTC | newest]
Thread overview: 20+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-05-10 20:12 [PATCH] libpq GSSAPI encryption support Robbie Harwood <[email protected]>
2018-05-10 20:12 [PATCH] libpq GSSAPI encryption support Robbie Harwood <[email protected]>
2018-05-10 20:12 [PATCH] libpq GSSAPI encryption support Robbie Harwood <[email protected]>
2018-05-10 20:12 [PATCH] libpq GSSAPI encryption support Robbie Harwood <[email protected]>
2018-05-10 20:12 [PATCH] libpq GSSAPI encryption support Robbie Harwood <[email protected]>
2018-05-10 20:12 [PATCH] libpq GSSAPI encryption support Robbie Harwood <[email protected]>
2020-02-23 00:45 [PATCH 4/7] Add explain(MACHINE) to hide machine-dependent output.. Justin Pryzby <[email protected]>
2024-08-23 12:40 Re: gamma() and lgamma() functions Peter Eisentraut <[email protected]>
2024-09-04 17:55 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-04 18:21 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-04 18:34 ` Re: gamma() and lgamma() functions Tom Lane <[email protected]>
2024-09-06 09:42 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-09-06 11:58 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2024-11-14 16:28 ` Re: gamma() and lgamma() functions Dmitry Koval <[email protected]>
2024-11-14 22:35 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-02 10:29 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-25 05:01 ` Re: gamma() and lgamma() functions Alexandra Wang <[email protected]>
2025-03-26 09:49 ` Re: gamma() and lgamma() functions Dean Rasheed <[email protected]>
2025-03-26 11:58 ` Re: gamma() and lgamma() functions Marcos Pegoraro <[email protected]>
2025-03-26 12:02 ` Re: gamma() and lgamma() functions Marcos Pegoraro <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox