agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH] libpq GSSAPI encryption support
18+ messages / 7 participants
[nested] [flat]
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 18+ 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] 18+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 18+ 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] 18+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 18+ 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] 18+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 18+ 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] 18+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 18+ 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] 18+ messages in thread
* [PATCH] libpq GSSAPI encryption support
@ 2018-05-10 20:12 Robbie Harwood <[email protected]>
0 siblings, 0 replies; 18+ 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] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
@ 2023-01-04 21:18 Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Andrew Dunstan @ 2023-01-04 21:18 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: PostgreSQL Hackers <[email protected]>
On 2023-01-02 Mo 10:44, Tom Lane wrote:
> Andrew Dunstan <[email protected]> writes:
>> I've been wondering if it might be a good idea to have a third parameter
>> for pg_input_error_message() which would default to false, but which if
>> true would cause it to emit the detail and hint fields, if any, as well
>> as the message field from the error_data.
> I don't think that just concatenating those strings would make for a
> pleasant API. More sensible, perhaps, to have a separate function
> that returns a record. Or we could redefine the existing function
> that way, but I suspect that "just the primary error" will be a
> principal use-case.
>
> Being able to get the SQLSTATE is likely to be interesting too.
>
>
OK, here's a patch along those lines.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
Attachments:
[text/x-patch] pg_input_error_detail.patch (8.5K, ../../[email protected]/2-pg_input_error_detail.patch)
download | inline diff:
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3bf8d021c3..d44d78fa67 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24768,6 +24768,40 @@ SELECT collation for ('foo' COLLATE "de_DE");
<returnvalue>numeric field overflow</returnvalue>
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_input_error_detail</primary>
+ </indexterm>
+ <function>pg_input_error_detail</function> (
+ <parameter>string</parameter> <type>text</type>,
+ <parameter>type</parameter> <type>text</type>
+ )
+ <returnvalue>record</returnvalue>
+ ( <parameter>message</parameter> <type>text</type>,
+ <parameter>detail</parameter> <type>text</type>,
+ <parameter>hint</parameter> <type>text</type>,
+ <parameter>sql_error_code</parameter> <type>text</type> )
+ </para>
+ <para>
+ Tests whether the given <parameter>string</parameter> is valid
+ input for the specified data type; if not, return the details of
+ the error that would have been thrown. If the input is valid, the
+ results are NULL. The inputs are the same as
+ for <function>pg_input_is_valid</function>.
+ </para>
+ <para>
+ This function will only work as desired if the data type's input
+ function has been updated to report invalid input as
+ a <quote>soft</quote> error. Otherwise, invalid input will abort
+ the transaction, just as if the string had been cast to the type
+ directly.
+ </para>
+ <para>
+ <literal>to_json(pg_input_error_detail('{1,2', 'integer[]'))</literal>
+ <returnvalue>{"message":"malformed array literal: \"{1,2\"","detail":"Unexpected end of input.","hint":null,"sql_error_code":"22P02"}</returnvalue>
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 220ddb8c01..622b534532 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -688,6 +688,63 @@ pg_input_error_message(PG_FUNCTION_ARGS)
PG_RETURN_TEXT_P(cstring_to_text(escontext.error_data->message));
}
+/*
+ * pg_input_error_detail - test whether string is valid input for datatype.
+ *
+ * Returns NULL data if OK, else the primary message, detail message,
+ * hint message and sql error code from the error.
+ *
+ * This will only work usefully if the datatype's input function has been
+ * updated to return "soft" errors via errsave/ereturn.
+ */
+Datum
+pg_input_error_detail(PG_FUNCTION_ARGS)
+{
+ text *txt = PG_GETARG_TEXT_PP(0);
+ text *typname = PG_GETARG_TEXT_PP(1);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+ TupleDesc tupdesc;
+ Datum values[4];
+ bool isnull[4];
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ /* Enable details_wanted */
+ escontext.details_wanted = true;
+
+ if (pg_input_is_valid_common(fcinfo, txt, typname,
+ &escontext))
+ {
+ memset(isnull,true,sizeof(isnull));
+ }
+ else
+ {
+ Assert(escontext.error_occurred);
+ Assert(escontext.error_data != NULL);
+ Assert(escontext.error_data->message != NULL);
+
+ memset(isnull, false, sizeof(isnull));
+
+ values[0] = CStringGetTextDatum(escontext.error_data->message);
+
+ if (escontext.error_data->detail != NULL)
+ values[1] = CStringGetTextDatum(escontext.error_data->detail);
+ else
+ isnull[1] = true;
+
+ if (escontext.error_data->hint != NULL)
+ values[2] = CStringGetTextDatum(escontext.error_data->hint);
+ else
+ isnull[2] = true;
+
+ values[3] = CStringGetTextDatum(
+ unpack_sql_state(escontext.error_data->sqlerrcode));
+ }
+
+ return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+}
+
/* Common subroutine for the above */
static bool
pg_input_is_valid_common(FunctionCallInfo fcinfo,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7be9a50147..dfc0846f6f 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7081,6 +7081,14 @@
descr => 'get error message if string is not valid input for data type',
proname => 'pg_input_error_message', provolatile => 's', prorettype => 'text',
proargtypes => 'text text', prosrc => 'pg_input_error_message' },
+{ oid => '8052',
+ descr => 'get error details if string is not valid input for data type',
+ proname => 'pg_input_error_detail', provolatile => 's',
+ prorettype => 'record', proargtypes => 'text text',
+ proallargtypes => '{text,text,text,text,text,text}',
+ proargmodes => '{i,i,o,o,o,o}',
+ proargnames => '{value,type_name,message,detail,hint,sql_error_code}',
+ prosrc => 'pg_input_error_detail' },
{ oid => '1268',
descr => 'parse qualified identifier to array of identifiers',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index a2f9d7ed16..9ee081b014 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -207,6 +207,12 @@ SELECT pg_input_error_message('{1,zed}', 'integer[]');
invalid input syntax for type integer: "zed"
(1 row)
+SELECT to_json(pg_input_error_detail('{1,2','integer[]'));
+ to_json
+--------------------------------------------------------------------------------------------------------------------------
+ {"message":"malformed array literal: \"{1,2\"","detail":"Unexpected end of input.","hint":null,"sql_error_code":"22P02"}
+(1 row)
+
-- test mixed slice/scalar subscripting
select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
int4
diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
index a034fbb346..4f95ebcc29 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -532,6 +532,18 @@ SELECT pg_input_error_message('no_such_type', 'regtype');
type "no_such_type" does not exist
(1 row)
+SELECT to_json(pg_input_error_detail('+(int4)','regoperator'));
+ to_json
+---------------------------------------------------------------------------------------------------------------------------------------------
+ {"message":"missing argument","detail":null,"hint":"Use NONE to denote the missing argument of a unary operator.","sql_error_code":"42P02"}
+(1 row)
+
+SELECT to_json(pg_input_error_detail('+(int4,int4,int4)','regoperator'));
+ to_json
+---------------------------------------------------------------------------------------------------------------------------
+ {"message":"too many arguments","detail":null,"hint":"Provide two argument types for operator.","sql_error_code":"54023"}
+(1 row)
+
-- Some cases that should be soft errors, but are not yet
SELECT pg_input_error_message('incorrect type name syntax', 'regtype');
ERROR: syntax error at or near "type"
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 38e8dd440b..db29ccb50e 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -118,6 +118,7 @@ SELECT pg_input_is_valid('{1,2,3}', 'integer[]');
SELECT pg_input_is_valid('{1,2', 'integer[]');
SELECT pg_input_is_valid('{1,zed}', 'integer[]');
SELECT pg_input_error_message('{1,zed}', 'integer[]');
+SELECT to_json(pg_input_error_detail('{1,2','integer[]'));
-- test mixed slice/scalar subscripting
select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql
index 2cb8c9a253..34033a64e1 100644
--- a/src/test/regress/sql/regproc.sql
+++ b/src/test/regress/sql/regproc.sql
@@ -139,6 +139,8 @@ SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
SELECT pg_input_error_message('regress_regrole_test', 'regrole');
SELECT pg_input_error_message('no_such_type', 'regtype');
+SELECT to_json(pg_input_error_detail('+(int4)','regoperator'));
+SELECT to_json(pg_input_error_detail('+(int4,int4,int4)','regoperator'));
-- Some cases that should be soft errors, but are not yet
SELECT pg_input_error_message('incorrect type name syntax', 'regtype');
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
@ 2023-01-10 23:41 ` Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Nathan Bossart @ 2023-01-10 23:41 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Wed, Jan 04, 2023 at 04:18:59PM -0500, Andrew Dunstan wrote:
> On 2023-01-02 Mo 10:44, Tom Lane wrote:
>> I don't think that just concatenating those strings would make for a
>> pleasant API. More sensible, perhaps, to have a separate function
>> that returns a record. Or we could redefine the existing function
>> that way, but I suspect that "just the primary error" will be a
>> principal use-case.
>>
>> Being able to get the SQLSTATE is likely to be interesting too.
>
> OK, here's a patch along those lines.
My vote would be to redefine the existing pg_input_error_message() function
to return a record, but I recognize that this would inflate the patch quite
a bit due to all the existing uses in the tests. If this is the only
argument against this approach, I'm happy to help with the patch.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
@ 2023-02-23 18:40 ` Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Nathan Bossart @ 2023-02-23 18:40 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Tue, Jan 10, 2023 at 03:41:12PM -0800, Nathan Bossart wrote:
> My vote would be to redefine the existing pg_input_error_message() function
> to return a record, but I recognize that this would inflate the patch quite
> a bit due to all the existing uses in the tests. If this is the only
> argument against this approach, I'm happy to help with the patch.
Here's an attempt at this.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-diff] v2-0001-add-details-to-pg_input_error_message.patch (82.8K, ../../20230223184048.GA470211@nathanxps13/2-v2-0001-add-details-to-pg_input_error_message.patch)
download | inline diff:
From 1b92850e7b8811a1e1114a18359d01d33089c0bf Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Thu, 23 Feb 2023 10:31:24 -0800
Subject: [PATCH v2 1/1] add details to pg_input_error_message
---
contrib/cube/expected/cube.out | 6 +-
contrib/hstore/expected/hstore.out | 12 +--
contrib/intarray/expected/_int.out | 10 +--
contrib/isn/expected/isn.out | 10 +--
contrib/ltree/expected/ltree.out | 20 ++---
contrib/seg/expected/seg.out | 16 ++--
doc/src/sgml/func.sgml | 29 ++++---
src/backend/utils/adt/misc.c | 38 +++++++--
src/include/catalog/pg_proc.dat | 10 ++-
src/test/regress/expected/arrays.out | 6 +-
src/test/regress/expected/bit.out | 30 +++----
src/test/regress/expected/boolean.out | 6 +-
src/test/regress/expected/box.out | 12 +--
src/test/regress/expected/char_1.out | 6 +-
src/test/regress/expected/date.out | 12 +--
src/test/regress/expected/domain.out | 24 +++---
src/test/regress/expected/enum.out | 12 +--
src/test/regress/expected/float4.out | 6 +-
src/test/regress/expected/float8.out | 6 +-
src/test/regress/expected/geometry.out | 12 +--
src/test/regress/expected/inet.out | 18 ++---
src/test/regress/expected/int2.out | 18 ++---
src/test/regress/expected/int4.out | 6 +-
src/test/regress/expected/int8.out | 6 +-
src/test/regress/expected/interval.out | 12 +--
src/test/regress/expected/json.out | 6 +-
src/test/regress/expected/json_encoding.out | 2 +-
src/test/regress/expected/jsonb.out | 12 +--
src/test/regress/expected/jsonpath.out | 14 ++--
src/test/regress/expected/line.out | 30 +++----
src/test/regress/expected/lseg.out | 6 +-
src/test/regress/expected/macaddr.out | 12 +--
src/test/regress/expected/macaddr8.out | 12 +--
src/test/regress/expected/money.out | 12 +--
src/test/regress/expected/multirangetypes.out | 12 +--
src/test/regress/expected/numeric.out | 18 ++---
src/test/regress/expected/oid.out | 24 +++---
src/test/regress/expected/path.out | 12 +--
src/test/regress/expected/pg_lsn.out | 6 +-
src/test/regress/expected/point.out | 6 +-
src/test/regress/expected/polygon.out | 12 +--
src/test/regress/expected/privileges.out | 18 ++---
src/test/regress/expected/rangetypes.out | 30 +++----
src/test/regress/expected/regproc.out | 78 +++++++++----------
src/test/regress/expected/rowtypes.out | 12 +--
src/test/regress/expected/strings.out | 18 ++---
src/test/regress/expected/tid.out | 12 +--
src/test/regress/expected/time.out | 12 +--
src/test/regress/expected/timestamp.out | 12 +--
src/test/regress/expected/timestamptz.out | 12 +--
src/test/regress/expected/timetz.out | 12 +--
src/test/regress/expected/tstypes.out | 18 ++---
src/test/regress/expected/uuid.out | 6 +-
src/test/regress/expected/varchar_1.out | 6 +-
src/test/regress/expected/xid.out | 24 +++---
src/test/regress/expected/xml.out | 20 +++--
56 files changed, 438 insertions(+), 391 deletions(-)
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index dc23e5ccc0..3bb42b063b 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -345,9 +345,9 @@ SELECT pg_input_is_valid('-1e-700', 'cube');
(1 row)
SELECT pg_input_error_message('-1e-700', 'cube');
- pg_input_error_message
------------------------------------------------------
- "-1e-700" is out of range for type double precision
+ pg_input_error_message
+-------------------------------------------------------------------
+ ("""-1e-700"" is out of range for type double precision",,,22003)
(1 row)
--
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index d6faa91867..d58fee585e 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -266,15 +266,15 @@ select pg_input_is_valid('a=b', 'hstore');
(1 row)
select pg_input_error_message('a=b', 'hstore');
- pg_input_error_message
-------------------------------------------------
- syntax error in hstore, near "b" at position 2
+ pg_input_error_message
+--------------------------------------------------------------
+ ("syntax error in hstore, near ""b"" at position 2",,,42601)
(1 row)
select pg_input_error_message(' =>b', 'hstore');
- pg_input_error_message
-------------------------------------------------
- syntax error in hstore, near "=" at position 1
+ pg_input_error_message
+--------------------------------------------------------------
+ ("syntax error in hstore, near ""="" at position 1",,,42601)
(1 row)
-- -> operator
diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out
index c953065a5c..73dfea605a 100644
--- a/contrib/intarray/expected/_int.out
+++ b/contrib/intarray/expected/_int.out
@@ -406,11 +406,11 @@ FROM (VALUES ('1&(2&(4&(5|6)))'),
('1#(2&(4&(5&6)))'),
('foo'))
AS a(str);
- query_int | ok | errmsg
------------------+----+--------------
- 1&(2&(4&(5|6))) | t |
- 1#(2&(4&(5&6))) | f | syntax error
- foo | f | syntax error
+ query_int | ok | errmsg
+-----------------+----+--------------------------
+ 1&(2&(4&(5|6))) | t | (,,,)
+ 1#(2&(4&(5&6))) | f | ("syntax error",,,42601)
+ foo | f | ("syntax error",,,42601)
(3 rows)
CREATE TABLE test__int( a int[] );
diff --git a/contrib/isn/expected/isn.out b/contrib/isn/expected/isn.out
index 72171b2790..cbe4166fd0 100644
--- a/contrib/isn/expected/isn.out
+++ b/contrib/isn/expected/isn.out
@@ -268,11 +268,11 @@ FROM (VALUES ('9780123456786', 'UPC'),
('postgresql...','EAN13'),
('9771234567003','ISSN'))
AS a(str,typ);
- isn | type | ok | errmsg
----------------+-------+----+--------------------------------------------------------
- 9780123456786 | UPC | f | cannot cast ISBN to UPC for number: "9780123456786"
- postgresql... | EAN13 | f | invalid input syntax for EAN13 number: "postgresql..."
- 9771234567003 | ISSN | t |
+ isn | type | ok | errmsg
+---------------+-------+----+----------------------------------------------------------------------
+ 9780123456786 | UPC | f | ("cannot cast ISBN to UPC for number: ""9780123456786""",,,22P02)
+ postgresql... | EAN13 | f | ("invalid input syntax for EAN13 number: ""postgresql...""",,,22P02)
+ 9771234567003 | ISSN | t | (,,,)
(3 rows)
--
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index d2a53b9f0c..cf925b2469 100644
--- a/contrib/ltree/expected/ltree.out
+++ b/contrib/ltree/expected/ltree.out
@@ -8111,15 +8111,15 @@ FROM (VALUES ('.2.3', 'ltree'),
('$tree & aWdf@*','ltxtquery'),
('!tree & aWdf@*','ltxtquery'))
AS a(str,typ);
- value | type | ok | errmsg
-----------------+-----------+----+------------------------------------
- .2.3 | ltree | f | ltree syntax error at character 1
- 1.2. | ltree | f | ltree syntax error
- 1.2.3 | ltree | t |
- @.2.3 | lquery | f | lquery syntax error at character 1
- 2.3 | lquery | f | lquery syntax error at character 1
- 1.2.3 | lquery | t |
- $tree & aWdf@* | ltxtquery | f | operand syntax error
- !tree & aWdf@* | ltxtquery | t |
+ value | type | ok | errmsg
+----------------+-----------+----+----------------------------------------------------------
+ .2.3 | ltree | f | ("ltree syntax error at character 1",,,42601)
+ 1.2. | ltree | f | ("ltree syntax error","Unexpected end of input.",,42601)
+ 1.2.3 | ltree | t | (,,,)
+ @.2.3 | lquery | f | ("lquery syntax error at character 1",,,42601)
+ 2.3 | lquery | f | ("lquery syntax error at character 1",,,42601)
+ 1.2.3 | lquery | t | (,,,)
+ $tree & aWdf@* | ltxtquery | f | ("operand syntax error",,,42601)
+ !tree & aWdf@* | ltxtquery | t | (,,,)
(8 rows)
diff --git a/contrib/seg/expected/seg.out b/contrib/seg/expected/seg.out
index 7a06113ed8..4b09bd1d4f 100644
--- a/contrib/seg/expected/seg.out
+++ b/contrib/seg/expected/seg.out
@@ -1283,13 +1283,13 @@ FROM unnest(ARRAY['-1 .. 1'::text,
'ABC',
'1 e7',
'1e700']) str;
- seg | ok | errmsg
-----------+----+---------------------------------------
- -1 .. 1 | t |
- 100(+-)1 | t |
- | f | bad seg representation
- ABC | f | bad seg representation
- 1 e7 | f | bad seg representation
- 1e700 | f | "1e700" is out of range for type real
+ seg | ok | errmsg
+----------+----+-------------------------------------------------------------------
+ -1 .. 1 | t | (,,,)
+ 100(+-)1 | t | (,,,)
+ | f | ("bad seg representation","syntax error at end of input",,42601)
+ ABC | f | ("bad seg representation","syntax error at or near ""A""",,42601)
+ 1 e7 | f | ("bad seg representation","syntax error at or near ""e""",,42601)
+ 1e700 | f | ("""1e700"" is out of range for type real",,,22003)
(6 rows)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0cbdf63632..76dade49db 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24781,13 +24781,17 @@ SELECT collation for ('foo' COLLATE "de_DE");
<parameter>string</parameter> <type>text</type>,
<parameter>type</parameter> <type>text</type>
)
- <returnvalue>text</returnvalue>
+ <returnvalue>record</returnvalue>
+ ( <parameter>message</parameter> <type>text</type>,
+ <parameter>detail</parameter> <type>text</type>,
+ <parameter>hint</parameter> <type>text</type>,
+ <parameter>sql_error_code</parameter> <type>text</type> )
</para>
<para>
Tests whether the given <parameter>string</parameter> is valid
- input for the specified data type; if not, return the error
- message that would have been thrown. If the input is valid, the
- result is NULL. The inputs are the same as
+ input for the specified data type; if not, return the details of
+ the error would have been thrown. If the input is valid, the
+ results are NULL. The inputs are the same as
for <function>pg_input_is_valid</function>.
</para>
<para>
@@ -24798,12 +24802,17 @@ SELECT collation for ('foo' COLLATE "de_DE");
directly.
</para>
<para>
- <literal>pg_input_error_message('42000000000', 'integer')</literal>
- <returnvalue>value "42000000000" is out of range for type integer</returnvalue>
- </para>
- <para>
- <literal>pg_input_error_message('1234.567', 'numeric(7,4)')</literal>
- <returnvalue>numeric field overflow</returnvalue>
+<programlisting>
+SELECT * FROM pg_input_error_message('42000000000', 'integer');
+ message | detail | hint | sql_error_code
+------------------------------------------------------+--------+------+----------------
+ value "42000000000" is out of range for type integer | | | 22003
+
+SELECT * FROM pg_input_error_message('1234.567', 'numeric(7,4)');
+ message | detail | hint | sql_error_code
+------------------------+-----------------------------------------------------------------------------------+------+----------------
+ numeric field overflow | A field with precision 7, scale 4 must round to an absolute value less than 10^3. | | 22003
+</programlisting>
</para></entry>
</row>
</tbody>
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index f95256efd3..a4f4b7fb90 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -662,7 +662,8 @@ pg_input_is_valid(PG_FUNCTION_ARGS)
/*
* pg_input_error_message - test whether string is valid input for datatype.
*
- * Returns NULL if OK, else the primary message string from the error.
+ * Returns NULL if OK, else the primary message, detail message, hint message,
+ * and sql error code from the error.
*
* This will only work usefully if the datatype's input function has been
* updated to return "soft" errors via errsave/ereturn.
@@ -673,19 +674,44 @@ pg_input_error_message(PG_FUNCTION_ARGS)
text *txt = PG_GETARG_TEXT_PP(0);
text *typname = PG_GETARG_TEXT_PP(1);
ErrorSaveContext escontext = {T_ErrorSaveContext};
+ TupleDesc tupdesc;
+ Datum values[4];
+ bool isnull[4];
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
/* Enable details_wanted */
escontext.details_wanted = true;
if (pg_input_is_valid_common(fcinfo, txt, typname,
&escontext))
- PG_RETURN_NULL();
+ memset(isnull, true, sizeof(isnull));
+ else
+ {
+ Assert(escontext.error_occurred);
+ Assert(escontext.error_data != NULL);
+ Assert(escontext.error_data->message != NULL);
+
+ memset(isnull, false, sizeof(isnull));
+
+ values[0] = CStringGetTextDatum(escontext.error_data->message);
- Assert(escontext.error_occurred);
- Assert(escontext.error_data != NULL);
- Assert(escontext.error_data->message != NULL);
+ if (escontext.error_data->detail != NULL)
+ values[1] = CStringGetTextDatum(escontext.error_data->detail);
+ else
+ isnull[1] = true;
+
+ if (escontext.error_data->hint != NULL)
+ values[2] = CStringGetTextDatum(escontext.error_data->hint);
+ else
+ isnull[2] = true;
+
+ values[3] = CStringGetTextDatum(
+ unpack_sql_state(escontext.error_data->sqlerrcode));
+ }
- PG_RETURN_TEXT_P(cstring_to_text(escontext.error_data->message));
+ return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
}
/* Common subroutine for the above */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e2a7642a2b..ecb5df2e53 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7118,9 +7118,13 @@
proname => 'pg_input_is_valid', provolatile => 's', prorettype => 'bool',
proargtypes => 'text text', prosrc => 'pg_input_is_valid' },
{ oid => '8051',
- descr => 'get error message if string is not valid input for data type',
- proname => 'pg_input_error_message', provolatile => 's', prorettype => 'text',
- proargtypes => 'text text', prosrc => 'pg_input_error_message' },
+ descr => 'get error details if string is not valid input for data type',
+ proname => 'pg_input_error_message', provolatile => 's', prorettype => 'record',
+ proargtypes => 'text text',
+ proallargtypes => '{text,text,text,text,text,text}',
+ proargmodes => '{i,i,o,o,o,o}',
+ proargnames => '{value,type_name,message,detail,hint,sql_error_code}',
+ prosrc => 'pg_input_error_message' },
{ oid => '1268',
descr => 'parse qualified identifier to array of identifiers',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index a2f9d7ed16..0e6a50dbca 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -202,9 +202,9 @@ SELECT pg_input_is_valid('{1,zed}', 'integer[]');
(1 row)
SELECT pg_input_error_message('{1,zed}', 'integer[]');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
-- test mixed slice/scalar subscripting
diff --git a/src/test/regress/expected/bit.out b/src/test/regress/expected/bit.out
index 209044713c..1b88327cb6 100644
--- a/src/test/regress/expected/bit.out
+++ b/src/test/regress/expected/bit.out
@@ -754,9 +754,9 @@ SELECT pg_input_is_valid('01010001', 'bit(10)');
(1 row)
SELECT pg_input_error_message('01010001', 'bit(10)');
- pg_input_error_message
--------------------------------------------------
- bit string length 8 does not match type bit(10)
+ pg_input_error_message
+-------------------------------------------------------------
+ ("bit string length 8 does not match type bit(10)",,,22026)
(1 row)
SELECT pg_input_is_valid('01010Z01', 'bit(8)');
@@ -766,9 +766,9 @@ SELECT pg_input_is_valid('01010Z01', 'bit(8)');
(1 row)
SELECT pg_input_error_message('01010Z01', 'bit(8)');
- pg_input_error_message
----------------------------------
- "Z" is not a valid binary digit
+ pg_input_error_message
+-----------------------------------------------
+ ("""Z"" is not a valid binary digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
@@ -778,9 +778,9 @@ SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
(1 row)
SELECT pg_input_error_message('x01010Z01', 'bit(32)');
- pg_input_error_message
---------------------------------------
- "Z" is not a valid hexadecimal digit
+ pg_input_error_message
+----------------------------------------------------
+ ("""Z"" is not a valid hexadecimal digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('01010Z01', 'varbit');
@@ -790,9 +790,9 @@ SELECT pg_input_is_valid('01010Z01', 'varbit');
(1 row)
SELECT pg_input_error_message('01010Z01', 'varbit');
- pg_input_error_message
----------------------------------
- "Z" is not a valid binary digit
+ pg_input_error_message
+-----------------------------------------------
+ ("""Z"" is not a valid binary digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('x01010Z01', 'varbit');
@@ -802,8 +802,8 @@ SELECT pg_input_is_valid('x01010Z01', 'varbit');
(1 row)
SELECT pg_input_error_message('x01010Z01', 'varbit');
- pg_input_error_message
---------------------------------------
- "Z" is not a valid hexadecimal digit
+ pg_input_error_message
+----------------------------------------------------
+ ("""Z"" is not a valid hexadecimal digit",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/boolean.out b/src/test/regress/expected/boolean.out
index 977124b20b..331a8d2890 100644
--- a/src/test/regress/expected/boolean.out
+++ b/src/test/regress/expected/boolean.out
@@ -156,9 +156,9 @@ SELECT pg_input_is_valid('asdf', 'bool');
(1 row)
SELECT pg_input_error_message('junk', 'bool');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type boolean: "junk"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input syntax for type boolean: ""junk""",,,22P02)
(1 row)
-- and, or, not in qualifications
diff --git a/src/test/regress/expected/box.out b/src/test/regress/expected/box.out
index 0d70194def..7e7cbfee9c 100644
--- a/src/test/regress/expected/box.out
+++ b/src/test/regress/expected/box.out
@@ -647,9 +647,9 @@ SELECT pg_input_is_valid('200', 'box');
(1 row)
SELECT pg_input_error_message('200', 'box');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type box: "200"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type box: ""200""",,,22P02)
(1 row)
SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
@@ -659,8 +659,8 @@ SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
(1 row)
SELECT pg_input_error_message('((200,300),(500, xyz))', 'box');
- pg_input_error_message
--------------------------------------------------------------
- invalid input syntax for type box: "((200,300),(500, xyz))"
+ pg_input_error_message
+---------------------------------------------------------------------------
+ ("invalid input syntax for type box: ""((200,300),(500, xyz))""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/char_1.out b/src/test/regress/expected/char_1.out
index 3dcb0daa0d..32e4b28f6c 100644
--- a/src/test/regress/expected/char_1.out
+++ b/src/test/regress/expected/char_1.out
@@ -133,9 +133,9 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+ pg_input_error_message
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index c0dec448e1..d3fca737eb 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -860,15 +860,15 @@ SELECT pg_input_is_valid('6874898-01-01', 'date');
(1 row)
SELECT pg_input_error_message('garbage', 'date');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type date: "garbage"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input syntax for type date: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('6874898-01-01', 'date');
- pg_input_error_message
-------------------------------------
- date out of range: "6874898-01-01"
+ pg_input_error_message
+--------------------------------------------------
+ ("date out of range: ""6874898-01-01""",,,22008)
(1 row)
RESET datestyle;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 25f6bb9e1f..c4adce88c1 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -109,27 +109,27 @@ select pg_input_is_valid('-1', 'positiveint');
(1 row)
select pg_input_error_message('junk', 'positiveint');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type integer: "junk"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input syntax for type integer: ""junk""",,,22P02)
(1 row)
select pg_input_error_message('-1', 'positiveint');
- pg_input_error_message
-----------------------------------------------------------------------------
- value for domain positiveint violates check constraint "positiveint_check"
+ pg_input_error_message
+------------------------------------------------------------------------------------------
+ ("value for domain positiveint violates check constraint ""positiveint_check""",,,23514)
(1 row)
select pg_input_error_message('junk', 'weirdfloat');
- pg_input_error_message
---------------------------------------------------------
- invalid input syntax for type double precision: "junk"
+ pg_input_error_message
+----------------------------------------------------------------------
+ ("invalid input syntax for type double precision: ""junk""",,,22P02)
(1 row)
select pg_input_error_message('0.01', 'weirdfloat');
- pg_input_error_message
---------------------------------------------------------------------------
- value for domain weirdfloat violates check constraint "weirdfloat_check"
+ pg_input_error_message
+----------------------------------------------------------------------------------------
+ ("value for domain weirdfloat violates check constraint ""weirdfloat_check""",,,23514)
(1 row)
-- We currently can't trap errors raised in the CHECK expression itself
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 4b45fcf8f0..c3cf9625b4 100644
--- a/src/test/regress/expected/enum.out
+++ b/src/test/regress/expected/enum.out
@@ -38,15 +38,15 @@ SELECT pg_input_is_valid('mauve', 'rainbow');
(1 row)
SELECT pg_input_error_message('mauve', 'rainbow');
- pg_input_error_message
------------------------------------------------
- invalid input value for enum rainbow: "mauve"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input value for enum rainbow: ""mauve""",,,22P02)
(1 row)
SELECT pg_input_error_message(repeat('too_long', 32), 'rainbow');
- pg_input_error_message
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- invalid input value for enum rainbow: "too_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_long"
+ pg_input_error_message
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ("invalid input value for enum rainbow: ""too_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_long""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 1d7090a90d..7b0f71a23d 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -101,9 +101,9 @@ SELECT pg_input_is_valid('1e400', 'float4');
(1 row)
SELECT pg_input_error_message('1e400', 'float4');
- pg_input_error_message
----------------------------------------
- "1e400" is out of range for type real
+ pg_input_error_message
+-----------------------------------------------------
+ ("""1e400"" is out of range for type real",,,22003)
(1 row)
-- special inputs
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 2b25784f7f..f6c8941438 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -88,9 +88,9 @@ SELECT pg_input_is_valid('1e4000', 'float8');
(1 row)
SELECT pg_input_error_message('1e4000', 'float8');
- pg_input_error_message
-----------------------------------------------------
- "1e4000" is out of range for type double precision
+ pg_input_error_message
+------------------------------------------------------------------
+ ("""1e4000"" is out of range for type double precision",,,22003)
(1 row)
-- special inputs
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 291cacdf4f..54bc17a6d7 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -5303,9 +5303,9 @@ SELECT pg_input_is_valid('(1', 'circle');
(1 row)
SELECT pg_input_error_message('1,', 'circle');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type circle: "1,"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type circle: ""1,""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(1,2),-1', 'circle');
@@ -5315,8 +5315,8 @@ SELECT pg_input_is_valid('(1,2),-1', 'circle');
(1 row)
SELECT pg_input_error_message('(1,2),-1', 'circle');
- pg_input_error_message
---------------------------------------------------
- invalid input syntax for type circle: "(1,2),-1"
+ pg_input_error_message
+----------------------------------------------------------------
+ ("invalid input syntax for type circle: ""(1,2),-1""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
index c9f466ac1d..957c0003fc 100644
--- a/src/test/regress/expected/inet.out
+++ b/src/test/regress/expected/inet.out
@@ -1064,9 +1064,9 @@ SELECT pg_input_is_valid('1234', 'cidr');
(1 row)
SELECT pg_input_error_message('1234', 'cidr');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type cidr: "1234"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type cidr: ""1234""",,,22P02)
(1 row)
SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
@@ -1076,9 +1076,9 @@ SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
(1 row)
SELECT pg_input_error_message('192.168.198.200/24', 'cidr');
- pg_input_error_message
-------------------------------------------
- invalid cidr value: "192.168.198.200/24"
+ pg_input_error_message
+----------------------------------------------------------------------------------------------
+ ("invalid cidr value: ""192.168.198.200/24""","Value has bits set to right of mask.",,22P02)
(1 row)
SELECT pg_input_is_valid('1234', 'inet');
@@ -1088,8 +1088,8 @@ SELECT pg_input_is_valid('1234', 'inet');
(1 row)
SELECT pg_input_error_message('1234', 'inet');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type inet: "1234"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type inet: ""1234""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 73b4ee023c..d42b15c423 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -65,9 +65,9 @@ SELECT pg_input_is_valid('50000', 'int2');
(1 row)
SELECT pg_input_error_message('50000', 'int2');
- pg_input_error_message
--------------------------------------------------
- value "50000" is out of range for type smallint
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""50000"" is out of range for type smallint",,,22003)
(1 row)
-- While we're here, check int2vector as well
@@ -78,15 +78,15 @@ SELECT pg_input_is_valid(' 1 3 5 ', 'int2vector');
(1 row)
SELECT pg_input_error_message('1 asdf', 'int2vector');
- pg_input_error_message
-------------------------------------------------
- invalid input syntax for type smallint: "asdf"
+ pg_input_error_message
+--------------------------------------------------------------
+ ("invalid input syntax for type smallint: ""asdf""",,,22P02)
(1 row)
SELECT pg_input_error_message('50000', 'int2vector');
- pg_input_error_message
--------------------------------------------------
- value "50000" is out of range for type smallint
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""50000"" is out of range for type smallint",,,22003)
(1 row)
SELECT * FROM INT2_TBL AS f(a, b);
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index 9c20574ca5..683415a948 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -65,9 +65,9 @@ SELECT pg_input_is_valid('1000000000000', 'int4');
(1 row)
SELECT pg_input_error_message('1000000000000', 'int4');
- pg_input_error_message
---------------------------------------------------------
- value "1000000000000" is out of range for type integer
+ pg_input_error_message
+----------------------------------------------------------------------
+ ("value ""1000000000000"" is out of range for type integer",,,22003)
(1 row)
SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index d9dca64e88..e081cb6b00 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -62,9 +62,9 @@ SELECT pg_input_is_valid('10000000000000000000', 'int8');
(1 row)
SELECT pg_input_error_message('10000000000000000000', 'int8');
- pg_input_error_message
---------------------------------------------------------------
- value "10000000000000000000" is out of range for type bigint
+ pg_input_error_message
+----------------------------------------------------------------------------
+ ("value ""10000000000000000000"" is out of range for type bigint",,,22003)
(1 row)
-- int8/int8 cmp
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a154840c85..2a7b49c36c 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -92,15 +92,15 @@ SELECT pg_input_is_valid('@ 30 eons ago', 'interval');
(1 row)
SELECT pg_input_error_message('garbage', 'interval');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type interval: "garbage"
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("invalid input syntax for type interval: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('@ 30 eons ago', 'interval');
- pg_input_error_message
----------------------------------------------------------
- invalid input syntax for type interval: "@ 30 eons ago"
+ pg_input_error_message
+-----------------------------------------------------------------------
+ ("invalid input syntax for type interval: ""@ 30 eons ago""",,,22007)
(1 row)
-- test interval operators
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index af96ce4180..a3096c2c2e 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -334,9 +334,9 @@ select pg_input_is_valid('{"a":true', 'json');
(1 row)
select pg_input_error_message('{"a":true', 'json');
- pg_input_error_message
-------------------------------------
- invalid input syntax for type json
+ pg_input_error_message
+--------------------------------------------------------------------------------------
+ ("invalid input syntax for type json","The input string ended unexpectedly.",,22P02)
(1 row)
--constructors
diff --git a/src/test/regress/expected/json_encoding.out b/src/test/regress/expected/json_encoding.out
index 083621fb21..54a5bb75f5 100644
--- a/src/test/regress/expected/json_encoding.out
+++ b/src/test/regress/expected/json_encoding.out
@@ -264,6 +264,6 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
select pg_input_error_message('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
pg_input_error_message
------------------------
-
+ (,,,)
(1 row)
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index d3248aa0fd..dc125b1b56 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -324,15 +324,15 @@ select pg_input_is_valid('{"a":true', 'jsonb');
(1 row)
select pg_input_error_message('{"a":true', 'jsonb');
- pg_input_error_message
-------------------------------------
- invalid input syntax for type json
+ pg_input_error_message
+--------------------------------------------------------------------------------------
+ ("invalid input syntax for type json","The input string ended unexpectedly.",,22P02)
(1 row)
select pg_input_error_message('{"a":1e1000000}', 'jsonb');
- pg_input_error_message
---------------------------------
- value overflows numeric format
+ pg_input_error_message
+--------------------------------------------
+ ("value overflows numeric format",,,22003)
(1 row)
-- make sure jsonb is passed through json generators without being escaped
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ca0cdf1ab2..87a4141f3a 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -1041,12 +1041,12 @@ FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text,
'@ + 1',
'00',
'1a']) str;
- jsonpath | ok | errmsg
--------------------------------------------+----+-----------------------------------------------------------------------
- $ ? (@ like_regex "pattern" flag "smixq") | t |
- $ ? (@ like_regex "pattern" flag "a") | f | invalid input syntax for type jsonpath
- @ + 1 | f | @ is not allowed in root expressions
- 00 | f | trailing junk after numeric literal at or near "00" of jsonpath input
- 1a | f | trailing junk after numeric literal at or near "1a" of jsonpath input
+ jsonpath | ok | errmsg
+-------------------------------------------+----+----------------------------------------------------------------------------------------------------------------
+ $ ? (@ like_regex "pattern" flag "smixq") | t | (,,,)
+ $ ? (@ like_regex "pattern" flag "a") | f | ("invalid input syntax for type jsonpath","Unrecognized flag character ""a"" in LIKE_REGEX predicate.",,42601)
+ @ + 1 | f | ("@ is not allowed in root expressions",,,42601)
+ 00 | f | ("trailing junk after numeric literal at or near ""00"" of jsonpath input",,,42601)
+ 1a | f | ("trailing junk after numeric literal at or near ""1a"" of jsonpath input",,,42601)
(5 rows)
diff --git a/src/test/regress/expected/line.out b/src/test/regress/expected/line.out
index 6baea8fdbd..4f67aee728 100644
--- a/src/test/regress/expected/line.out
+++ b/src/test/regress/expected/line.out
@@ -93,9 +93,9 @@ SELECT pg_input_is_valid('{1, 1}', 'line');
(1 row)
SELECT pg_input_error_message('{1, 1}', 'line');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type line: "{1, 1}"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type line: ""{1, 1}""",,,22P02)
(1 row)
SELECT pg_input_is_valid('{0, 0, 0}', 'line');
@@ -105,9 +105,9 @@ SELECT pg_input_is_valid('{0, 0, 0}', 'line');
(1 row)
SELECT pg_input_error_message('{0, 0, 0}', 'line');
- pg_input_error_message
----------------------------------------------------------
- invalid line specification: A and B cannot both be zero
+ pg_input_error_message
+---------------------------------------------------------------------
+ ("invalid line specification: A and B cannot both be zero",,,22P02)
(1 row)
SELECT pg_input_is_valid('{1, 1, a}', 'line');
@@ -117,9 +117,9 @@ SELECT pg_input_is_valid('{1, 1, a}', 'line');
(1 row)
SELECT pg_input_error_message('{1, 1, a}', 'line');
- pg_input_error_message
--------------------------------------------------
- invalid input syntax for type line: "{1, 1, a}"
+ pg_input_error_message
+---------------------------------------------------------------
+ ("invalid input syntax for type line: ""{1, 1, a}""",,,22P02)
(1 row)
SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
@@ -129,9 +129,9 @@ SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
(1 row)
SELECT pg_input_error_message('{1, 1, 1e400}', 'line');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
@@ -141,8 +141,8 @@ SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
(1 row)
SELECT pg_input_error_message('(1, 1), (1, 1e400)', 'line');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
diff --git a/src/test/regress/expected/lseg.out b/src/test/regress/expected/lseg.out
index afb323fe04..14e4f73ed1 100644
--- a/src/test/regress/expected/lseg.out
+++ b/src/test/regress/expected/lseg.out
@@ -50,8 +50,8 @@ SELECT pg_input_is_valid('[(1,2),(3)]', 'lseg');
(1 row)
SELECT pg_input_error_message('[(1,2),(3)]', 'lseg');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type lseg: "[(1,2),(3)]"
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("invalid input syntax for type lseg: ""[(1,2),(3)]""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/macaddr.out b/src/test/regress/expected/macaddr.out
index cb646af79b..ca7c5f71ef 100644
--- a/src/test/regress/expected/macaddr.out
+++ b/src/test/regress/expected/macaddr.out
@@ -166,9 +166,9 @@ SELECT pg_input_is_valid('08:00:2b:01:02:ZZ', 'macaddr');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:ZZ', 'macaddr');
- pg_input_error_message
-------------------------------------------------------------
- invalid input syntax for type macaddr: "08:00:2b:01:02:ZZ"
+ pg_input_error_message
+--------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr: ""08:00:2b:01:02:ZZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
@@ -178,8 +178,8 @@ SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:', 'macaddr');
- pg_input_error_message
-----------------------------------------------------------
- invalid input syntax for type macaddr: "08:00:2b:01:02:"
+ pg_input_error_message
+------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr: ""08:00:2b:01:02:""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/macaddr8.out b/src/test/regress/expected/macaddr8.out
index bf681988f8..fa5308bb1f 100644
--- a/src/test/regress/expected/macaddr8.out
+++ b/src/test/regress/expected/macaddr8.out
@@ -360,9 +360,9 @@ SELECT pg_input_is_valid('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
- pg_input_error_message
--------------------------------------------------------------------
- invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:ZZ"
+ pg_input_error_message
+---------------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr8: ""08:00:2b:01:02:03:04:ZZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
@@ -372,8 +372,8 @@ SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:03:04:', 'macaddr8');
- pg_input_error_message
------------------------------------------------------------------
- invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:"
+ pg_input_error_message
+-------------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr8: ""08:00:2b:01:02:03:04:""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index 46b2eab51a..574cbd17a5 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -339,9 +339,9 @@ SELECT pg_input_is_valid('\x0001', 'money');
(1 row)
SELECT pg_input_error_message('\x0001', 'money');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type money: "\x0001"
+ pg_input_error_message
+--------------------------------------------------------------
+ ("invalid input syntax for type money: ""\\x0001""",,,22P02)
(1 row)
SELECT pg_input_is_valid('192233720368547758.07', 'money');
@@ -351,9 +351,9 @@ SELECT pg_input_is_valid('192233720368547758.07', 'money');
(1 row)
SELECT pg_input_error_message('192233720368547758.07', 'money');
- pg_input_error_message
---------------------------------------------------------------
- value "192233720368547758.07" is out of range for type money
+ pg_input_error_message
+----------------------------------------------------------------------------
+ ("value ""192233720368547758.07"" is out of range for type money",,,22003)
(1 row)
-- documented minimums and maximums
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index e70896b754..79137d14bb 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -288,9 +288,9 @@ select pg_input_is_valid('{[1,2], [4,5]', 'int4multirange');
(1 row)
select pg_input_error_message('{[1,2], [4,5]', 'int4multirange');
- pg_input_error_message
------------------------------------------------
- malformed multirange literal: "{[1,2], [4,5]"
+ pg_input_error_message
+---------------------------------------------------------------------------------------
+ ("malformed multirange literal: ""{[1,2], [4,5]""","Unexpected end of input.",,22P02)
(1 row)
select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
@@ -300,9 +300,9 @@ select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
(1 row)
select pg_input_error_message('{[1,2], [4,zed]}', 'int4multirange');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 56a3f3630a..cee8603dad 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2313,9 +2313,9 @@ SELECT pg_input_is_valid('1e400000', 'numeric');
(1 row)
SELECT pg_input_error_message('1e400000', 'numeric');
- pg_input_error_message
---------------------------------
- value overflows numeric format
+ pg_input_error_message
+--------------------------------------------
+ ("value overflows numeric format",,,22003)
(1 row)
SELECT pg_input_is_valid('1234.567', 'numeric(8,4)');
@@ -2331,15 +2331,15 @@ SELECT pg_input_is_valid('1234.567', 'numeric(7,4)');
(1 row)
SELECT pg_input_error_message('1234.567', 'numeric(7,4)');
- pg_input_error_message
-------------------------
- numeric field overflow
+ pg_input_error_message
+-----------------------------------------------------------------------------------------------------------------------
+ ("numeric field overflow","A field with precision 7, scale 4 must round to an absolute value less than 10^3.",,22003)
(1 row)
SELECT pg_input_error_message('0x1234.567', 'numeric');
- pg_input_error_message
------------------------------------------------------
- invalid input syntax for type numeric: "0x1234.567"
+ pg_input_error_message
+-------------------------------------------------------------------
+ ("invalid input syntax for type numeric: ""0x1234.567""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index b664bab5f9..2a4c3b08df 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -79,9 +79,9 @@ SELECT pg_input_is_valid('01XYZ', 'oid');
(1 row)
SELECT pg_input_error_message('01XYZ', 'oid');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type oid: "01XYZ"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type oid: ""01XYZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('9999999999', 'oid');
@@ -91,9 +91,9 @@ SELECT pg_input_is_valid('9999999999', 'oid');
(1 row)
SELECT pg_input_error_message('9999999999', 'oid');
- pg_input_error_message
--------------------------------------------------
- value "9999999999" is out of range for type oid
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""9999999999"" is out of range for type oid",,,22003)
(1 row)
-- While we're here, check oidvector as well
@@ -110,9 +110,9 @@ SELECT pg_input_is_valid('01 01XYZ', 'oidvector');
(1 row)
SELECT pg_input_error_message('01 01XYZ', 'oidvector');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type oid: "XYZ"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type oid: ""XYZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('01 9999999999', 'oidvector');
@@ -122,9 +122,9 @@ SELECT pg_input_is_valid('01 9999999999', 'oidvector');
(1 row)
SELECT pg_input_error_message('01 9999999999', 'oidvector');
- pg_input_error_message
--------------------------------------------------
- value "9999999999" is out of range for type oid
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""9999999999"" is out of range for type oid",,,22003)
(1 row)
SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
diff --git a/src/test/regress/expected/path.out b/src/test/regress/expected/path.out
index 529a5e6fc2..2886091675 100644
--- a/src/test/regress/expected/path.out
+++ b/src/test/regress/expected/path.out
@@ -88,9 +88,9 @@ SELECT pg_input_is_valid('[(1,2),(3)]', 'path');
(1 row)
SELECT pg_input_error_message('[(1,2),(3)]', 'path');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type path: "[(1,2),(3)]"
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("invalid input syntax for type path: ""[(1,2),(3)]""",,,22P02)
(1 row)
SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
@@ -100,8 +100,8 @@ SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
(1 row)
SELECT pg_input_error_message('[(1,2,6),(3,4,6)]', 'path');
- pg_input_error_message
----------------------------------------------------------
- invalid input syntax for type path: "[(1,2,6),(3,4,6)]"
+ pg_input_error_message
+-----------------------------------------------------------------------
+ ("invalid input syntax for type path: ""[(1,2,6),(3,4,6)]""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/pg_lsn.out b/src/test/regress/expected/pg_lsn.out
index 01501f8c9b..7dfa0bae8b 100644
--- a/src/test/regress/expected/pg_lsn.out
+++ b/src/test/regress/expected/pg_lsn.out
@@ -34,9 +34,9 @@ SELECT pg_input_is_valid('16AE7F7', 'pg_lsn');
(1 row)
SELECT pg_input_error_message('16AE7F7', 'pg_lsn');
- pg_input_error_message
--------------------------------------------------
- invalid input syntax for type pg_lsn: "16AE7F7"
+ pg_input_error_message
+---------------------------------------------------------------
+ ("invalid input syntax for type pg_lsn: ""16AE7F7""",,,22P02)
(1 row)
-- Min/Max aggregation
diff --git a/src/test/regress/expected/point.out b/src/test/regress/expected/point.out
index a716ceb881..5a5b1ac4ef 100644
--- a/src/test/regress/expected/point.out
+++ b/src/test/regress/expected/point.out
@@ -471,8 +471,8 @@ SELECT pg_input_is_valid('1,y', 'point');
(1 row)
SELECT pg_input_error_message('1,y', 'point');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type point: "1,y"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type point: ""1,y""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/polygon.out b/src/test/regress/expected/polygon.out
index c7d565ad53..ef31e4c631 100644
--- a/src/test/regress/expected/polygon.out
+++ b/src/test/regress/expected/polygon.out
@@ -314,9 +314,9 @@ SELECT pg_input_is_valid('(2.0,0.8,0.1)', 'polygon');
(1 row)
SELECT pg_input_error_message('(2.0,0.8,0.1)', 'polygon');
- pg_input_error_message
---------------------------------------------------------
- invalid input syntax for type polygon: "(2.0,0.8,0.1)"
+ pg_input_error_message
+----------------------------------------------------------------------
+ ("invalid input syntax for type polygon: ""(2.0,0.8,0.1)""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
@@ -326,8 +326,8 @@ SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
(1 row)
SELECT pg_input_error_message('(2.0,xyz)', 'polygon');
- pg_input_error_message
-----------------------------------------------------
- invalid input syntax for type polygon: "(2.0,xyz)"
+ pg_input_error_message
+------------------------------------------------------------------
+ ("invalid input syntax for type polygon: ""(2.0,xyz)""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..c2e82f39e7 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2247,9 +2247,9 @@ SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem');
(1 row)
SELECT pg_input_error_message('regress_priv_user1=r/', 'aclitem');
- pg_input_error_message
----------------------------------
- a name must follow the "/" sign
+ pg_input_error_message
+-----------------------------------------------
+ ("a name must follow the ""/"" sign",,,22P02)
(1 row)
SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem');
@@ -2259,9 +2259,9 @@ SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem')
(1 row)
SELECT pg_input_error_message('regress_priv_user1=r/regress_no_such_user', 'aclitem');
- pg_input_error_message
---------------------------------------------
- role "regress_no_such_user" does not exist
+ pg_input_error_message
+----------------------------------------------------------
+ ("role ""regress_no_such_user"" does not exist",,,42704)
(1 row)
SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
@@ -2271,9 +2271,9 @@ SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
(1 row)
SELECT pg_input_error_message('regress_priv_user1=rY', 'aclitem');
- pg_input_error_message
-----------------------------------------------------------
- invalid mode character: must be one of "arwdDxtXUCTcsAm"
+ pg_input_error_message
+------------------------------------------------------------------------
+ ("invalid mode character: must be one of ""arwdDxtXUCTcsAm""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index a3e9e447af..e3f1029555 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -189,9 +189,9 @@ select pg_input_is_valid('(1,4', 'int4range');
(1 row)
select pg_input_error_message('(1,4', 'int4range');
- pg_input_error_message
----------------------------------
- malformed range literal: "(1,4"
+ pg_input_error_message
+-------------------------------------------------------------------------
+ ("malformed range literal: ""(1,4""","Unexpected end of input.",,22P02)
(1 row)
select pg_input_is_valid('(4,1)', 'int4range');
@@ -201,9 +201,9 @@ select pg_input_is_valid('(4,1)', 'int4range');
(1 row)
select pg_input_error_message('(4,1)', 'int4range');
- pg_input_error_message
--------------------------------------------------------------------
- range lower bound must be less than or equal to range upper bound
+ pg_input_error_message
+-------------------------------------------------------------------------------
+ ("range lower bound must be less than or equal to range upper bound",,,22000)
(1 row)
select pg_input_is_valid('(4,zed)', 'int4range');
@@ -213,9 +213,9 @@ select pg_input_is_valid('(4,zed)', 'int4range');
(1 row)
select pg_input_error_message('(4,zed)', 'int4range');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
select pg_input_is_valid('[1,2147483647]', 'int4range');
@@ -225,9 +225,9 @@ select pg_input_is_valid('[1,2147483647]', 'int4range');
(1 row)
select pg_input_error_message('[1,2147483647]', 'int4range');
- pg_input_error_message
-------------------------
- integer out of range
+ pg_input_error_message
+----------------------------------
+ ("integer out of range",,,22003)
(1 row)
select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
@@ -237,9 +237,9 @@ select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
(1 row)
select pg_input_error_message('[2000-01-01,5874897-12-31]', 'daterange');
- pg_input_error_message
-------------------------
- date out of range
+ pg_input_error_message
+-------------------------------
+ ("date out of range",,,22008)
(1 row)
--
diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
index a034fbb346..cd1d79bad7 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -449,9 +449,9 @@ SELECT to_regnamespace('foo.bar');
-- Test soft-error API
SELECT pg_input_error_message('ng_catalog.pg_class', 'regclass');
- pg_input_error_message
------------------------------------------------
- relation "ng_catalog.pg_class" does not exist
+ pg_input_error_message
+-------------------------------------------------------------
+ ("relation ""ng_catalog.pg_class"" does not exist",,,42P01)
(1 row)
SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
@@ -461,75 +461,75 @@ SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
(1 row)
SELECT pg_input_error_message('no_such_config', 'regconfig');
- pg_input_error_message
------------------------------------------------------------
- text search configuration "no_such_config" does not exist
+ pg_input_error_message
+-------------------------------------------------------------------------
+ ("text search configuration ""no_such_config"" does not exist",,,42704)
(1 row)
SELECT pg_input_error_message('no_such_dictionary', 'regdictionary');
- pg_input_error_message
-------------------------------------------------------------
- text search dictionary "no_such_dictionary" does not exist
+ pg_input_error_message
+--------------------------------------------------------------------------
+ ("text search dictionary ""no_such_dictionary"" does not exist",,,42704)
(1 row)
SELECT pg_input_error_message('Nonexistent', 'regnamespace');
- pg_input_error_message
--------------------------------------
- schema "nonexistent" does not exist
+ pg_input_error_message
+---------------------------------------------------
+ ("schema ""nonexistent"" does not exist",,,3F000)
(1 row)
SELECT pg_input_error_message('ng_catalog.||/', 'regoper');
- pg_input_error_message
------------------------------------------
- operator does not exist: ng_catalog.||/
+ pg_input_error_message
+-----------------------------------------------------
+ ("operator does not exist: ng_catalog.||/",,,42883)
(1 row)
SELECT pg_input_error_message('-', 'regoper');
- pg_input_error_message
---------------------------------
- more than one operator named -
+ pg_input_error_message
+--------------------------------------------
+ ("more than one operator named -",,,42725)
(1 row)
SELECT pg_input_error_message('ng_catalog.+(int4,int4)', 'regoperator');
- pg_input_error_message
---------------------------------------------------
- operator does not exist: ng_catalog.+(int4,int4)
+ pg_input_error_message
+--------------------------------------------------------------
+ ("operator does not exist: ng_catalog.+(int4,int4)",,,42883)
(1 row)
SELECT pg_input_error_message('-', 'regoperator');
- pg_input_error_message
------------------------------
- expected a left parenthesis
+ pg_input_error_message
+-----------------------------------------
+ ("expected a left parenthesis",,,22P02)
(1 row)
SELECT pg_input_error_message('ng_catalog.now', 'regproc');
- pg_input_error_message
-------------------------------------------
- function "ng_catalog.now" does not exist
+ pg_input_error_message
+--------------------------------------------------------
+ ("function ""ng_catalog.now"" does not exist",,,42883)
(1 row)
SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
- pg_input_error_message
----------------------------------------------------
- function "ng_catalog.abs(numeric)" does not exist
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("function ""ng_catalog.abs(numeric)"" does not exist",,,42883)
(1 row)
SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
- pg_input_error_message
-------------------------------
- expected a right parenthesis
+ pg_input_error_message
+------------------------------------------
+ ("expected a right parenthesis",,,22P02)
(1 row)
SELECT pg_input_error_message('regress_regrole_test', 'regrole');
- pg_input_error_message
---------------------------------------------
- role "regress_regrole_test" does not exist
+ pg_input_error_message
+----------------------------------------------------------
+ ("role ""regress_regrole_test"" does not exist",,,42704)
(1 row)
SELECT pg_input_error_message('no_such_type', 'regtype');
- pg_input_error_message
-------------------------------------
- type "no_such_type" does not exist
+ pg_input_error_message
+--------------------------------------------------
+ ("type ""no_such_type"" does not exist",,,42704)
(1 row)
-- Some cases that should be soft errors, but are not yet
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 801d9e556b..a9fb5320e3 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -89,15 +89,15 @@ SELECT pg_input_is_valid('(1,zed)', 'complex');
(1 row)
SELECT pg_input_error_message('(1,zed)', 'complex');
- pg_input_error_message
--------------------------------------------------------
- invalid input syntax for type double precision: "zed"
+ pg_input_error_message
+---------------------------------------------------------------------
+ ("invalid input syntax for type double precision: ""zed""",,,22P02)
(1 row)
SELECT pg_input_error_message('(1,1e400)', 'complex');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
create temp table quadtable(f1 int, q quad);
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index f028c1f10f..28c78c960b 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -281,21 +281,21 @@ SELECT pg_input_is_valid(E'\\xDeAdBeE', 'bytea');
(1 row)
SELECT pg_input_error_message(E'\\xDeAdBeE', 'bytea');
- pg_input_error_message
-------------------------------------------------
- invalid hexadecimal data: odd number of digits
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid hexadecimal data: odd number of digits",,,22023)
(1 row)
SELECT pg_input_error_message(E'\\xDeAdBeEx', 'bytea');
- pg_input_error_message
---------------------------------
- invalid hexadecimal digit: "x"
+ pg_input_error_message
+----------------------------------------------
+ ("invalid hexadecimal digit: ""x""",,,22023)
(1 row)
SELECT pg_input_error_message(E'foo\\99bar', 'bytea');
- pg_input_error_message
--------------------------------------
- invalid input syntax for type bytea
+ pg_input_error_message
+-------------------------------------------------
+ ("invalid input syntax for type bytea",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/tid.out b/src/test/regress/expected/tid.out
index ff67ed43f0..7ee891cc1a 100644
--- a/src/test/regress/expected/tid.out
+++ b/src/test/regress/expected/tid.out
@@ -25,9 +25,9 @@ SELECT pg_input_is_valid('(0)', 'tid');
(1 row)
SELECT pg_input_error_message('(0)', 'tid');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type tid: "(0)"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type tid: ""(0)""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(0,-1)', 'tid');
@@ -37,9 +37,9 @@ SELECT pg_input_is_valid('(0,-1)', 'tid');
(1 row)
SELECT pg_input_error_message('(0,-1)', 'tid');
- pg_input_error_message
----------------------------------------------
- invalid input syntax for type tid: "(0,-1)"
+ pg_input_error_message
+-----------------------------------------------------------
+ ("invalid input syntax for type tid: ""(0,-1)""",,,22P02)
(1 row)
-- tests for functions related to TID handling
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index a44caededd..79de9377db 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -134,15 +134,15 @@ SELECT pg_input_is_valid('15:36:39 America/New_York', 'time');
(1 row)
SELECT pg_input_error_message('25:00:00', 'time');
- pg_input_error_message
-------------------------------------------------
- date/time field value out of range: "25:00:00"
+ pg_input_error_message
+--------------------------------------------------------------
+ ("date/time field value out of range: ""25:00:00""",,,22008)
(1 row)
SELECT pg_input_error_message('15:36:39 America/New_York', 'time');
- pg_input_error_message
------------------------------------------------------------------
- invalid input syntax for type time: "15:36:39 America/New_York"
+ pg_input_error_message
+-------------------------------------------------------------------------------
+ ("invalid input syntax for type time: ""15:36:39 America/New_York""",,,22007)
(1 row)
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index eef2f7001c..eafccc38ac 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -145,15 +145,15 @@ SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
(1 row)
SELECT pg_input_error_message('garbage', 'timestamp');
- pg_input_error_message
-----------------------------------------------------
- invalid input syntax for type timestamp: "garbage"
+ pg_input_error_message
+------------------------------------------------------------------
+ ("invalid input syntax for type timestamp: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
- pg_input_error_message
---------------------------------------------
- time zone "nehwon/lankhmar" not recognized
+ pg_input_error_message
+----------------------------------------------------------
+ ("time zone ""nehwon/lankhmar"" not recognized",,,22023)
(1 row)
-- Check date conversion and date arithmetic
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index b85a93a3c2..73c7d70455 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -196,15 +196,15 @@ SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
(1 row)
SELECT pg_input_error_message('garbage', 'timestamptz');
- pg_input_error_message
--------------------------------------------------------------------
- invalid input syntax for type timestamp with time zone: "garbage"
+ pg_input_error_message
+---------------------------------------------------------------------------------
+ ("invalid input syntax for type timestamp with time zone: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
- pg_input_error_message
---------------------------------------------
- time zone "nehwon/lankhmar" not recognized
+ pg_input_error_message
+----------------------------------------------------------
+ ("time zone ""nehwon/lankhmar"" not recognized",,,22023)
(1 row)
-- Check date conversion and date arithmetic
diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out
index 984285663b..665fa4495c 100644
--- a/src/test/regress/expected/timetz.out
+++ b/src/test/regress/expected/timetz.out
@@ -151,15 +151,15 @@ SELECT pg_input_is_valid('15:36:39 America/New_York', 'timetz');
(1 row)
SELECT pg_input_error_message('25:00:00 PDT', 'timetz');
- pg_input_error_message
-----------------------------------------------------
- date/time field value out of range: "25:00:00 PDT"
+ pg_input_error_message
+------------------------------------------------------------------
+ ("date/time field value out of range: ""25:00:00 PDT""",,,22008)
(1 row)
SELECT pg_input_error_message('15:36:39 America/New_York', 'timetz');
- pg_input_error_message
---------------------------------------------------------------------------------
- invalid input syntax for type time with time zone: "15:36:39 America/New_York"
+ pg_input_error_message
+----------------------------------------------------------------------------------------------
+ ("invalid input syntax for type time with time zone: ""15:36:39 America/New_York""",,,22007)
(1 row)
--
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
index a8785cd708..35b19814c4 100644
--- a/src/test/regress/expected/tstypes.out
+++ b/src/test/regress/expected/tstypes.out
@@ -103,9 +103,9 @@ SELECT pg_input_is_valid($$''$$, 'tsvector');
(1 row)
SELECT pg_input_error_message($$''$$, 'tsvector');
- pg_input_error_message
---------------------------------
- syntax error in tsvector: "''"
+ pg_input_error_message
+----------------------------------------------
+ ("syntax error in tsvector: ""''""",,,42601)
(1 row)
--Base tsquery test
@@ -405,15 +405,15 @@ SELECT pg_input_is_valid('foo!', 'tsquery');
(1 row)
SELECT pg_input_error_message('foo!', 'tsquery');
- pg_input_error_message
----------------------------------
- syntax error in tsquery: "foo!"
+ pg_input_error_message
+-----------------------------------------------
+ ("syntax error in tsquery: ""foo!""",,,42601)
(1 row)
SELECT pg_input_error_message('a <100000> b', 'tsquery');
- pg_input_error_message
----------------------------------------------------------------------------------------
- distance in phrase operator must be an integer value between zero and 16384 inclusive
+ pg_input_error_message
+---------------------------------------------------------------------------------------------------
+ ("distance in phrase operator must be an integer value between zero and 16384 inclusive",,,22023)
(1 row)
--comparisons
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index 0f47232009..3934626dda 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -47,9 +47,9 @@ SELECT pg_input_is_valid('11', 'uuid');
(1 row)
SELECT pg_input_error_message('11', 'uuid');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type uuid: "11"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type uuid: ""11""",,,22P02)
(1 row)
--inserting three input formats
diff --git a/src/test/regress/expected/varchar_1.out b/src/test/regress/expected/varchar_1.out
index 6690f81c0b..ee0e920e84 100644
--- a/src/test/regress/expected/varchar_1.out
+++ b/src/test/regress/expected/varchar_1.out
@@ -125,8 +125,8 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+ pg_input_error_message
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index e62f701943..754ff6b725 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -44,9 +44,9 @@ SELECT pg_input_is_valid('asdf', 'xid');
(1 row)
SELECT pg_input_error_message('0xffffffffff', 'xid');
- pg_input_error_message
----------------------------------------------------
- value "0xffffffffff" is out of range for type xid
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("value ""0xffffffffff"" is out of range for type xid",,,22003)
(1 row)
SELECT pg_input_is_valid('42', 'xid8');
@@ -62,9 +62,9 @@ SELECT pg_input_is_valid('asdf', 'xid8');
(1 row)
SELECT pg_input_error_message('0xffffffffffffffffffff', 'xid8');
- pg_input_error_message
---------------------------------------------------------------
- value "0xffffffffffffffffffff" is out of range for type xid8
+ pg_input_error_message
+----------------------------------------------------------------------------
+ ("value ""0xffffffffffffffffffff"" is out of range for type xid8",,,22003)
(1 row)
-- equality
@@ -224,9 +224,9 @@ select pg_input_is_valid('31:12:', 'pg_snapshot');
(1 row)
select pg_input_error_message('31:12:', 'pg_snapshot');
- pg_input_error_message
------------------------------------------------------
- invalid input syntax for type pg_snapshot: "31:12:"
+ pg_input_error_message
+-------------------------------------------------------------------
+ ("invalid input syntax for type pg_snapshot: ""31:12:""",,,22P02)
(1 row)
select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
@@ -236,9 +236,9 @@ select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
(1 row)
select pg_input_error_message('12:16:14,13', 'pg_snapshot');
- pg_input_error_message
-----------------------------------------------------------
- invalid input syntax for type pg_snapshot: "12:16:14,13"
+ pg_input_error_message
+------------------------------------------------------------------------
+ ("invalid input syntax for type pg_snapshot: ""12:16:14,13""",,,22P02)
(1 row)
create temp table snapshot_test (
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 3c357a9c7e..e1a8f09a42 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -32,9 +32,17 @@ SELECT pg_input_is_valid('<value>one</', 'xml');
(1 row)
SELECT pg_input_error_message('<value>one</', 'xml');
- pg_input_error_message
-------------------------
- invalid XML content
+ pg_input_error_message
+-----------------------------------------------------------------------
+ ("invalid XML content","line 1: expected '>' +
+ <value>one</ +
+ ^ +
+ line 1: Opening and ending tag mismatch: value line 1 and unparseable+
+ <value>one</ +
+ ^ +
+ line 1: chunk is not well balanced +
+ <value>one</ +
+ ^",,2200N)
(1 row)
SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
@@ -44,9 +52,9 @@ SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
(1 row)
SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_error_message
-----------------------------------------------
- invalid XML content: invalid XML declaration
+ pg_input_error_message
+--------------------------------------------------------------------------------------------------
+ ("invalid XML content: invalid XML declaration","standalone accepts only 'yes' or 'no'.",,2200N)
(1 row)
SELECT xmlcomment('test');
--
2.25.1
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
@ 2023-02-23 19:30 ` Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Nathan Bossart @ 2023-02-23 19:30 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Feb 23, 2023 at 10:40:48AM -0800, Nathan Bossart wrote:
> On Tue, Jan 10, 2023 at 03:41:12PM -0800, Nathan Bossart wrote:
>> My vote would be to redefine the existing pg_input_error_message() function
>> to return a record, but I recognize that this would inflate the patch quite
>> a bit due to all the existing uses in the tests. If this is the only
>> argument against this approach, I'm happy to help with the patch.
>
> Here's an attempt at this.
This seems to have made cfbot angry. Will post a new version shortly.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
@ 2023-02-23 21:47 ` Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Nathan Bossart @ 2023-02-23 21:47 UTC (permalink / raw)
To: Andrew Dunstan <[email protected]>; +Cc: Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Feb 23, 2023 at 11:30:38AM -0800, Nathan Bossart wrote:
> Will post a new version shortly.
As promised...
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-diff] v3-0001-add-details-to-pg_input_error_message.patch (88.9K, ../../20230223214723.GA610828@nathanxps13/2-v3-0001-add-details-to-pg_input_error_message.patch)
download | inline diff:
From b284ba24f771b6ccbf599839bdc813af718b91a1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Thu, 23 Feb 2023 10:31:24 -0800
Subject: [PATCH v3 1/1] add details to pg_input_error_message
---
contrib/cube/expected/cube.out | 6 +-
contrib/hstore/expected/hstore.out | 12 +--
contrib/intarray/expected/_int.out | 10 +--
contrib/isn/expected/isn.out | 10 +--
contrib/ltree/expected/ltree.out | 20 ++---
contrib/seg/expected/seg.out | 16 ++--
doc/src/sgml/func.sgml | 29 ++++---
src/backend/utils/adt/misc.c | 38 +++++++--
src/include/catalog/pg_proc.dat | 10 ++-
src/test/regress/expected/arrays.out | 6 +-
src/test/regress/expected/bit.out | 30 +++----
src/test/regress/expected/boolean.out | 6 +-
src/test/regress/expected/box.out | 12 +--
src/test/regress/expected/char.out | 6 +-
src/test/regress/expected/char_1.out | 6 +-
src/test/regress/expected/char_2.out | 6 +-
src/test/regress/expected/date.out | 12 +--
src/test/regress/expected/domain.out | 24 +++---
src/test/regress/expected/enum.out | 12 +--
src/test/regress/expected/float4.out | 6 +-
src/test/regress/expected/float8.out | 6 +-
src/test/regress/expected/geometry.out | 12 +--
src/test/regress/expected/inet.out | 18 ++---
src/test/regress/expected/int2.out | 18 ++---
src/test/regress/expected/int4.out | 6 +-
src/test/regress/expected/int8.out | 6 +-
src/test/regress/expected/interval.out | 12 +--
src/test/regress/expected/json.out | 6 +-
src/test/regress/expected/json_encoding.out | 2 +-
src/test/regress/expected/json_encoding_1.out | 6 +-
src/test/regress/expected/jsonb.out | 12 +--
src/test/regress/expected/jsonpath.out | 14 ++--
src/test/regress/expected/line.out | 30 +++----
src/test/regress/expected/lseg.out | 6 +-
src/test/regress/expected/macaddr.out | 12 +--
src/test/regress/expected/macaddr8.out | 12 +--
src/test/regress/expected/money.out | 12 +--
src/test/regress/expected/multirangetypes.out | 12 +--
src/test/regress/expected/numeric.out | 18 ++---
src/test/regress/expected/oid.out | 24 +++---
src/test/regress/expected/path.out | 12 +--
src/test/regress/expected/pg_lsn.out | 6 +-
src/test/regress/expected/point.out | 6 +-
src/test/regress/expected/polygon.out | 12 +--
src/test/regress/expected/privileges.out | 18 ++---
src/test/regress/expected/rangetypes.out | 30 +++----
src/test/regress/expected/regproc.out | 78 +++++++++----------
src/test/regress/expected/rowtypes.out | 12 +--
src/test/regress/expected/strings.out | 18 ++---
src/test/regress/expected/tid.out | 12 +--
src/test/regress/expected/time.out | 12 +--
src/test/regress/expected/timestamp.out | 12 +--
src/test/regress/expected/timestamptz.out | 12 +--
src/test/regress/expected/timetz.out | 12 +--
src/test/regress/expected/tstypes.out | 18 ++---
src/test/regress/expected/uuid.out | 6 +-
src/test/regress/expected/varchar.out | 6 +-
src/test/regress/expected/varchar_1.out | 6 +-
src/test/regress/expected/varchar_2.out | 6 +-
src/test/regress/expected/xid.out | 24 +++---
src/test/regress/expected/xml.out | 10 +--
src/test/regress/expected/xml_1.out | 4 +-
src/test/regress/expected/xml_2.out | 8 +-
src/test/regress/sql/xml.sql | 4 +-
64 files changed, 452 insertions(+), 413 deletions(-)
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index dc23e5ccc0..3bb42b063b 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -345,9 +345,9 @@ SELECT pg_input_is_valid('-1e-700', 'cube');
(1 row)
SELECT pg_input_error_message('-1e-700', 'cube');
- pg_input_error_message
------------------------------------------------------
- "-1e-700" is out of range for type double precision
+ pg_input_error_message
+-------------------------------------------------------------------
+ ("""-1e-700"" is out of range for type double precision",,,22003)
(1 row)
--
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index d6faa91867..d58fee585e 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -266,15 +266,15 @@ select pg_input_is_valid('a=b', 'hstore');
(1 row)
select pg_input_error_message('a=b', 'hstore');
- pg_input_error_message
-------------------------------------------------
- syntax error in hstore, near "b" at position 2
+ pg_input_error_message
+--------------------------------------------------------------
+ ("syntax error in hstore, near ""b"" at position 2",,,42601)
(1 row)
select pg_input_error_message(' =>b', 'hstore');
- pg_input_error_message
-------------------------------------------------
- syntax error in hstore, near "=" at position 1
+ pg_input_error_message
+--------------------------------------------------------------
+ ("syntax error in hstore, near ""="" at position 1",,,42601)
(1 row)
-- -> operator
diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out
index c953065a5c..73dfea605a 100644
--- a/contrib/intarray/expected/_int.out
+++ b/contrib/intarray/expected/_int.out
@@ -406,11 +406,11 @@ FROM (VALUES ('1&(2&(4&(5|6)))'),
('1#(2&(4&(5&6)))'),
('foo'))
AS a(str);
- query_int | ok | errmsg
------------------+----+--------------
- 1&(2&(4&(5|6))) | t |
- 1#(2&(4&(5&6))) | f | syntax error
- foo | f | syntax error
+ query_int | ok | errmsg
+-----------------+----+--------------------------
+ 1&(2&(4&(5|6))) | t | (,,,)
+ 1#(2&(4&(5&6))) | f | ("syntax error",,,42601)
+ foo | f | ("syntax error",,,42601)
(3 rows)
CREATE TABLE test__int( a int[] );
diff --git a/contrib/isn/expected/isn.out b/contrib/isn/expected/isn.out
index 72171b2790..cbe4166fd0 100644
--- a/contrib/isn/expected/isn.out
+++ b/contrib/isn/expected/isn.out
@@ -268,11 +268,11 @@ FROM (VALUES ('9780123456786', 'UPC'),
('postgresql...','EAN13'),
('9771234567003','ISSN'))
AS a(str,typ);
- isn | type | ok | errmsg
----------------+-------+----+--------------------------------------------------------
- 9780123456786 | UPC | f | cannot cast ISBN to UPC for number: "9780123456786"
- postgresql... | EAN13 | f | invalid input syntax for EAN13 number: "postgresql..."
- 9771234567003 | ISSN | t |
+ isn | type | ok | errmsg
+---------------+-------+----+----------------------------------------------------------------------
+ 9780123456786 | UPC | f | ("cannot cast ISBN to UPC for number: ""9780123456786""",,,22P02)
+ postgresql... | EAN13 | f | ("invalid input syntax for EAN13 number: ""postgresql...""",,,22P02)
+ 9771234567003 | ISSN | t | (,,,)
(3 rows)
--
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index d2a53b9f0c..cf925b2469 100644
--- a/contrib/ltree/expected/ltree.out
+++ b/contrib/ltree/expected/ltree.out
@@ -8111,15 +8111,15 @@ FROM (VALUES ('.2.3', 'ltree'),
('$tree & aWdf@*','ltxtquery'),
('!tree & aWdf@*','ltxtquery'))
AS a(str,typ);
- value | type | ok | errmsg
-----------------+-----------+----+------------------------------------
- .2.3 | ltree | f | ltree syntax error at character 1
- 1.2. | ltree | f | ltree syntax error
- 1.2.3 | ltree | t |
- @.2.3 | lquery | f | lquery syntax error at character 1
- 2.3 | lquery | f | lquery syntax error at character 1
- 1.2.3 | lquery | t |
- $tree & aWdf@* | ltxtquery | f | operand syntax error
- !tree & aWdf@* | ltxtquery | t |
+ value | type | ok | errmsg
+----------------+-----------+----+----------------------------------------------------------
+ .2.3 | ltree | f | ("ltree syntax error at character 1",,,42601)
+ 1.2. | ltree | f | ("ltree syntax error","Unexpected end of input.",,42601)
+ 1.2.3 | ltree | t | (,,,)
+ @.2.3 | lquery | f | ("lquery syntax error at character 1",,,42601)
+ 2.3 | lquery | f | ("lquery syntax error at character 1",,,42601)
+ 1.2.3 | lquery | t | (,,,)
+ $tree & aWdf@* | ltxtquery | f | ("operand syntax error",,,42601)
+ !tree & aWdf@* | ltxtquery | t | (,,,)
(8 rows)
diff --git a/contrib/seg/expected/seg.out b/contrib/seg/expected/seg.out
index 7a06113ed8..4b09bd1d4f 100644
--- a/contrib/seg/expected/seg.out
+++ b/contrib/seg/expected/seg.out
@@ -1283,13 +1283,13 @@ FROM unnest(ARRAY['-1 .. 1'::text,
'ABC',
'1 e7',
'1e700']) str;
- seg | ok | errmsg
-----------+----+---------------------------------------
- -1 .. 1 | t |
- 100(+-)1 | t |
- | f | bad seg representation
- ABC | f | bad seg representation
- 1 e7 | f | bad seg representation
- 1e700 | f | "1e700" is out of range for type real
+ seg | ok | errmsg
+----------+----+-------------------------------------------------------------------
+ -1 .. 1 | t | (,,,)
+ 100(+-)1 | t | (,,,)
+ | f | ("bad seg representation","syntax error at end of input",,42601)
+ ABC | f | ("bad seg representation","syntax error at or near ""A""",,42601)
+ 1 e7 | f | ("bad seg representation","syntax error at or near ""e""",,42601)
+ 1e700 | f | ("""1e700"" is out of range for type real",,,22003)
(6 rows)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0cbdf63632..76dade49db 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24781,13 +24781,17 @@ SELECT collation for ('foo' COLLATE "de_DE");
<parameter>string</parameter> <type>text</type>,
<parameter>type</parameter> <type>text</type>
)
- <returnvalue>text</returnvalue>
+ <returnvalue>record</returnvalue>
+ ( <parameter>message</parameter> <type>text</type>,
+ <parameter>detail</parameter> <type>text</type>,
+ <parameter>hint</parameter> <type>text</type>,
+ <parameter>sql_error_code</parameter> <type>text</type> )
</para>
<para>
Tests whether the given <parameter>string</parameter> is valid
- input for the specified data type; if not, return the error
- message that would have been thrown. If the input is valid, the
- result is NULL. The inputs are the same as
+ input for the specified data type; if not, return the details of
+ the error would have been thrown. If the input is valid, the
+ results are NULL. The inputs are the same as
for <function>pg_input_is_valid</function>.
</para>
<para>
@@ -24798,12 +24802,17 @@ SELECT collation for ('foo' COLLATE "de_DE");
directly.
</para>
<para>
- <literal>pg_input_error_message('42000000000', 'integer')</literal>
- <returnvalue>value "42000000000" is out of range for type integer</returnvalue>
- </para>
- <para>
- <literal>pg_input_error_message('1234.567', 'numeric(7,4)')</literal>
- <returnvalue>numeric field overflow</returnvalue>
+<programlisting>
+SELECT * FROM pg_input_error_message('42000000000', 'integer');
+ message | detail | hint | sql_error_code
+------------------------------------------------------+--------+------+----------------
+ value "42000000000" is out of range for type integer | | | 22003
+
+SELECT * FROM pg_input_error_message('1234.567', 'numeric(7,4)');
+ message | detail | hint | sql_error_code
+------------------------+-----------------------------------------------------------------------------------+------+----------------
+ numeric field overflow | A field with precision 7, scale 4 must round to an absolute value less than 10^3. | | 22003
+</programlisting>
</para></entry>
</row>
</tbody>
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index f95256efd3..a4f4b7fb90 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -662,7 +662,8 @@ pg_input_is_valid(PG_FUNCTION_ARGS)
/*
* pg_input_error_message - test whether string is valid input for datatype.
*
- * Returns NULL if OK, else the primary message string from the error.
+ * Returns NULL if OK, else the primary message, detail message, hint message,
+ * and sql error code from the error.
*
* This will only work usefully if the datatype's input function has been
* updated to return "soft" errors via errsave/ereturn.
@@ -673,19 +674,44 @@ pg_input_error_message(PG_FUNCTION_ARGS)
text *txt = PG_GETARG_TEXT_PP(0);
text *typname = PG_GETARG_TEXT_PP(1);
ErrorSaveContext escontext = {T_ErrorSaveContext};
+ TupleDesc tupdesc;
+ Datum values[4];
+ bool isnull[4];
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
/* Enable details_wanted */
escontext.details_wanted = true;
if (pg_input_is_valid_common(fcinfo, txt, typname,
&escontext))
- PG_RETURN_NULL();
+ memset(isnull, true, sizeof(isnull));
+ else
+ {
+ Assert(escontext.error_occurred);
+ Assert(escontext.error_data != NULL);
+ Assert(escontext.error_data->message != NULL);
+
+ memset(isnull, false, sizeof(isnull));
+
+ values[0] = CStringGetTextDatum(escontext.error_data->message);
- Assert(escontext.error_occurred);
- Assert(escontext.error_data != NULL);
- Assert(escontext.error_data->message != NULL);
+ if (escontext.error_data->detail != NULL)
+ values[1] = CStringGetTextDatum(escontext.error_data->detail);
+ else
+ isnull[1] = true;
+
+ if (escontext.error_data->hint != NULL)
+ values[2] = CStringGetTextDatum(escontext.error_data->hint);
+ else
+ isnull[2] = true;
+
+ values[3] = CStringGetTextDatum(
+ unpack_sql_state(escontext.error_data->sqlerrcode));
+ }
- PG_RETURN_TEXT_P(cstring_to_text(escontext.error_data->message));
+ return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
}
/* Common subroutine for the above */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e2a7642a2b..ecb5df2e53 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7118,9 +7118,13 @@
proname => 'pg_input_is_valid', provolatile => 's', prorettype => 'bool',
proargtypes => 'text text', prosrc => 'pg_input_is_valid' },
{ oid => '8051',
- descr => 'get error message if string is not valid input for data type',
- proname => 'pg_input_error_message', provolatile => 's', prorettype => 'text',
- proargtypes => 'text text', prosrc => 'pg_input_error_message' },
+ descr => 'get error details if string is not valid input for data type',
+ proname => 'pg_input_error_message', provolatile => 's', prorettype => 'record',
+ proargtypes => 'text text',
+ proallargtypes => '{text,text,text,text,text,text}',
+ proargmodes => '{i,i,o,o,o,o}',
+ proargnames => '{value,type_name,message,detail,hint,sql_error_code}',
+ prosrc => 'pg_input_error_message' },
{ oid => '1268',
descr => 'parse qualified identifier to array of identifiers',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index a2f9d7ed16..0e6a50dbca 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -202,9 +202,9 @@ SELECT pg_input_is_valid('{1,zed}', 'integer[]');
(1 row)
SELECT pg_input_error_message('{1,zed}', 'integer[]');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
-- test mixed slice/scalar subscripting
diff --git a/src/test/regress/expected/bit.out b/src/test/regress/expected/bit.out
index 209044713c..1b88327cb6 100644
--- a/src/test/regress/expected/bit.out
+++ b/src/test/regress/expected/bit.out
@@ -754,9 +754,9 @@ SELECT pg_input_is_valid('01010001', 'bit(10)');
(1 row)
SELECT pg_input_error_message('01010001', 'bit(10)');
- pg_input_error_message
--------------------------------------------------
- bit string length 8 does not match type bit(10)
+ pg_input_error_message
+-------------------------------------------------------------
+ ("bit string length 8 does not match type bit(10)",,,22026)
(1 row)
SELECT pg_input_is_valid('01010Z01', 'bit(8)');
@@ -766,9 +766,9 @@ SELECT pg_input_is_valid('01010Z01', 'bit(8)');
(1 row)
SELECT pg_input_error_message('01010Z01', 'bit(8)');
- pg_input_error_message
----------------------------------
- "Z" is not a valid binary digit
+ pg_input_error_message
+-----------------------------------------------
+ ("""Z"" is not a valid binary digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
@@ -778,9 +778,9 @@ SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
(1 row)
SELECT pg_input_error_message('x01010Z01', 'bit(32)');
- pg_input_error_message
---------------------------------------
- "Z" is not a valid hexadecimal digit
+ pg_input_error_message
+----------------------------------------------------
+ ("""Z"" is not a valid hexadecimal digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('01010Z01', 'varbit');
@@ -790,9 +790,9 @@ SELECT pg_input_is_valid('01010Z01', 'varbit');
(1 row)
SELECT pg_input_error_message('01010Z01', 'varbit');
- pg_input_error_message
----------------------------------
- "Z" is not a valid binary digit
+ pg_input_error_message
+-----------------------------------------------
+ ("""Z"" is not a valid binary digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('x01010Z01', 'varbit');
@@ -802,8 +802,8 @@ SELECT pg_input_is_valid('x01010Z01', 'varbit');
(1 row)
SELECT pg_input_error_message('x01010Z01', 'varbit');
- pg_input_error_message
---------------------------------------
- "Z" is not a valid hexadecimal digit
+ pg_input_error_message
+----------------------------------------------------
+ ("""Z"" is not a valid hexadecimal digit",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/boolean.out b/src/test/regress/expected/boolean.out
index 977124b20b..331a8d2890 100644
--- a/src/test/regress/expected/boolean.out
+++ b/src/test/regress/expected/boolean.out
@@ -156,9 +156,9 @@ SELECT pg_input_is_valid('asdf', 'bool');
(1 row)
SELECT pg_input_error_message('junk', 'bool');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type boolean: "junk"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input syntax for type boolean: ""junk""",,,22P02)
(1 row)
-- and, or, not in qualifications
diff --git a/src/test/regress/expected/box.out b/src/test/regress/expected/box.out
index 0d70194def..7e7cbfee9c 100644
--- a/src/test/regress/expected/box.out
+++ b/src/test/regress/expected/box.out
@@ -647,9 +647,9 @@ SELECT pg_input_is_valid('200', 'box');
(1 row)
SELECT pg_input_error_message('200', 'box');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type box: "200"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type box: ""200""",,,22P02)
(1 row)
SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
@@ -659,8 +659,8 @@ SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
(1 row)
SELECT pg_input_error_message('((200,300),(500, xyz))', 'box');
- pg_input_error_message
--------------------------------------------------------------
- invalid input syntax for type box: "((200,300),(500, xyz))"
+ pg_input_error_message
+---------------------------------------------------------------------------
+ ("invalid input syntax for type box: ""((200,300),(500, xyz))""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/char.out b/src/test/regress/expected/char.out
index 199001b2fe..f565ea844f 100644
--- a/src/test/regress/expected/char.out
+++ b/src/test/regress/expected/char.out
@@ -133,9 +133,9 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+ pg_input_error_message
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/char_1.out b/src/test/regress/expected/char_1.out
index 3dcb0daa0d..32e4b28f6c 100644
--- a/src/test/regress/expected/char_1.out
+++ b/src/test/regress/expected/char_1.out
@@ -133,9 +133,9 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+ pg_input_error_message
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/char_2.out b/src/test/regress/expected/char_2.out
index dd5d34fe8d..c7221f6d44 100644
--- a/src/test/regress/expected/char_2.out
+++ b/src/test/regress/expected/char_2.out
@@ -133,9 +133,9 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+ pg_input_error_message
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index c0dec448e1..d3fca737eb 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -860,15 +860,15 @@ SELECT pg_input_is_valid('6874898-01-01', 'date');
(1 row)
SELECT pg_input_error_message('garbage', 'date');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type date: "garbage"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input syntax for type date: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('6874898-01-01', 'date');
- pg_input_error_message
-------------------------------------
- date out of range: "6874898-01-01"
+ pg_input_error_message
+--------------------------------------------------
+ ("date out of range: ""6874898-01-01""",,,22008)
(1 row)
RESET datestyle;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 25f6bb9e1f..c4adce88c1 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -109,27 +109,27 @@ select pg_input_is_valid('-1', 'positiveint');
(1 row)
select pg_input_error_message('junk', 'positiveint');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type integer: "junk"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input syntax for type integer: ""junk""",,,22P02)
(1 row)
select pg_input_error_message('-1', 'positiveint');
- pg_input_error_message
-----------------------------------------------------------------------------
- value for domain positiveint violates check constraint "positiveint_check"
+ pg_input_error_message
+------------------------------------------------------------------------------------------
+ ("value for domain positiveint violates check constraint ""positiveint_check""",,,23514)
(1 row)
select pg_input_error_message('junk', 'weirdfloat');
- pg_input_error_message
---------------------------------------------------------
- invalid input syntax for type double precision: "junk"
+ pg_input_error_message
+----------------------------------------------------------------------
+ ("invalid input syntax for type double precision: ""junk""",,,22P02)
(1 row)
select pg_input_error_message('0.01', 'weirdfloat');
- pg_input_error_message
---------------------------------------------------------------------------
- value for domain weirdfloat violates check constraint "weirdfloat_check"
+ pg_input_error_message
+----------------------------------------------------------------------------------------
+ ("value for domain weirdfloat violates check constraint ""weirdfloat_check""",,,23514)
(1 row)
-- We currently can't trap errors raised in the CHECK expression itself
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 4b45fcf8f0..c3cf9625b4 100644
--- a/src/test/regress/expected/enum.out
+++ b/src/test/regress/expected/enum.out
@@ -38,15 +38,15 @@ SELECT pg_input_is_valid('mauve', 'rainbow');
(1 row)
SELECT pg_input_error_message('mauve', 'rainbow');
- pg_input_error_message
------------------------------------------------
- invalid input value for enum rainbow: "mauve"
+ pg_input_error_message
+-------------------------------------------------------------
+ ("invalid input value for enum rainbow: ""mauve""",,,22P02)
(1 row)
SELECT pg_input_error_message(repeat('too_long', 32), 'rainbow');
- pg_input_error_message
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- invalid input value for enum rainbow: "too_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_long"
+ pg_input_error_message
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ("invalid input value for enum rainbow: ""too_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_long""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 1d7090a90d..7b0f71a23d 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -101,9 +101,9 @@ SELECT pg_input_is_valid('1e400', 'float4');
(1 row)
SELECT pg_input_error_message('1e400', 'float4');
- pg_input_error_message
----------------------------------------
- "1e400" is out of range for type real
+ pg_input_error_message
+-----------------------------------------------------
+ ("""1e400"" is out of range for type real",,,22003)
(1 row)
-- special inputs
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 2b25784f7f..f6c8941438 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -88,9 +88,9 @@ SELECT pg_input_is_valid('1e4000', 'float8');
(1 row)
SELECT pg_input_error_message('1e4000', 'float8');
- pg_input_error_message
-----------------------------------------------------
- "1e4000" is out of range for type double precision
+ pg_input_error_message
+------------------------------------------------------------------
+ ("""1e4000"" is out of range for type double precision",,,22003)
(1 row)
-- special inputs
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 291cacdf4f..54bc17a6d7 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -5303,9 +5303,9 @@ SELECT pg_input_is_valid('(1', 'circle');
(1 row)
SELECT pg_input_error_message('1,', 'circle');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type circle: "1,"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type circle: ""1,""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(1,2),-1', 'circle');
@@ -5315,8 +5315,8 @@ SELECT pg_input_is_valid('(1,2),-1', 'circle');
(1 row)
SELECT pg_input_error_message('(1,2),-1', 'circle');
- pg_input_error_message
---------------------------------------------------
- invalid input syntax for type circle: "(1,2),-1"
+ pg_input_error_message
+----------------------------------------------------------------
+ ("invalid input syntax for type circle: ""(1,2),-1""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
index c9f466ac1d..957c0003fc 100644
--- a/src/test/regress/expected/inet.out
+++ b/src/test/regress/expected/inet.out
@@ -1064,9 +1064,9 @@ SELECT pg_input_is_valid('1234', 'cidr');
(1 row)
SELECT pg_input_error_message('1234', 'cidr');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type cidr: "1234"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type cidr: ""1234""",,,22P02)
(1 row)
SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
@@ -1076,9 +1076,9 @@ SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
(1 row)
SELECT pg_input_error_message('192.168.198.200/24', 'cidr');
- pg_input_error_message
-------------------------------------------
- invalid cidr value: "192.168.198.200/24"
+ pg_input_error_message
+----------------------------------------------------------------------------------------------
+ ("invalid cidr value: ""192.168.198.200/24""","Value has bits set to right of mask.",,22P02)
(1 row)
SELECT pg_input_is_valid('1234', 'inet');
@@ -1088,8 +1088,8 @@ SELECT pg_input_is_valid('1234', 'inet');
(1 row)
SELECT pg_input_error_message('1234', 'inet');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type inet: "1234"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type inet: ""1234""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 73b4ee023c..d42b15c423 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -65,9 +65,9 @@ SELECT pg_input_is_valid('50000', 'int2');
(1 row)
SELECT pg_input_error_message('50000', 'int2');
- pg_input_error_message
--------------------------------------------------
- value "50000" is out of range for type smallint
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""50000"" is out of range for type smallint",,,22003)
(1 row)
-- While we're here, check int2vector as well
@@ -78,15 +78,15 @@ SELECT pg_input_is_valid(' 1 3 5 ', 'int2vector');
(1 row)
SELECT pg_input_error_message('1 asdf', 'int2vector');
- pg_input_error_message
-------------------------------------------------
- invalid input syntax for type smallint: "asdf"
+ pg_input_error_message
+--------------------------------------------------------------
+ ("invalid input syntax for type smallint: ""asdf""",,,22P02)
(1 row)
SELECT pg_input_error_message('50000', 'int2vector');
- pg_input_error_message
--------------------------------------------------
- value "50000" is out of range for type smallint
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""50000"" is out of range for type smallint",,,22003)
(1 row)
SELECT * FROM INT2_TBL AS f(a, b);
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index 9c20574ca5..683415a948 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -65,9 +65,9 @@ SELECT pg_input_is_valid('1000000000000', 'int4');
(1 row)
SELECT pg_input_error_message('1000000000000', 'int4');
- pg_input_error_message
---------------------------------------------------------
- value "1000000000000" is out of range for type integer
+ pg_input_error_message
+----------------------------------------------------------------------
+ ("value ""1000000000000"" is out of range for type integer",,,22003)
(1 row)
SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index d9dca64e88..e081cb6b00 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -62,9 +62,9 @@ SELECT pg_input_is_valid('10000000000000000000', 'int8');
(1 row)
SELECT pg_input_error_message('10000000000000000000', 'int8');
- pg_input_error_message
---------------------------------------------------------------
- value "10000000000000000000" is out of range for type bigint
+ pg_input_error_message
+----------------------------------------------------------------------------
+ ("value ""10000000000000000000"" is out of range for type bigint",,,22003)
(1 row)
-- int8/int8 cmp
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a154840c85..2a7b49c36c 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -92,15 +92,15 @@ SELECT pg_input_is_valid('@ 30 eons ago', 'interval');
(1 row)
SELECT pg_input_error_message('garbage', 'interval');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type interval: "garbage"
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("invalid input syntax for type interval: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('@ 30 eons ago', 'interval');
- pg_input_error_message
----------------------------------------------------------
- invalid input syntax for type interval: "@ 30 eons ago"
+ pg_input_error_message
+-----------------------------------------------------------------------
+ ("invalid input syntax for type interval: ""@ 30 eons ago""",,,22007)
(1 row)
-- test interval operators
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index af96ce4180..a3096c2c2e 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -334,9 +334,9 @@ select pg_input_is_valid('{"a":true', 'json');
(1 row)
select pg_input_error_message('{"a":true', 'json');
- pg_input_error_message
-------------------------------------
- invalid input syntax for type json
+ pg_input_error_message
+--------------------------------------------------------------------------------------
+ ("invalid input syntax for type json","The input string ended unexpectedly.",,22P02)
(1 row)
--constructors
diff --git a/src/test/regress/expected/json_encoding.out b/src/test/regress/expected/json_encoding.out
index 083621fb21..54a5bb75f5 100644
--- a/src/test/regress/expected/json_encoding.out
+++ b/src/test/regress/expected/json_encoding.out
@@ -264,6 +264,6 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
select pg_input_error_message('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
pg_input_error_message
------------------------
-
+ (,,,)
(1 row)
diff --git a/src/test/regress/expected/json_encoding_1.out b/src/test/regress/expected/json_encoding_1.out
index 021d226f8d..cb1aec0dfa 100644
--- a/src/test/regress/expected/json_encoding_1.out
+++ b/src/test/regress/expected/json_encoding_1.out
@@ -258,8 +258,8 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
-- soft error for input-time failure
select pg_input_error_message('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
- pg_input_error_message
--------------------------------------
- unsupported Unicode escape sequence
+ pg_input_error_message
+-----------------------------------------------------------------------------------------------------------------------------------
+ ("unsupported Unicode escape sequence","Unicode escape value could not be translated to the server's encoding SQL_ASCII.",,22P05)
(1 row)
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index d3248aa0fd..dc125b1b56 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -324,15 +324,15 @@ select pg_input_is_valid('{"a":true', 'jsonb');
(1 row)
select pg_input_error_message('{"a":true', 'jsonb');
- pg_input_error_message
-------------------------------------
- invalid input syntax for type json
+ pg_input_error_message
+--------------------------------------------------------------------------------------
+ ("invalid input syntax for type json","The input string ended unexpectedly.",,22P02)
(1 row)
select pg_input_error_message('{"a":1e1000000}', 'jsonb');
- pg_input_error_message
---------------------------------
- value overflows numeric format
+ pg_input_error_message
+--------------------------------------------
+ ("value overflows numeric format",,,22003)
(1 row)
-- make sure jsonb is passed through json generators without being escaped
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ca0cdf1ab2..87a4141f3a 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -1041,12 +1041,12 @@ FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text,
'@ + 1',
'00',
'1a']) str;
- jsonpath | ok | errmsg
--------------------------------------------+----+-----------------------------------------------------------------------
- $ ? (@ like_regex "pattern" flag "smixq") | t |
- $ ? (@ like_regex "pattern" flag "a") | f | invalid input syntax for type jsonpath
- @ + 1 | f | @ is not allowed in root expressions
- 00 | f | trailing junk after numeric literal at or near "00" of jsonpath input
- 1a | f | trailing junk after numeric literal at or near "1a" of jsonpath input
+ jsonpath | ok | errmsg
+-------------------------------------------+----+----------------------------------------------------------------------------------------------------------------
+ $ ? (@ like_regex "pattern" flag "smixq") | t | (,,,)
+ $ ? (@ like_regex "pattern" flag "a") | f | ("invalid input syntax for type jsonpath","Unrecognized flag character ""a"" in LIKE_REGEX predicate.",,42601)
+ @ + 1 | f | ("@ is not allowed in root expressions",,,42601)
+ 00 | f | ("trailing junk after numeric literal at or near ""00"" of jsonpath input",,,42601)
+ 1a | f | ("trailing junk after numeric literal at or near ""1a"" of jsonpath input",,,42601)
(5 rows)
diff --git a/src/test/regress/expected/line.out b/src/test/regress/expected/line.out
index 6baea8fdbd..4f67aee728 100644
--- a/src/test/regress/expected/line.out
+++ b/src/test/regress/expected/line.out
@@ -93,9 +93,9 @@ SELECT pg_input_is_valid('{1, 1}', 'line');
(1 row)
SELECT pg_input_error_message('{1, 1}', 'line');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type line: "{1, 1}"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type line: ""{1, 1}""",,,22P02)
(1 row)
SELECT pg_input_is_valid('{0, 0, 0}', 'line');
@@ -105,9 +105,9 @@ SELECT pg_input_is_valid('{0, 0, 0}', 'line');
(1 row)
SELECT pg_input_error_message('{0, 0, 0}', 'line');
- pg_input_error_message
----------------------------------------------------------
- invalid line specification: A and B cannot both be zero
+ pg_input_error_message
+---------------------------------------------------------------------
+ ("invalid line specification: A and B cannot both be zero",,,22P02)
(1 row)
SELECT pg_input_is_valid('{1, 1, a}', 'line');
@@ -117,9 +117,9 @@ SELECT pg_input_is_valid('{1, 1, a}', 'line');
(1 row)
SELECT pg_input_error_message('{1, 1, a}', 'line');
- pg_input_error_message
--------------------------------------------------
- invalid input syntax for type line: "{1, 1, a}"
+ pg_input_error_message
+---------------------------------------------------------------
+ ("invalid input syntax for type line: ""{1, 1, a}""",,,22P02)
(1 row)
SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
@@ -129,9 +129,9 @@ SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
(1 row)
SELECT pg_input_error_message('{1, 1, 1e400}', 'line');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
@@ -141,8 +141,8 @@ SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
(1 row)
SELECT pg_input_error_message('(1, 1), (1, 1e400)', 'line');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
diff --git a/src/test/regress/expected/lseg.out b/src/test/regress/expected/lseg.out
index afb323fe04..14e4f73ed1 100644
--- a/src/test/regress/expected/lseg.out
+++ b/src/test/regress/expected/lseg.out
@@ -50,8 +50,8 @@ SELECT pg_input_is_valid('[(1,2),(3)]', 'lseg');
(1 row)
SELECT pg_input_error_message('[(1,2),(3)]', 'lseg');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type lseg: "[(1,2),(3)]"
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("invalid input syntax for type lseg: ""[(1,2),(3)]""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/macaddr.out b/src/test/regress/expected/macaddr.out
index cb646af79b..ca7c5f71ef 100644
--- a/src/test/regress/expected/macaddr.out
+++ b/src/test/regress/expected/macaddr.out
@@ -166,9 +166,9 @@ SELECT pg_input_is_valid('08:00:2b:01:02:ZZ', 'macaddr');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:ZZ', 'macaddr');
- pg_input_error_message
-------------------------------------------------------------
- invalid input syntax for type macaddr: "08:00:2b:01:02:ZZ"
+ pg_input_error_message
+--------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr: ""08:00:2b:01:02:ZZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
@@ -178,8 +178,8 @@ SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:', 'macaddr');
- pg_input_error_message
-----------------------------------------------------------
- invalid input syntax for type macaddr: "08:00:2b:01:02:"
+ pg_input_error_message
+------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr: ""08:00:2b:01:02:""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/macaddr8.out b/src/test/regress/expected/macaddr8.out
index bf681988f8..fa5308bb1f 100644
--- a/src/test/regress/expected/macaddr8.out
+++ b/src/test/regress/expected/macaddr8.out
@@ -360,9 +360,9 @@ SELECT pg_input_is_valid('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
- pg_input_error_message
--------------------------------------------------------------------
- invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:ZZ"
+ pg_input_error_message
+---------------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr8: ""08:00:2b:01:02:03:04:ZZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
@@ -372,8 +372,8 @@ SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
(1 row)
SELECT pg_input_error_message('08:00:2b:01:02:03:04:', 'macaddr8');
- pg_input_error_message
------------------------------------------------------------------
- invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:"
+ pg_input_error_message
+-------------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr8: ""08:00:2b:01:02:03:04:""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index 46b2eab51a..574cbd17a5 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -339,9 +339,9 @@ SELECT pg_input_is_valid('\x0001', 'money');
(1 row)
SELECT pg_input_error_message('\x0001', 'money');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type money: "\x0001"
+ pg_input_error_message
+--------------------------------------------------------------
+ ("invalid input syntax for type money: ""\\x0001""",,,22P02)
(1 row)
SELECT pg_input_is_valid('192233720368547758.07', 'money');
@@ -351,9 +351,9 @@ SELECT pg_input_is_valid('192233720368547758.07', 'money');
(1 row)
SELECT pg_input_error_message('192233720368547758.07', 'money');
- pg_input_error_message
---------------------------------------------------------------
- value "192233720368547758.07" is out of range for type money
+ pg_input_error_message
+----------------------------------------------------------------------------
+ ("value ""192233720368547758.07"" is out of range for type money",,,22003)
(1 row)
-- documented minimums and maximums
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index e70896b754..79137d14bb 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -288,9 +288,9 @@ select pg_input_is_valid('{[1,2], [4,5]', 'int4multirange');
(1 row)
select pg_input_error_message('{[1,2], [4,5]', 'int4multirange');
- pg_input_error_message
------------------------------------------------
- malformed multirange literal: "{[1,2], [4,5]"
+ pg_input_error_message
+---------------------------------------------------------------------------------------
+ ("malformed multirange literal: ""{[1,2], [4,5]""","Unexpected end of input.",,22P02)
(1 row)
select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
@@ -300,9 +300,9 @@ select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
(1 row)
select pg_input_error_message('{[1,2], [4,zed]}', 'int4multirange');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 56a3f3630a..cee8603dad 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2313,9 +2313,9 @@ SELECT pg_input_is_valid('1e400000', 'numeric');
(1 row)
SELECT pg_input_error_message('1e400000', 'numeric');
- pg_input_error_message
---------------------------------
- value overflows numeric format
+ pg_input_error_message
+--------------------------------------------
+ ("value overflows numeric format",,,22003)
(1 row)
SELECT pg_input_is_valid('1234.567', 'numeric(8,4)');
@@ -2331,15 +2331,15 @@ SELECT pg_input_is_valid('1234.567', 'numeric(7,4)');
(1 row)
SELECT pg_input_error_message('1234.567', 'numeric(7,4)');
- pg_input_error_message
-------------------------
- numeric field overflow
+ pg_input_error_message
+-----------------------------------------------------------------------------------------------------------------------
+ ("numeric field overflow","A field with precision 7, scale 4 must round to an absolute value less than 10^3.",,22003)
(1 row)
SELECT pg_input_error_message('0x1234.567', 'numeric');
- pg_input_error_message
------------------------------------------------------
- invalid input syntax for type numeric: "0x1234.567"
+ pg_input_error_message
+-------------------------------------------------------------------
+ ("invalid input syntax for type numeric: ""0x1234.567""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index b664bab5f9..2a4c3b08df 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -79,9 +79,9 @@ SELECT pg_input_is_valid('01XYZ', 'oid');
(1 row)
SELECT pg_input_error_message('01XYZ', 'oid');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type oid: "01XYZ"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type oid: ""01XYZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('9999999999', 'oid');
@@ -91,9 +91,9 @@ SELECT pg_input_is_valid('9999999999', 'oid');
(1 row)
SELECT pg_input_error_message('9999999999', 'oid');
- pg_input_error_message
--------------------------------------------------
- value "9999999999" is out of range for type oid
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""9999999999"" is out of range for type oid",,,22003)
(1 row)
-- While we're here, check oidvector as well
@@ -110,9 +110,9 @@ SELECT pg_input_is_valid('01 01XYZ', 'oidvector');
(1 row)
SELECT pg_input_error_message('01 01XYZ', 'oidvector');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type oid: "XYZ"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type oid: ""XYZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('01 9999999999', 'oidvector');
@@ -122,9 +122,9 @@ SELECT pg_input_is_valid('01 9999999999', 'oidvector');
(1 row)
SELECT pg_input_error_message('01 9999999999', 'oidvector');
- pg_input_error_message
--------------------------------------------------
- value "9999999999" is out of range for type oid
+ pg_input_error_message
+---------------------------------------------------------------
+ ("value ""9999999999"" is out of range for type oid",,,22003)
(1 row)
SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
diff --git a/src/test/regress/expected/path.out b/src/test/regress/expected/path.out
index 529a5e6fc2..2886091675 100644
--- a/src/test/regress/expected/path.out
+++ b/src/test/regress/expected/path.out
@@ -88,9 +88,9 @@ SELECT pg_input_is_valid('[(1,2),(3)]', 'path');
(1 row)
SELECT pg_input_error_message('[(1,2),(3)]', 'path');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type path: "[(1,2),(3)]"
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("invalid input syntax for type path: ""[(1,2),(3)]""",,,22P02)
(1 row)
SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
@@ -100,8 +100,8 @@ SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
(1 row)
SELECT pg_input_error_message('[(1,2,6),(3,4,6)]', 'path');
- pg_input_error_message
----------------------------------------------------------
- invalid input syntax for type path: "[(1,2,6),(3,4,6)]"
+ pg_input_error_message
+-----------------------------------------------------------------------
+ ("invalid input syntax for type path: ""[(1,2,6),(3,4,6)]""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/pg_lsn.out b/src/test/regress/expected/pg_lsn.out
index 01501f8c9b..7dfa0bae8b 100644
--- a/src/test/regress/expected/pg_lsn.out
+++ b/src/test/regress/expected/pg_lsn.out
@@ -34,9 +34,9 @@ SELECT pg_input_is_valid('16AE7F7', 'pg_lsn');
(1 row)
SELECT pg_input_error_message('16AE7F7', 'pg_lsn');
- pg_input_error_message
--------------------------------------------------
- invalid input syntax for type pg_lsn: "16AE7F7"
+ pg_input_error_message
+---------------------------------------------------------------
+ ("invalid input syntax for type pg_lsn: ""16AE7F7""",,,22P02)
(1 row)
-- Min/Max aggregation
diff --git a/src/test/regress/expected/point.out b/src/test/regress/expected/point.out
index a716ceb881..5a5b1ac4ef 100644
--- a/src/test/regress/expected/point.out
+++ b/src/test/regress/expected/point.out
@@ -471,8 +471,8 @@ SELECT pg_input_is_valid('1,y', 'point');
(1 row)
SELECT pg_input_error_message('1,y', 'point');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type point: "1,y"
+ pg_input_error_message
+----------------------------------------------------------
+ ("invalid input syntax for type point: ""1,y""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/polygon.out b/src/test/regress/expected/polygon.out
index c7d565ad53..ef31e4c631 100644
--- a/src/test/regress/expected/polygon.out
+++ b/src/test/regress/expected/polygon.out
@@ -314,9 +314,9 @@ SELECT pg_input_is_valid('(2.0,0.8,0.1)', 'polygon');
(1 row)
SELECT pg_input_error_message('(2.0,0.8,0.1)', 'polygon');
- pg_input_error_message
---------------------------------------------------------
- invalid input syntax for type polygon: "(2.0,0.8,0.1)"
+ pg_input_error_message
+----------------------------------------------------------------------
+ ("invalid input syntax for type polygon: ""(2.0,0.8,0.1)""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
@@ -326,8 +326,8 @@ SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
(1 row)
SELECT pg_input_error_message('(2.0,xyz)', 'polygon');
- pg_input_error_message
-----------------------------------------------------
- invalid input syntax for type polygon: "(2.0,xyz)"
+ pg_input_error_message
+------------------------------------------------------------------
+ ("invalid input syntax for type polygon: ""(2.0,xyz)""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..c2e82f39e7 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2247,9 +2247,9 @@ SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem');
(1 row)
SELECT pg_input_error_message('regress_priv_user1=r/', 'aclitem');
- pg_input_error_message
----------------------------------
- a name must follow the "/" sign
+ pg_input_error_message
+-----------------------------------------------
+ ("a name must follow the ""/"" sign",,,22P02)
(1 row)
SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem');
@@ -2259,9 +2259,9 @@ SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem')
(1 row)
SELECT pg_input_error_message('regress_priv_user1=r/regress_no_such_user', 'aclitem');
- pg_input_error_message
---------------------------------------------
- role "regress_no_such_user" does not exist
+ pg_input_error_message
+----------------------------------------------------------
+ ("role ""regress_no_such_user"" does not exist",,,42704)
(1 row)
SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
@@ -2271,9 +2271,9 @@ SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
(1 row)
SELECT pg_input_error_message('regress_priv_user1=rY', 'aclitem');
- pg_input_error_message
-----------------------------------------------------------
- invalid mode character: must be one of "arwdDxtXUCTcsAm"
+ pg_input_error_message
+------------------------------------------------------------------------
+ ("invalid mode character: must be one of ""arwdDxtXUCTcsAm""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index a3e9e447af..e3f1029555 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -189,9 +189,9 @@ select pg_input_is_valid('(1,4', 'int4range');
(1 row)
select pg_input_error_message('(1,4', 'int4range');
- pg_input_error_message
----------------------------------
- malformed range literal: "(1,4"
+ pg_input_error_message
+-------------------------------------------------------------------------
+ ("malformed range literal: ""(1,4""","Unexpected end of input.",,22P02)
(1 row)
select pg_input_is_valid('(4,1)', 'int4range');
@@ -201,9 +201,9 @@ select pg_input_is_valid('(4,1)', 'int4range');
(1 row)
select pg_input_error_message('(4,1)', 'int4range');
- pg_input_error_message
--------------------------------------------------------------------
- range lower bound must be less than or equal to range upper bound
+ pg_input_error_message
+-------------------------------------------------------------------------------
+ ("range lower bound must be less than or equal to range upper bound",,,22000)
(1 row)
select pg_input_is_valid('(4,zed)', 'int4range');
@@ -213,9 +213,9 @@ select pg_input_is_valid('(4,zed)', 'int4range');
(1 row)
select pg_input_error_message('(4,zed)', 'int4range');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
select pg_input_is_valid('[1,2147483647]', 'int4range');
@@ -225,9 +225,9 @@ select pg_input_is_valid('[1,2147483647]', 'int4range');
(1 row)
select pg_input_error_message('[1,2147483647]', 'int4range');
- pg_input_error_message
-------------------------
- integer out of range
+ pg_input_error_message
+----------------------------------
+ ("integer out of range",,,22003)
(1 row)
select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
@@ -237,9 +237,9 @@ select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
(1 row)
select pg_input_error_message('[2000-01-01,5874897-12-31]', 'daterange');
- pg_input_error_message
-------------------------
- date out of range
+ pg_input_error_message
+-------------------------------
+ ("date out of range",,,22008)
(1 row)
--
diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
index a034fbb346..cd1d79bad7 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -449,9 +449,9 @@ SELECT to_regnamespace('foo.bar');
-- Test soft-error API
SELECT pg_input_error_message('ng_catalog.pg_class', 'regclass');
- pg_input_error_message
------------------------------------------------
- relation "ng_catalog.pg_class" does not exist
+ pg_input_error_message
+-------------------------------------------------------------
+ ("relation ""ng_catalog.pg_class"" does not exist",,,42P01)
(1 row)
SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
@@ -461,75 +461,75 @@ SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
(1 row)
SELECT pg_input_error_message('no_such_config', 'regconfig');
- pg_input_error_message
------------------------------------------------------------
- text search configuration "no_such_config" does not exist
+ pg_input_error_message
+-------------------------------------------------------------------------
+ ("text search configuration ""no_such_config"" does not exist",,,42704)
(1 row)
SELECT pg_input_error_message('no_such_dictionary', 'regdictionary');
- pg_input_error_message
-------------------------------------------------------------
- text search dictionary "no_such_dictionary" does not exist
+ pg_input_error_message
+--------------------------------------------------------------------------
+ ("text search dictionary ""no_such_dictionary"" does not exist",,,42704)
(1 row)
SELECT pg_input_error_message('Nonexistent', 'regnamespace');
- pg_input_error_message
--------------------------------------
- schema "nonexistent" does not exist
+ pg_input_error_message
+---------------------------------------------------
+ ("schema ""nonexistent"" does not exist",,,3F000)
(1 row)
SELECT pg_input_error_message('ng_catalog.||/', 'regoper');
- pg_input_error_message
------------------------------------------
- operator does not exist: ng_catalog.||/
+ pg_input_error_message
+-----------------------------------------------------
+ ("operator does not exist: ng_catalog.||/",,,42883)
(1 row)
SELECT pg_input_error_message('-', 'regoper');
- pg_input_error_message
---------------------------------
- more than one operator named -
+ pg_input_error_message
+--------------------------------------------
+ ("more than one operator named -",,,42725)
(1 row)
SELECT pg_input_error_message('ng_catalog.+(int4,int4)', 'regoperator');
- pg_input_error_message
---------------------------------------------------
- operator does not exist: ng_catalog.+(int4,int4)
+ pg_input_error_message
+--------------------------------------------------------------
+ ("operator does not exist: ng_catalog.+(int4,int4)",,,42883)
(1 row)
SELECT pg_input_error_message('-', 'regoperator');
- pg_input_error_message
------------------------------
- expected a left parenthesis
+ pg_input_error_message
+-----------------------------------------
+ ("expected a left parenthesis",,,22P02)
(1 row)
SELECT pg_input_error_message('ng_catalog.now', 'regproc');
- pg_input_error_message
-------------------------------------------
- function "ng_catalog.now" does not exist
+ pg_input_error_message
+--------------------------------------------------------
+ ("function ""ng_catalog.now"" does not exist",,,42883)
(1 row)
SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
- pg_input_error_message
----------------------------------------------------
- function "ng_catalog.abs(numeric)" does not exist
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("function ""ng_catalog.abs(numeric)"" does not exist",,,42883)
(1 row)
SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
- pg_input_error_message
-------------------------------
- expected a right parenthesis
+ pg_input_error_message
+------------------------------------------
+ ("expected a right parenthesis",,,22P02)
(1 row)
SELECT pg_input_error_message('regress_regrole_test', 'regrole');
- pg_input_error_message
---------------------------------------------
- role "regress_regrole_test" does not exist
+ pg_input_error_message
+----------------------------------------------------------
+ ("role ""regress_regrole_test"" does not exist",,,42704)
(1 row)
SELECT pg_input_error_message('no_such_type', 'regtype');
- pg_input_error_message
-------------------------------------
- type "no_such_type" does not exist
+ pg_input_error_message
+--------------------------------------------------
+ ("type ""no_such_type"" does not exist",,,42704)
(1 row)
-- Some cases that should be soft errors, but are not yet
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 801d9e556b..a9fb5320e3 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -89,15 +89,15 @@ SELECT pg_input_is_valid('(1,zed)', 'complex');
(1 row)
SELECT pg_input_error_message('(1,zed)', 'complex');
- pg_input_error_message
--------------------------------------------------------
- invalid input syntax for type double precision: "zed"
+ pg_input_error_message
+---------------------------------------------------------------------
+ ("invalid input syntax for type double precision: ""zed""",,,22P02)
(1 row)
SELECT pg_input_error_message('(1,1e400)', 'complex');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
create temp table quadtable(f1 int, q quad);
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index f028c1f10f..28c78c960b 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -281,21 +281,21 @@ SELECT pg_input_is_valid(E'\\xDeAdBeE', 'bytea');
(1 row)
SELECT pg_input_error_message(E'\\xDeAdBeE', 'bytea');
- pg_input_error_message
-------------------------------------------------
- invalid hexadecimal data: odd number of digits
+ pg_input_error_message
+------------------------------------------------------------
+ ("invalid hexadecimal data: odd number of digits",,,22023)
(1 row)
SELECT pg_input_error_message(E'\\xDeAdBeEx', 'bytea');
- pg_input_error_message
---------------------------------
- invalid hexadecimal digit: "x"
+ pg_input_error_message
+----------------------------------------------
+ ("invalid hexadecimal digit: ""x""",,,22023)
(1 row)
SELECT pg_input_error_message(E'foo\\99bar', 'bytea');
- pg_input_error_message
--------------------------------------
- invalid input syntax for type bytea
+ pg_input_error_message
+-------------------------------------------------
+ ("invalid input syntax for type bytea",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/tid.out b/src/test/regress/expected/tid.out
index ff67ed43f0..7ee891cc1a 100644
--- a/src/test/regress/expected/tid.out
+++ b/src/test/regress/expected/tid.out
@@ -25,9 +25,9 @@ SELECT pg_input_is_valid('(0)', 'tid');
(1 row)
SELECT pg_input_error_message('(0)', 'tid');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type tid: "(0)"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type tid: ""(0)""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(0,-1)', 'tid');
@@ -37,9 +37,9 @@ SELECT pg_input_is_valid('(0,-1)', 'tid');
(1 row)
SELECT pg_input_error_message('(0,-1)', 'tid');
- pg_input_error_message
----------------------------------------------
- invalid input syntax for type tid: "(0,-1)"
+ pg_input_error_message
+-----------------------------------------------------------
+ ("invalid input syntax for type tid: ""(0,-1)""",,,22P02)
(1 row)
-- tests for functions related to TID handling
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index a44caededd..79de9377db 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -134,15 +134,15 @@ SELECT pg_input_is_valid('15:36:39 America/New_York', 'time');
(1 row)
SELECT pg_input_error_message('25:00:00', 'time');
- pg_input_error_message
-------------------------------------------------
- date/time field value out of range: "25:00:00"
+ pg_input_error_message
+--------------------------------------------------------------
+ ("date/time field value out of range: ""25:00:00""",,,22008)
(1 row)
SELECT pg_input_error_message('15:36:39 America/New_York', 'time');
- pg_input_error_message
------------------------------------------------------------------
- invalid input syntax for type time: "15:36:39 America/New_York"
+ pg_input_error_message
+-------------------------------------------------------------------------------
+ ("invalid input syntax for type time: ""15:36:39 America/New_York""",,,22007)
(1 row)
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index eef2f7001c..eafccc38ac 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -145,15 +145,15 @@ SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
(1 row)
SELECT pg_input_error_message('garbage', 'timestamp');
- pg_input_error_message
-----------------------------------------------------
- invalid input syntax for type timestamp: "garbage"
+ pg_input_error_message
+------------------------------------------------------------------
+ ("invalid input syntax for type timestamp: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
- pg_input_error_message
---------------------------------------------
- time zone "nehwon/lankhmar" not recognized
+ pg_input_error_message
+----------------------------------------------------------
+ ("time zone ""nehwon/lankhmar"" not recognized",,,22023)
(1 row)
-- Check date conversion and date arithmetic
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index b85a93a3c2..73c7d70455 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -196,15 +196,15 @@ SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
(1 row)
SELECT pg_input_error_message('garbage', 'timestamptz');
- pg_input_error_message
--------------------------------------------------------------------
- invalid input syntax for type timestamp with time zone: "garbage"
+ pg_input_error_message
+---------------------------------------------------------------------------------
+ ("invalid input syntax for type timestamp with time zone: ""garbage""",,,22007)
(1 row)
SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
- pg_input_error_message
---------------------------------------------
- time zone "nehwon/lankhmar" not recognized
+ pg_input_error_message
+----------------------------------------------------------
+ ("time zone ""nehwon/lankhmar"" not recognized",,,22023)
(1 row)
-- Check date conversion and date arithmetic
diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out
index 984285663b..665fa4495c 100644
--- a/src/test/regress/expected/timetz.out
+++ b/src/test/regress/expected/timetz.out
@@ -151,15 +151,15 @@ SELECT pg_input_is_valid('15:36:39 America/New_York', 'timetz');
(1 row)
SELECT pg_input_error_message('25:00:00 PDT', 'timetz');
- pg_input_error_message
-----------------------------------------------------
- date/time field value out of range: "25:00:00 PDT"
+ pg_input_error_message
+------------------------------------------------------------------
+ ("date/time field value out of range: ""25:00:00 PDT""",,,22008)
(1 row)
SELECT pg_input_error_message('15:36:39 America/New_York', 'timetz');
- pg_input_error_message
---------------------------------------------------------------------------------
- invalid input syntax for type time with time zone: "15:36:39 America/New_York"
+ pg_input_error_message
+----------------------------------------------------------------------------------------------
+ ("invalid input syntax for type time with time zone: ""15:36:39 America/New_York""",,,22007)
(1 row)
--
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
index a8785cd708..35b19814c4 100644
--- a/src/test/regress/expected/tstypes.out
+++ b/src/test/regress/expected/tstypes.out
@@ -103,9 +103,9 @@ SELECT pg_input_is_valid($$''$$, 'tsvector');
(1 row)
SELECT pg_input_error_message($$''$$, 'tsvector');
- pg_input_error_message
---------------------------------
- syntax error in tsvector: "''"
+ pg_input_error_message
+----------------------------------------------
+ ("syntax error in tsvector: ""''""",,,42601)
(1 row)
--Base tsquery test
@@ -405,15 +405,15 @@ SELECT pg_input_is_valid('foo!', 'tsquery');
(1 row)
SELECT pg_input_error_message('foo!', 'tsquery');
- pg_input_error_message
----------------------------------
- syntax error in tsquery: "foo!"
+ pg_input_error_message
+-----------------------------------------------
+ ("syntax error in tsquery: ""foo!""",,,42601)
(1 row)
SELECT pg_input_error_message('a <100000> b', 'tsquery');
- pg_input_error_message
----------------------------------------------------------------------------------------
- distance in phrase operator must be an integer value between zero and 16384 inclusive
+ pg_input_error_message
+---------------------------------------------------------------------------------------------------
+ ("distance in phrase operator must be an integer value between zero and 16384 inclusive",,,22023)
(1 row)
--comparisons
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index 0f47232009..3934626dda 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -47,9 +47,9 @@ SELECT pg_input_is_valid('11', 'uuid');
(1 row)
SELECT pg_input_error_message('11', 'uuid');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type uuid: "11"
+ pg_input_error_message
+--------------------------------------------------------
+ ("invalid input syntax for type uuid: ""11""",,,22P02)
(1 row)
--inserting three input formats
diff --git a/src/test/regress/expected/varchar.out b/src/test/regress/expected/varchar.out
index 62b683d86f..d93dc1d9f9 100644
--- a/src/test/regress/expected/varchar.out
+++ b/src/test/regress/expected/varchar.out
@@ -125,8 +125,8 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+ pg_input_error_message
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/varchar_1.out b/src/test/regress/expected/varchar_1.out
index 6690f81c0b..ee0e920e84 100644
--- a/src/test/regress/expected/varchar_1.out
+++ b/src/test/regress/expected/varchar_1.out
@@ -125,8 +125,8 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+ pg_input_error_message
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/varchar_2.out b/src/test/regress/expected/varchar_2.out
index ad8aa7c693..3b663298ab 100644
--- a/src/test/regress/expected/varchar_2.out
+++ b/src/test/regress/expected/varchar_2.out
@@ -125,8 +125,8 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
(1 row)
SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+ pg_input_error_message
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index e62f701943..754ff6b725 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -44,9 +44,9 @@ SELECT pg_input_is_valid('asdf', 'xid');
(1 row)
SELECT pg_input_error_message('0xffffffffff', 'xid');
- pg_input_error_message
----------------------------------------------------
- value "0xffffffffff" is out of range for type xid
+ pg_input_error_message
+-----------------------------------------------------------------
+ ("value ""0xffffffffff"" is out of range for type xid",,,22003)
(1 row)
SELECT pg_input_is_valid('42', 'xid8');
@@ -62,9 +62,9 @@ SELECT pg_input_is_valid('asdf', 'xid8');
(1 row)
SELECT pg_input_error_message('0xffffffffffffffffffff', 'xid8');
- pg_input_error_message
---------------------------------------------------------------
- value "0xffffffffffffffffffff" is out of range for type xid8
+ pg_input_error_message
+----------------------------------------------------------------------------
+ ("value ""0xffffffffffffffffffff"" is out of range for type xid8",,,22003)
(1 row)
-- equality
@@ -224,9 +224,9 @@ select pg_input_is_valid('31:12:', 'pg_snapshot');
(1 row)
select pg_input_error_message('31:12:', 'pg_snapshot');
- pg_input_error_message
------------------------------------------------------
- invalid input syntax for type pg_snapshot: "31:12:"
+ pg_input_error_message
+-------------------------------------------------------------------
+ ("invalid input syntax for type pg_snapshot: ""31:12:""",,,22P02)
(1 row)
select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
@@ -236,9 +236,9 @@ select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
(1 row)
select pg_input_error_message('12:16:14,13', 'pg_snapshot');
- pg_input_error_message
-----------------------------------------------------------
- invalid input syntax for type pg_snapshot: "12:16:14,13"
+ pg_input_error_message
+------------------------------------------------------------------------
+ ("invalid input syntax for type pg_snapshot: ""12:16:14,13""",,,22P02)
(1 row)
create temp table snapshot_test (
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 3c357a9c7e..da6ac19ccb 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -31,9 +31,9 @@ SELECT pg_input_is_valid('<value>one</', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<value>one</', 'xml');
- pg_input_error_message
-------------------------
+SELECT message FROM pg_input_error_message('<value>one</', 'xml');
+ message
+---------------------
invalid XML content
(1 row)
@@ -43,8 +43,8 @@ SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_error_message
+SELECT message FROM pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ message
----------------------------------------------
invalid XML content: invalid XML declaration
(1 row)
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 378b412db0..9451c3808a 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -29,13 +29,13 @@ DETAIL: This functionality requires the server to be built with libxml support.
SELECT pg_input_is_valid('<value>one</', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
-SELECT pg_input_error_message('<value>one</', 'xml');
+SELECT message FROM pg_input_error_message('<value>one</', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+SELECT message FROM pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
SELECT xmlcomment('test');
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 42055c5003..0d1e52855f 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -29,8 +29,8 @@ SELECT pg_input_is_valid('<value>one</', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<value>one</', 'xml');
- pg_input_error_message
+SELECT message FROM pg_input_error_message('<value>one</', 'xml');
+ message
------------------------
invalid XML content
(1 row)
@@ -41,8 +41,8 @@ SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_error_message
+SELECT message FROM pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ message
----------------------------------------------
invalid XML content: invalid XML declaration
(1 row)
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index ddff459297..4dea563094 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -12,9 +12,9 @@ SELECT * FROM xmltest;
-- test non-throwing API, too
SELECT pg_input_is_valid('<value>one</value>', 'xml');
SELECT pg_input_is_valid('<value>one</', 'xml');
-SELECT pg_input_error_message('<value>one</', 'xml');
+SELECT message FROM pg_input_error_message('<value>one</', 'xml');
SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+SELECT message FROM pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
SELECT xmlcomment('test');
--
2.25.1
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
@ 2023-02-24 22:36 ` Corey Huinker <[email protected]>
2023-02-25 04:39 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Corey Huinker @ 2023-02-24 22:36 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Andrew Dunstan <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Thu, Feb 23, 2023 at 4:47 PM Nathan Bossart <[email protected]>
wrote:
> On Thu, Feb 23, 2023 at 11:30:38AM -0800, Nathan Bossart wrote:
> > Will post a new version shortly.
>
> As promised...
>
> --
> Nathan Bossart
> Amazon Web Services: https://aws.amazon.com
Looks good to me, passes make check-world. Thanks for slogging through this.
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
@ 2023-02-25 04:39 ` Michael Paquier <[email protected]>
2023-02-25 23:29 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Michael Paquier @ 2023-02-25 04:39 UTC (permalink / raw)
To: Corey Huinker <[email protected]>; +Cc: Nathan Bossart <[email protected]>; Andrew Dunstan <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Fri, Feb 24, 2023 at 05:36:42PM -0500, Corey Huinker wrote:
> Looks good to me, passes make check-world. Thanks for slogging through this.
FWIW, I agree that switching pg_input_error_message() to return a row
would be nicer in the long-run than just getting an error message
because it has the merit to be extensible at will with all the data
we'd like to attach to it (I suspect that getting more fields is not
much likely, but who knows..).
pg_input_error_message() does not strike me as a good function name,
though, because it now returns much more than an error message.
Hence, couldn't something like pg_input_error() be better, because
more generic?
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../Y%[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
2023-02-25 04:39 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
@ 2023-02-25 23:29 ` Nathan Bossart <[email protected]>
2023-02-26 01:07 ` Re: verbose mode for pg_input_error_message? Tom Lane <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Nathan Bossart @ 2023-02-25 23:29 UTC (permalink / raw)
To: Michael Paquier <[email protected]>; +Cc: Corey Huinker <[email protected]>; Andrew Dunstan <[email protected]>; Tom Lane <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Feb 25, 2023 at 01:39:21PM +0900, Michael Paquier wrote:
> pg_input_error_message() does not strike me as a good function name,
> though, because it now returns much more than an error message.
> Hence, couldn't something like pg_input_error() be better, because
> more generic?
I personally think the existing name is fine. It returns the error
message, which includes the primary, detail, and hint messages. Also, I'm
not sure that pg_input_error() is descriptive enough. That being said, I'm
happy to run the sed command to change the name to whatever folks think is
best.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
2023-02-25 04:39 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
2023-02-25 23:29 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
@ 2023-02-26 01:07 ` Tom Lane <[email protected]>
2023-02-26 04:58 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Tom Lane @ 2023-02-26 01:07 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Michael Paquier <[email protected]>; Corey Huinker <[email protected]>; Andrew Dunstan <[email protected]>; PostgreSQL Hackers <[email protected]>
Nathan Bossart <[email protected]> writes:
> On Sat, Feb 25, 2023 at 01:39:21PM +0900, Michael Paquier wrote:
>> pg_input_error_message() does not strike me as a good function name,
>> though, because it now returns much more than an error message.
>> Hence, couldn't something like pg_input_error() be better, because
>> more generic?
> I personally think the existing name is fine. It returns the error
> message, which includes the primary, detail, and hint messages. Also, I'm
> not sure that pg_input_error() is descriptive enough. That being said, I'm
> happy to run the sed command to change the name to whatever folks think is
> best.
Maybe pg_input_error_info()? I tend to agree with Michael that as
soon as you throw things like the SQLSTATE code into it, "message"
seems not very apropos. I'm not dead set on that position, though.
regards, tom lane
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
2023-02-25 04:39 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
2023-02-25 23:29 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-26 01:07 ` Re: verbose mode for pg_input_error_message? Tom Lane <[email protected]>
@ 2023-02-26 04:58 ` Nathan Bossart <[email protected]>
2023-02-26 05:35 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
0 siblings, 1 reply; 18+ messages in thread
From: Nathan Bossart @ 2023-02-26 04:58 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: Michael Paquier <[email protected]>; Corey Huinker <[email protected]>; Andrew Dunstan <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Feb 25, 2023 at 08:07:33PM -0500, Tom Lane wrote:
> Maybe pg_input_error_info()? I tend to agree with Michael that as
> soon as you throw things like the SQLSTATE code into it, "message"
> seems not very apropos. I'm not dead set on that position, though.
pg_input_error_info() seems more descriptive to me. I changed the name to
that in v4.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
[text/x-diff] v4-0001-add-details-to-pg_input_error_message-and-rename-.patch (139.6K, ../../20230226045817.GA1244537@nathanxps13/2-v4-0001-add-details-to-pg_input_error_message-and-rename-.patch)
download | inline diff:
From e06bba8374ba486c8593138b10a256aeef42a8af Mon Sep 17 00:00:00 2001
From: Nathan Bossart <[email protected]>
Date: Thu, 23 Feb 2023 10:31:24 -0800
Subject: [PATCH v4 1/1] add details to pg_input_error_message and rename to
pg_input_error_info
---
contrib/cube/expected/cube.out | 8 +-
contrib/cube/sql/cube.sql | 2 +-
contrib/hstore/expected/hstore.out | 16 +--
contrib/hstore/sql/hstore.sql | 4 +-
contrib/intarray/expected/_int.out | 12 +-
contrib/intarray/sql/_int.sql | 2 +-
contrib/isn/expected/isn.out | 12 +-
contrib/isn/sql/isn.sql | 2 +-
contrib/ltree/expected/ltree.out | 22 ++--
contrib/ltree/sql/ltree.sql | 2 +-
contrib/seg/expected/seg.out | 18 +--
contrib/seg/sql/seg.sql | 2 +-
doc/src/sgml/func.sgml | 33 +++--
src/backend/utils/adt/misc.c | 42 +++++--
src/include/catalog/pg_proc.dat | 10 +-
src/test/regress/expected/arrays.out | 8 +-
src/test/regress/expected/bit.out | 40 +++---
src/test/regress/expected/boolean.out | 8 +-
src/test/regress/expected/box.out | 16 +--
src/test/regress/expected/char.out | 8 +-
src/test/regress/expected/char_1.out | 8 +-
src/test/regress/expected/char_2.out | 8 +-
src/test/regress/expected/date.out | 16 +--
src/test/regress/expected/domain.out | 34 +++---
src/test/regress/expected/enum.out | 16 +--
.../expected/float4-misrounded-input.out | 4 +-
src/test/regress/expected/float4.out | 8 +-
src/test/regress/expected/float8.out | 8 +-
src/test/regress/expected/geometry.out | 16 +--
src/test/regress/expected/inet.out | 24 ++--
src/test/regress/expected/int2.out | 24 ++--
src/test/regress/expected/int4.out | 8 +-
src/test/regress/expected/int8.out | 8 +-
src/test/regress/expected/interval.out | 16 +--
src/test/regress/expected/json.out | 8 +-
src/test/regress/expected/json_encoding.out | 8 +-
src/test/regress/expected/json_encoding_1.out | 8 +-
src/test/regress/expected/jsonb.out | 16 +--
src/test/regress/expected/jsonpath.out | 16 +--
src/test/regress/expected/line.out | 40 +++---
src/test/regress/expected/lseg.out | 8 +-
src/test/regress/expected/macaddr.out | 16 +--
src/test/regress/expected/macaddr8.out | 16 +--
src/test/regress/expected/money.out | 16 +--
src/test/regress/expected/multirangetypes.out | 16 +--
src/test/regress/expected/numeric.out | 24 ++--
src/test/regress/expected/oid.out | 32 ++---
src/test/regress/expected/path.out | 16 +--
src/test/regress/expected/pg_lsn.out | 8 +-
src/test/regress/expected/point.out | 8 +-
src/test/regress/expected/polygon.out | 16 +--
src/test/regress/expected/privileges.out | 24 ++--
src/test/regress/expected/rangetypes.out | 40 +++---
src/test/regress/expected/regproc.out | 114 +++++++++---------
src/test/regress/expected/rowtypes.out | 16 +--
src/test/regress/expected/strings.out | 24 ++--
src/test/regress/expected/tid.out | 16 +--
src/test/regress/expected/time.out | 16 +--
src/test/regress/expected/timestamp.out | 16 +--
src/test/regress/expected/timestamptz.out | 16 +--
src/test/regress/expected/timetz.out | 16 +--
src/test/regress/expected/tstypes.out | 24 ++--
src/test/regress/expected/uuid.out | 8 +-
src/test/regress/expected/varchar.out | 8 +-
src/test/regress/expected/varchar_1.out | 8 +-
src/test/regress/expected/varchar_2.out | 8 +-
src/test/regress/expected/xid.out | 32 ++---
src/test/regress/expected/xml.out | 10 +-
src/test/regress/expected/xml_1.out | 4 +-
src/test/regress/expected/xml_2.out | 8 +-
src/test/regress/sql/arrays.sql | 2 +-
src/test/regress/sql/bit.sql | 10 +-
src/test/regress/sql/boolean.sql | 2 +-
src/test/regress/sql/box.sql | 4 +-
src/test/regress/sql/char.sql | 2 +-
src/test/regress/sql/date.sql | 4 +-
src/test/regress/sql/domain.sql | 10 +-
src/test/regress/sql/enum.sql | 4 +-
src/test/regress/sql/float4.sql | 2 +-
src/test/regress/sql/float8.sql | 2 +-
src/test/regress/sql/geometry.sql | 4 +-
src/test/regress/sql/inet.sql | 6 +-
src/test/regress/sql/int2.sql | 6 +-
src/test/regress/sql/int4.sql | 2 +-
src/test/regress/sql/int8.sql | 2 +-
src/test/regress/sql/interval.sql | 4 +-
src/test/regress/sql/json.sql | 2 +-
src/test/regress/sql/json_encoding.sql | 2 +-
src/test/regress/sql/jsonb.sql | 4 +-
src/test/regress/sql/jsonpath.sql | 2 +-
src/test/regress/sql/line.sql | 10 +-
src/test/regress/sql/lseg.sql | 2 +-
src/test/regress/sql/macaddr.sql | 4 +-
src/test/regress/sql/macaddr8.sql | 4 +-
src/test/regress/sql/money.sql | 4 +-
src/test/regress/sql/multirangetypes.sql | 4 +-
src/test/regress/sql/numeric.sql | 6 +-
src/test/regress/sql/oid.sql | 8 +-
src/test/regress/sql/path.sql | 4 +-
src/test/regress/sql/pg_lsn.sql | 2 +-
src/test/regress/sql/point.sql | 2 +-
src/test/regress/sql/polygon.sql | 4 +-
src/test/regress/sql/privileges.sql | 6 +-
src/test/regress/sql/rangetypes.sql | 10 +-
src/test/regress/sql/regproc.sql | 34 +++---
src/test/regress/sql/rowtypes.sql | 4 +-
src/test/regress/sql/strings.sql | 6 +-
src/test/regress/sql/tid.sql | 4 +-
src/test/regress/sql/time.sql | 4 +-
src/test/regress/sql/timestamp.sql | 4 +-
src/test/regress/sql/timestamptz.sql | 4 +-
src/test/regress/sql/timetz.sql | 4 +-
src/test/regress/sql/tstypes.sql | 6 +-
src/test/regress/sql/uuid.sql | 2 +-
src/test/regress/sql/varchar.sql | 2 +-
src/test/regress/sql/xid.sql | 8 +-
src/test/regress/sql/xml.sql | 4 +-
117 files changed, 708 insertions(+), 669 deletions(-)
diff --git a/contrib/cube/expected/cube.out b/contrib/cube/expected/cube.out
index dc23e5ccc0..5e2bade19c 100644
--- a/contrib/cube/expected/cube.out
+++ b/contrib/cube/expected/cube.out
@@ -344,10 +344,10 @@ SELECT pg_input_is_valid('-1e-700', 'cube');
f
(1 row)
-SELECT pg_input_error_message('-1e-700', 'cube');
- pg_input_error_message
------------------------------------------------------
- "-1e-700" is out of range for type double precision
+SELECT pg_input_error_info('-1e-700', 'cube');
+ pg_input_error_info
+-------------------------------------------------------------------
+ ("""-1e-700"" is out of range for type double precision",,,22003)
(1 row)
--
diff --git a/contrib/cube/sql/cube.sql b/contrib/cube/sql/cube.sql
index 384883d16e..2bb96e3490 100644
--- a/contrib/cube/sql/cube.sql
+++ b/contrib/cube/sql/cube.sql
@@ -83,7 +83,7 @@ SELECT '-1e-700'::cube AS cube; -- out of range
SELECT pg_input_is_valid('(1,2)', 'cube');
SELECT pg_input_is_valid('[(1),]', 'cube');
SELECT pg_input_is_valid('-1e-700', 'cube');
-SELECT pg_input_error_message('-1e-700', 'cube');
+SELECT pg_input_error_info('-1e-700', 'cube');
--
-- Testing building cubes from float8 values
diff --git a/contrib/hstore/expected/hstore.out b/contrib/hstore/expected/hstore.out
index d6faa91867..3d391a3453 100644
--- a/contrib/hstore/expected/hstore.out
+++ b/contrib/hstore/expected/hstore.out
@@ -265,16 +265,16 @@ select pg_input_is_valid('a=b', 'hstore');
f
(1 row)
-select pg_input_error_message('a=b', 'hstore');
- pg_input_error_message
-------------------------------------------------
- syntax error in hstore, near "b" at position 2
+select pg_input_error_info('a=b', 'hstore');
+ pg_input_error_info
+--------------------------------------------------------------
+ ("syntax error in hstore, near ""b"" at position 2",,,42601)
(1 row)
-select pg_input_error_message(' =>b', 'hstore');
- pg_input_error_message
-------------------------------------------------
- syntax error in hstore, near "=" at position 1
+select pg_input_error_info(' =>b', 'hstore');
+ pg_input_error_info
+--------------------------------------------------------------
+ ("syntax error in hstore, near ""="" at position 1",,,42601)
(1 row)
-- -> operator
diff --git a/contrib/hstore/sql/hstore.sql b/contrib/hstore/sql/hstore.sql
index 15f4f71416..3cd009c62a 100644
--- a/contrib/hstore/sql/hstore.sql
+++ b/contrib/hstore/sql/hstore.sql
@@ -60,8 +60,8 @@ select 'aa=>"'::hstore;
-- also try it with non-error-throwing API
select pg_input_is_valid('a=>b', 'hstore');
select pg_input_is_valid('a=b', 'hstore');
-select pg_input_error_message('a=b', 'hstore');
-select pg_input_error_message(' =>b', 'hstore');
+select pg_input_error_info('a=b', 'hstore');
+select pg_input_error_info(' =>b', 'hstore');
-- -> operator
diff --git a/contrib/intarray/expected/_int.out b/contrib/intarray/expected/_int.out
index c953065a5c..3645426165 100644
--- a/contrib/intarray/expected/_int.out
+++ b/contrib/intarray/expected/_int.out
@@ -401,16 +401,16 @@ SELECT '1&(2&(4&(5|!6)))'::query_int;
-- test non-error-throwing input
SELECT str as "query_int",
pg_input_is_valid(str,'query_int') as ok,
- pg_input_error_message(str,'query_int') as errmsg
+ pg_input_error_info(str,'query_int') as errmsg
FROM (VALUES ('1&(2&(4&(5|6)))'),
('1#(2&(4&(5&6)))'),
('foo'))
AS a(str);
- query_int | ok | errmsg
------------------+----+--------------
- 1&(2&(4&(5|6))) | t |
- 1#(2&(4&(5&6))) | f | syntax error
- foo | f | syntax error
+ query_int | ok | errmsg
+-----------------+----+--------------------------
+ 1&(2&(4&(5|6))) | t | (,,,)
+ 1#(2&(4&(5&6))) | f | ("syntax error",,,42601)
+ foo | f | ("syntax error",,,42601)
(3 rows)
CREATE TABLE test__int( a int[] );
diff --git a/contrib/intarray/sql/_int.sql b/contrib/intarray/sql/_int.sql
index 4c9ba4c1fb..6dde5c5e33 100644
--- a/contrib/intarray/sql/_int.sql
+++ b/contrib/intarray/sql/_int.sql
@@ -79,7 +79,7 @@ SELECT '1&(2&(4&(5|!6)))'::query_int;
SELECT str as "query_int",
pg_input_is_valid(str,'query_int') as ok,
- pg_input_error_message(str,'query_int') as errmsg
+ pg_input_error_info(str,'query_int') as errmsg
FROM (VALUES ('1&(2&(4&(5|6)))'),
('1#(2&(4&(5&6)))'),
('foo'))
diff --git a/contrib/isn/expected/isn.out b/contrib/isn/expected/isn.out
index 72171b2790..efc999ab37 100644
--- a/contrib/isn/expected/isn.out
+++ b/contrib/isn/expected/isn.out
@@ -263,16 +263,16 @@ SELECT '12345679'::ISSN = '9771234567003'::EAN13 AS "ok",
-- test non-error-throwing input API
SELECT str as isn, typ as "type",
pg_input_is_valid(str,typ) as ok,
- pg_input_error_message(str,typ) as errmsg
+ pg_input_error_info(str,typ) as errmsg
FROM (VALUES ('9780123456786', 'UPC'),
('postgresql...','EAN13'),
('9771234567003','ISSN'))
AS a(str,typ);
- isn | type | ok | errmsg
----------------+-------+----+--------------------------------------------------------
- 9780123456786 | UPC | f | cannot cast ISBN to UPC for number: "9780123456786"
- postgresql... | EAN13 | f | invalid input syntax for EAN13 number: "postgresql..."
- 9771234567003 | ISSN | t |
+ isn | type | ok | errmsg
+---------------+-------+----+----------------------------------------------------------------------
+ 9780123456786 | UPC | f | ("cannot cast ISBN to UPC for number: ""9780123456786""",,,22P02)
+ postgresql... | EAN13 | f | ("invalid input syntax for EAN13 number: ""postgresql...""",,,22P02)
+ 9771234567003 | ISSN | t | (,,,)
(3 rows)
--
diff --git a/contrib/isn/sql/isn.sql b/contrib/isn/sql/isn.sql
index 6426cb42a0..9fcb1f5bfe 100644
--- a/contrib/isn/sql/isn.sql
+++ b/contrib/isn/sql/isn.sql
@@ -110,7 +110,7 @@ SELECT '12345679'::ISSN = '9771234567003'::EAN13 AS "ok",
-- test non-error-throwing input API
SELECT str as isn, typ as "type",
pg_input_is_valid(str,typ) as ok,
- pg_input_error_message(str,typ) as errmsg
+ pg_input_error_info(str,typ) as errmsg
FROM (VALUES ('9780123456786', 'UPC'),
('postgresql...','EAN13'),
('9771234567003','ISSN'))
diff --git a/contrib/ltree/expected/ltree.out b/contrib/ltree/expected/ltree.out
index d2a53b9f0c..bc2eda125c 100644
--- a/contrib/ltree/expected/ltree.out
+++ b/contrib/ltree/expected/ltree.out
@@ -8101,7 +8101,7 @@ SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ;
-- test non-error-throwing input
SELECT str as "value", typ as "type",
pg_input_is_valid(str,typ) as ok,
- pg_input_error_message(str,typ) as errmsg
+ pg_input_error_info(str,typ) as errmsg
FROM (VALUES ('.2.3', 'ltree'),
('1.2.', 'ltree'),
('1.2.3','ltree'),
@@ -8111,15 +8111,15 @@ FROM (VALUES ('.2.3', 'ltree'),
('$tree & aWdf@*','ltxtquery'),
('!tree & aWdf@*','ltxtquery'))
AS a(str,typ);
- value | type | ok | errmsg
-----------------+-----------+----+------------------------------------
- .2.3 | ltree | f | ltree syntax error at character 1
- 1.2. | ltree | f | ltree syntax error
- 1.2.3 | ltree | t |
- @.2.3 | lquery | f | lquery syntax error at character 1
- 2.3 | lquery | f | lquery syntax error at character 1
- 1.2.3 | lquery | t |
- $tree & aWdf@* | ltxtquery | f | operand syntax error
- !tree & aWdf@* | ltxtquery | t |
+ value | type | ok | errmsg
+----------------+-----------+----+----------------------------------------------------------
+ .2.3 | ltree | f | ("ltree syntax error at character 1",,,42601)
+ 1.2. | ltree | f | ("ltree syntax error","Unexpected end of input.",,42601)
+ 1.2.3 | ltree | t | (,,,)
+ @.2.3 | lquery | f | ("lquery syntax error at character 1",,,42601)
+ 2.3 | lquery | f | ("lquery syntax error at character 1",,,42601)
+ 1.2.3 | lquery | t | (,,,)
+ $tree & aWdf@* | ltxtquery | f | ("operand syntax error",,,42601)
+ !tree & aWdf@* | ltxtquery | t | (,,,)
(8 rows)
diff --git a/contrib/ltree/sql/ltree.sql b/contrib/ltree/sql/ltree.sql
index 4a6e6266c3..ab30792354 100644
--- a/contrib/ltree/sql/ltree.sql
+++ b/contrib/ltree/sql/ltree.sql
@@ -393,7 +393,7 @@ SELECT count(*) FROM _ltreetest WHERE t ? '{23.*.1,23.*.2}' ;
SELECT str as "value", typ as "type",
pg_input_is_valid(str,typ) as ok,
- pg_input_error_message(str,typ) as errmsg
+ pg_input_error_info(str,typ) as errmsg
FROM (VALUES ('.2.3', 'ltree'),
('1.2.', 'ltree'),
('1.2.3','ltree'),
diff --git a/contrib/seg/expected/seg.out b/contrib/seg/expected/seg.out
index 7a06113ed8..6d8831d077 100644
--- a/contrib/seg/expected/seg.out
+++ b/contrib/seg/expected/seg.out
@@ -1276,20 +1276,20 @@ FROM test_seg WHERE s @> '11.2..11.3' OR s IS NULL ORDER BY s;
-- test non error throwing API
SELECT str as seg,
pg_input_is_valid(str,'seg') as ok,
- pg_input_error_message(str,'seg') as errmsg
+ pg_input_error_info(str,'seg') as errmsg
FROM unnest(ARRAY['-1 .. 1'::text,
'100(+-)1',
'',
'ABC',
'1 e7',
'1e700']) str;
- seg | ok | errmsg
-----------+----+---------------------------------------
- -1 .. 1 | t |
- 100(+-)1 | t |
- | f | bad seg representation
- ABC | f | bad seg representation
- 1 e7 | f | bad seg representation
- 1e700 | f | "1e700" is out of range for type real
+ seg | ok | errmsg
+----------+----+-------------------------------------------------------------------
+ -1 .. 1 | t | (,,,)
+ 100(+-)1 | t | (,,,)
+ | f | ("bad seg representation","syntax error at end of input",,42601)
+ ABC | f | ("bad seg representation","syntax error at or near ""A""",,42601)
+ 1 e7 | f | ("bad seg representation","syntax error at or near ""e""",,42601)
+ 1e700 | f | ("""1e700"" is out of range for type real",,,22003)
(6 rows)
diff --git a/contrib/seg/sql/seg.sql b/contrib/seg/sql/seg.sql
index b9a5d05d09..b2a96e84ba 100644
--- a/contrib/seg/sql/seg.sql
+++ b/contrib/seg/sql/seg.sql
@@ -244,7 +244,7 @@ FROM test_seg WHERE s @> '11.2..11.3' OR s IS NULL ORDER BY s;
SELECT str as seg,
pg_input_is_valid(str,'seg') as ok,
- pg_input_error_message(str,'seg') as errmsg
+ pg_input_error_info(str,'seg') as errmsg
FROM unnest(ARRAY['-1 .. 1'::text,
'100(+-)1',
'',
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0cbdf63632..97b3f1c1a6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -24775,19 +24775,23 @@ SELECT collation for ('foo' COLLATE "de_DE");
<row>
<entry role="func_table_entry"><para role="func_signature">
<indexterm>
- <primary>pg_input_error_message</primary>
+ <primary>pg_input_error_info</primary>
</indexterm>
- <function>pg_input_error_message</function> (
+ <function>pg_input_error_info</function> (
<parameter>string</parameter> <type>text</type>,
<parameter>type</parameter> <type>text</type>
)
- <returnvalue>text</returnvalue>
+ <returnvalue>record</returnvalue>
+ ( <parameter>message</parameter> <type>text</type>,
+ <parameter>detail</parameter> <type>text</type>,
+ <parameter>hint</parameter> <type>text</type>,
+ <parameter>sql_error_code</parameter> <type>text</type> )
</para>
<para>
Tests whether the given <parameter>string</parameter> is valid
- input for the specified data type; if not, return the error
- message that would have been thrown. If the input is valid, the
- result is NULL. The inputs are the same as
+ input for the specified data type; if not, return the details of
+ the error would have been thrown. If the input is valid, the
+ results are NULL. The inputs are the same as
for <function>pg_input_is_valid</function>.
</para>
<para>
@@ -24798,12 +24802,17 @@ SELECT collation for ('foo' COLLATE "de_DE");
directly.
</para>
<para>
- <literal>pg_input_error_message('42000000000', 'integer')</literal>
- <returnvalue>value "42000000000" is out of range for type integer</returnvalue>
- </para>
- <para>
- <literal>pg_input_error_message('1234.567', 'numeric(7,4)')</literal>
- <returnvalue>numeric field overflow</returnvalue>
+<programlisting>
+SELECT * FROM pg_input_error_info('42000000000', 'integer');
+ message | detail | hint | sql_error_code
+------------------------------------------------------+--------+------+----------------
+ value "42000000000" is out of range for type integer | | | 22003
+
+SELECT * FROM pg_input_error_info('1234.567', 'numeric(7,4)');
+ message | detail | hint | sql_error_code
+------------------------+-----------------------------------------------------------------------------------+------+----------------
+ numeric field overflow | A field with precision 7, scale 4 must round to an absolute value less than 10^3. | | 22003
+</programlisting>
</para></entry>
</row>
</tbody>
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index f95256efd3..182d16cdcd 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -660,32 +660,58 @@ pg_input_is_valid(PG_FUNCTION_ARGS)
}
/*
- * pg_input_error_message - test whether string is valid input for datatype.
+ * pg_input_error_info - test whether string is valid input for datatype.
*
- * Returns NULL if OK, else the primary message string from the error.
+ * Returns NULL if OK, else the primary message, detail message, hint message,
+ * and sql error code from the error.
*
* This will only work usefully if the datatype's input function has been
* updated to return "soft" errors via errsave/ereturn.
*/
Datum
-pg_input_error_message(PG_FUNCTION_ARGS)
+pg_input_error_info(PG_FUNCTION_ARGS)
{
text *txt = PG_GETARG_TEXT_PP(0);
text *typname = PG_GETARG_TEXT_PP(1);
ErrorSaveContext escontext = {T_ErrorSaveContext};
+ TupleDesc tupdesc;
+ Datum values[4];
+ bool isnull[4];
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
/* Enable details_wanted */
escontext.details_wanted = true;
if (pg_input_is_valid_common(fcinfo, txt, typname,
&escontext))
- PG_RETURN_NULL();
+ memset(isnull, true, sizeof(isnull));
+ else
+ {
+ Assert(escontext.error_occurred);
+ Assert(escontext.error_data != NULL);
+ Assert(escontext.error_data->message != NULL);
+
+ memset(isnull, false, sizeof(isnull));
+
+ values[0] = CStringGetTextDatum(escontext.error_data->message);
- Assert(escontext.error_occurred);
- Assert(escontext.error_data != NULL);
- Assert(escontext.error_data->message != NULL);
+ if (escontext.error_data->detail != NULL)
+ values[1] = CStringGetTextDatum(escontext.error_data->detail);
+ else
+ isnull[1] = true;
+
+ if (escontext.error_data->hint != NULL)
+ values[2] = CStringGetTextDatum(escontext.error_data->hint);
+ else
+ isnull[2] = true;
+
+ values[3] = CStringGetTextDatum(
+ unpack_sql_state(escontext.error_data->sqlerrcode));
+ }
- PG_RETURN_TEXT_P(cstring_to_text(escontext.error_data->message));
+ return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
}
/* Common subroutine for the above */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index e2a7642a2b..505595620e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -7118,9 +7118,13 @@
proname => 'pg_input_is_valid', provolatile => 's', prorettype => 'bool',
proargtypes => 'text text', prosrc => 'pg_input_is_valid' },
{ oid => '8051',
- descr => 'get error message if string is not valid input for data type',
- proname => 'pg_input_error_message', provolatile => 's', prorettype => 'text',
- proargtypes => 'text text', prosrc => 'pg_input_error_message' },
+ descr => 'get error details if string is not valid input for data type',
+ proname => 'pg_input_error_info', provolatile => 's', prorettype => 'record',
+ proargtypes => 'text text',
+ proallargtypes => '{text,text,text,text,text,text}',
+ proargmodes => '{i,i,o,o,o,o}',
+ proargnames => '{value,type_name,message,detail,hint,sql_error_code}',
+ prosrc => 'pg_input_error_info' },
{ oid => '1268',
descr => 'parse qualified identifier to array of identifiers',
diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out
index a2f9d7ed16..c840cc7765 100644
--- a/src/test/regress/expected/arrays.out
+++ b/src/test/regress/expected/arrays.out
@@ -201,10 +201,10 @@ SELECT pg_input_is_valid('{1,zed}', 'integer[]');
f
(1 row)
-SELECT pg_input_error_message('{1,zed}', 'integer[]');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+SELECT pg_input_error_info('{1,zed}', 'integer[]');
+ pg_input_error_info
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
-- test mixed slice/scalar subscripting
diff --git a/src/test/regress/expected/bit.out b/src/test/regress/expected/bit.out
index 209044713c..411e2abe23 100644
--- a/src/test/regress/expected/bit.out
+++ b/src/test/regress/expected/bit.out
@@ -753,10 +753,10 @@ SELECT pg_input_is_valid('01010001', 'bit(10)');
f
(1 row)
-SELECT pg_input_error_message('01010001', 'bit(10)');
- pg_input_error_message
--------------------------------------------------
- bit string length 8 does not match type bit(10)
+SELECT pg_input_error_info('01010001', 'bit(10)');
+ pg_input_error_info
+-------------------------------------------------------------
+ ("bit string length 8 does not match type bit(10)",,,22026)
(1 row)
SELECT pg_input_is_valid('01010Z01', 'bit(8)');
@@ -765,10 +765,10 @@ SELECT pg_input_is_valid('01010Z01', 'bit(8)');
f
(1 row)
-SELECT pg_input_error_message('01010Z01', 'bit(8)');
- pg_input_error_message
----------------------------------
- "Z" is not a valid binary digit
+SELECT pg_input_error_info('01010Z01', 'bit(8)');
+ pg_input_error_info
+-----------------------------------------------
+ ("""Z"" is not a valid binary digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
@@ -777,10 +777,10 @@ SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
f
(1 row)
-SELECT pg_input_error_message('x01010Z01', 'bit(32)');
- pg_input_error_message
---------------------------------------
- "Z" is not a valid hexadecimal digit
+SELECT pg_input_error_info('x01010Z01', 'bit(32)');
+ pg_input_error_info
+----------------------------------------------------
+ ("""Z"" is not a valid hexadecimal digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('01010Z01', 'varbit');
@@ -789,10 +789,10 @@ SELECT pg_input_is_valid('01010Z01', 'varbit');
f
(1 row)
-SELECT pg_input_error_message('01010Z01', 'varbit');
- pg_input_error_message
----------------------------------
- "Z" is not a valid binary digit
+SELECT pg_input_error_info('01010Z01', 'varbit');
+ pg_input_error_info
+-----------------------------------------------
+ ("""Z"" is not a valid binary digit",,,22P02)
(1 row)
SELECT pg_input_is_valid('x01010Z01', 'varbit');
@@ -801,9 +801,9 @@ SELECT pg_input_is_valid('x01010Z01', 'varbit');
f
(1 row)
-SELECT pg_input_error_message('x01010Z01', 'varbit');
- pg_input_error_message
---------------------------------------
- "Z" is not a valid hexadecimal digit
+SELECT pg_input_error_info('x01010Z01', 'varbit');
+ pg_input_error_info
+----------------------------------------------------
+ ("""Z"" is not a valid hexadecimal digit",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/boolean.out b/src/test/regress/expected/boolean.out
index 977124b20b..cbc4429a11 100644
--- a/src/test/regress/expected/boolean.out
+++ b/src/test/regress/expected/boolean.out
@@ -155,10 +155,10 @@ SELECT pg_input_is_valid('asdf', 'bool');
f
(1 row)
-SELECT pg_input_error_message('junk', 'bool');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type boolean: "junk"
+SELECT pg_input_error_info('junk', 'bool');
+ pg_input_error_info
+-------------------------------------------------------------
+ ("invalid input syntax for type boolean: ""junk""",,,22P02)
(1 row)
-- and, or, not in qualifications
diff --git a/src/test/regress/expected/box.out b/src/test/regress/expected/box.out
index 0d70194def..406ee5b815 100644
--- a/src/test/regress/expected/box.out
+++ b/src/test/regress/expected/box.out
@@ -646,10 +646,10 @@ SELECT pg_input_is_valid('200', 'box');
f
(1 row)
-SELECT pg_input_error_message('200', 'box');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type box: "200"
+SELECT pg_input_error_info('200', 'box');
+ pg_input_error_info
+--------------------------------------------------------
+ ("invalid input syntax for type box: ""200""",,,22P02)
(1 row)
SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
@@ -658,9 +658,9 @@ SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
f
(1 row)
-SELECT pg_input_error_message('((200,300),(500, xyz))', 'box');
- pg_input_error_message
--------------------------------------------------------------
- invalid input syntax for type box: "((200,300),(500, xyz))"
+SELECT pg_input_error_info('((200,300),(500, xyz))', 'box');
+ pg_input_error_info
+---------------------------------------------------------------------------
+ ("invalid input syntax for type box: ""((200,300),(500, xyz))""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/char.out b/src/test/regress/expected/char.out
index 199001b2fe..58cdd54ffb 100644
--- a/src/test/regress/expected/char.out
+++ b/src/test/regress/expected/char.out
@@ -132,10 +132,10 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
f
(1 row)
-SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+SELECT pg_input_error_info('abcde', 'char(4)');
+ pg_input_error_info
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/char_1.out b/src/test/regress/expected/char_1.out
index 3dcb0daa0d..c45fe2a400 100644
--- a/src/test/regress/expected/char_1.out
+++ b/src/test/regress/expected/char_1.out
@@ -132,10 +132,10 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
f
(1 row)
-SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+SELECT pg_input_error_info('abcde', 'char(4)');
+ pg_input_error_info
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/char_2.out b/src/test/regress/expected/char_2.out
index dd5d34fe8d..1281ba4394 100644
--- a/src/test/regress/expected/char_2.out
+++ b/src/test/regress/expected/char_2.out
@@ -132,10 +132,10 @@ SELECT pg_input_is_valid('abcde', 'char(4)');
f
(1 row)
-SELECT pg_input_error_message('abcde', 'char(4)');
- pg_input_error_message
---------------------------------------
- value too long for type character(4)
+SELECT pg_input_error_info('abcde', 'char(4)');
+ pg_input_error_info
+--------------------------------------------------
+ ("value too long for type character(4)",,,22001)
(1 row)
--
diff --git a/src/test/regress/expected/date.out b/src/test/regress/expected/date.out
index c0dec448e1..4a496aec5b 100644
--- a/src/test/regress/expected/date.out
+++ b/src/test/regress/expected/date.out
@@ -859,16 +859,16 @@ SELECT pg_input_is_valid('6874898-01-01', 'date');
f
(1 row)
-SELECT pg_input_error_message('garbage', 'date');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type date: "garbage"
+SELECT pg_input_error_info('garbage', 'date');
+ pg_input_error_info
+-------------------------------------------------------------
+ ("invalid input syntax for type date: ""garbage""",,,22007)
(1 row)
-SELECT pg_input_error_message('6874898-01-01', 'date');
- pg_input_error_message
-------------------------------------
- date out of range: "6874898-01-01"
+SELECT pg_input_error_info('6874898-01-01', 'date');
+ pg_input_error_info
+--------------------------------------------------
+ ("date out of range: ""6874898-01-01""",,,22008)
(1 row)
RESET datestyle;
diff --git a/src/test/regress/expected/domain.out b/src/test/regress/expected/domain.out
index 25f6bb9e1f..b832b146f7 100644
--- a/src/test/regress/expected/domain.out
+++ b/src/test/regress/expected/domain.out
@@ -108,32 +108,32 @@ select pg_input_is_valid('-1', 'positiveint');
f
(1 row)
-select pg_input_error_message('junk', 'positiveint');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type integer: "junk"
+select pg_input_error_info('junk', 'positiveint');
+ pg_input_error_info
+-------------------------------------------------------------
+ ("invalid input syntax for type integer: ""junk""",,,22P02)
(1 row)
-select pg_input_error_message('-1', 'positiveint');
- pg_input_error_message
-----------------------------------------------------------------------------
- value for domain positiveint violates check constraint "positiveint_check"
+select pg_input_error_info('-1', 'positiveint');
+ pg_input_error_info
+------------------------------------------------------------------------------------------
+ ("value for domain positiveint violates check constraint ""positiveint_check""",,,23514)
(1 row)
-select pg_input_error_message('junk', 'weirdfloat');
- pg_input_error_message
---------------------------------------------------------
- invalid input syntax for type double precision: "junk"
+select pg_input_error_info('junk', 'weirdfloat');
+ pg_input_error_info
+----------------------------------------------------------------------
+ ("invalid input syntax for type double precision: ""junk""",,,22P02)
(1 row)
-select pg_input_error_message('0.01', 'weirdfloat');
- pg_input_error_message
---------------------------------------------------------------------------
- value for domain weirdfloat violates check constraint "weirdfloat_check"
+select pg_input_error_info('0.01', 'weirdfloat');
+ pg_input_error_info
+----------------------------------------------------------------------------------------
+ ("value for domain weirdfloat violates check constraint ""weirdfloat_check""",,,23514)
(1 row)
-- We currently can't trap errors raised in the CHECK expression itself
-select pg_input_error_message('0', 'weirdfloat');
+select pg_input_error_info('0', 'weirdfloat');
ERROR: division by zero
drop domain positiveint;
drop domain weirdfloat;
diff --git a/src/test/regress/expected/enum.out b/src/test/regress/expected/enum.out
index 4b45fcf8f0..26671b23b7 100644
--- a/src/test/regress/expected/enum.out
+++ b/src/test/regress/expected/enum.out
@@ -37,16 +37,16 @@ SELECT pg_input_is_valid('mauve', 'rainbow');
f
(1 row)
-SELECT pg_input_error_message('mauve', 'rainbow');
- pg_input_error_message
------------------------------------------------
- invalid input value for enum rainbow: "mauve"
+SELECT pg_input_error_info('mauve', 'rainbow');
+ pg_input_error_info
+-------------------------------------------------------------
+ ("invalid input value for enum rainbow: ""mauve""",,,22P02)
(1 row)
-SELECT pg_input_error_message(repeat('too_long', 32), 'rainbow');
- pg_input_error_message
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- invalid input value for enum rainbow: "too_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_long"
+SELECT pg_input_error_info(repeat('too_long', 32), 'rainbow');
+ pg_input_error_info
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ ("invalid input value for enum rainbow: ""too_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_longtoo_long""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/float4-misrounded-input.out b/src/test/regress/expected/float4-misrounded-input.out
index 24fde6cc9f..12e18ff465 100644
--- a/src/test/regress/expected/float4-misrounded-input.out
+++ b/src/test/regress/expected/float4-misrounded-input.out
@@ -100,8 +100,8 @@ SELECT pg_input_is_valid('1e400', 'float4');
f
(1 row)
-SELECT pg_input_error_message('1e400', 'float4');
- pg_input_error_message
+SELECT pg_input_error_info('1e400', 'float4');
+ pg_input_error_info
---------------------------------------
"1e400" is out of range for type real
(1 row)
diff --git a/src/test/regress/expected/float4.out b/src/test/regress/expected/float4.out
index 1d7090a90d..94d68adb7b 100644
--- a/src/test/regress/expected/float4.out
+++ b/src/test/regress/expected/float4.out
@@ -100,10 +100,10 @@ SELECT pg_input_is_valid('1e400', 'float4');
f
(1 row)
-SELECT pg_input_error_message('1e400', 'float4');
- pg_input_error_message
----------------------------------------
- "1e400" is out of range for type real
+SELECT pg_input_error_info('1e400', 'float4');
+ pg_input_error_info
+-----------------------------------------------------
+ ("""1e400"" is out of range for type real",,,22003)
(1 row)
-- special inputs
diff --git a/src/test/regress/expected/float8.out b/src/test/regress/expected/float8.out
index 2b25784f7f..06cc8c6537 100644
--- a/src/test/regress/expected/float8.out
+++ b/src/test/regress/expected/float8.out
@@ -87,10 +87,10 @@ SELECT pg_input_is_valid('1e4000', 'float8');
f
(1 row)
-SELECT pg_input_error_message('1e4000', 'float8');
- pg_input_error_message
-----------------------------------------------------
- "1e4000" is out of range for type double precision
+SELECT pg_input_error_info('1e4000', 'float8');
+ pg_input_error_info
+------------------------------------------------------------------
+ ("""1e4000"" is out of range for type double precision",,,22003)
(1 row)
-- special inputs
diff --git a/src/test/regress/expected/geometry.out b/src/test/regress/expected/geometry.out
index 291cacdf4f..76ad614b61 100644
--- a/src/test/regress/expected/geometry.out
+++ b/src/test/regress/expected/geometry.out
@@ -5302,10 +5302,10 @@ SELECT pg_input_is_valid('(1', 'circle');
f
(1 row)
-SELECT pg_input_error_message('1,', 'circle');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type circle: "1,"
+SELECT pg_input_error_info('1,', 'circle');
+ pg_input_error_info
+----------------------------------------------------------
+ ("invalid input syntax for type circle: ""1,""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(1,2),-1', 'circle');
@@ -5314,9 +5314,9 @@ SELECT pg_input_is_valid('(1,2),-1', 'circle');
f
(1 row)
-SELECT pg_input_error_message('(1,2),-1', 'circle');
- pg_input_error_message
---------------------------------------------------
- invalid input syntax for type circle: "(1,2),-1"
+SELECT pg_input_error_info('(1,2),-1', 'circle');
+ pg_input_error_info
+----------------------------------------------------------------
+ ("invalid input syntax for type circle: ""(1,2),-1""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/inet.out b/src/test/regress/expected/inet.out
index c9f466ac1d..ada484642a 100644
--- a/src/test/regress/expected/inet.out
+++ b/src/test/regress/expected/inet.out
@@ -1063,10 +1063,10 @@ SELECT pg_input_is_valid('1234', 'cidr');
f
(1 row)
-SELECT pg_input_error_message('1234', 'cidr');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type cidr: "1234"
+SELECT pg_input_error_info('1234', 'cidr');
+ pg_input_error_info
+----------------------------------------------------------
+ ("invalid input syntax for type cidr: ""1234""",,,22P02)
(1 row)
SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
@@ -1075,10 +1075,10 @@ SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
f
(1 row)
-SELECT pg_input_error_message('192.168.198.200/24', 'cidr');
- pg_input_error_message
-------------------------------------------
- invalid cidr value: "192.168.198.200/24"
+SELECT pg_input_error_info('192.168.198.200/24', 'cidr');
+ pg_input_error_info
+----------------------------------------------------------------------------------------------
+ ("invalid cidr value: ""192.168.198.200/24""","Value has bits set to right of mask.",,22P02)
(1 row)
SELECT pg_input_is_valid('1234', 'inet');
@@ -1087,9 +1087,9 @@ SELECT pg_input_is_valid('1234', 'inet');
f
(1 row)
-SELECT pg_input_error_message('1234', 'inet');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type inet: "1234"
+SELECT pg_input_error_info('1234', 'inet');
+ pg_input_error_info
+----------------------------------------------------------
+ ("invalid input syntax for type inet: ""1234""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/int2.out b/src/test/regress/expected/int2.out
index 73b4ee023c..c0b06a1aa4 100644
--- a/src/test/regress/expected/int2.out
+++ b/src/test/regress/expected/int2.out
@@ -64,10 +64,10 @@ SELECT pg_input_is_valid('50000', 'int2');
f
(1 row)
-SELECT pg_input_error_message('50000', 'int2');
- pg_input_error_message
--------------------------------------------------
- value "50000" is out of range for type smallint
+SELECT pg_input_error_info('50000', 'int2');
+ pg_input_error_info
+---------------------------------------------------------------
+ ("value ""50000"" is out of range for type smallint",,,22003)
(1 row)
-- While we're here, check int2vector as well
@@ -77,16 +77,16 @@ SELECT pg_input_is_valid(' 1 3 5 ', 'int2vector');
t
(1 row)
-SELECT pg_input_error_message('1 asdf', 'int2vector');
- pg_input_error_message
-------------------------------------------------
- invalid input syntax for type smallint: "asdf"
+SELECT pg_input_error_info('1 asdf', 'int2vector');
+ pg_input_error_info
+--------------------------------------------------------------
+ ("invalid input syntax for type smallint: ""asdf""",,,22P02)
(1 row)
-SELECT pg_input_error_message('50000', 'int2vector');
- pg_input_error_message
--------------------------------------------------
- value "50000" is out of range for type smallint
+SELECT pg_input_error_info('50000', 'int2vector');
+ pg_input_error_info
+---------------------------------------------------------------
+ ("value ""50000"" is out of range for type smallint",,,22003)
(1 row)
SELECT * FROM INT2_TBL AS f(a, b);
diff --git a/src/test/regress/expected/int4.out b/src/test/regress/expected/int4.out
index 9c20574ca5..f0c469d8ab 100644
--- a/src/test/regress/expected/int4.out
+++ b/src/test/regress/expected/int4.out
@@ -64,10 +64,10 @@ SELECT pg_input_is_valid('1000000000000', 'int4');
f
(1 row)
-SELECT pg_input_error_message('1000000000000', 'int4');
- pg_input_error_message
---------------------------------------------------------
- value "1000000000000" is out of range for type integer
+SELECT pg_input_error_info('1000000000000', 'int4');
+ pg_input_error_info
+----------------------------------------------------------------------
+ ("value ""1000000000000"" is out of range for type integer",,,22003)
(1 row)
SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index d9dca64e88..7a788a15d7 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -61,10 +61,10 @@ SELECT pg_input_is_valid('10000000000000000000', 'int8');
f
(1 row)
-SELECT pg_input_error_message('10000000000000000000', 'int8');
- pg_input_error_message
---------------------------------------------------------------
- value "10000000000000000000" is out of range for type bigint
+SELECT pg_input_error_info('10000000000000000000', 'int8');
+ pg_input_error_info
+----------------------------------------------------------------------------
+ ("value ""10000000000000000000"" is out of range for type bigint",,,22003)
(1 row)
-- int8/int8 cmp
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a154840c85..7b654e77b2 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -91,16 +91,16 @@ SELECT pg_input_is_valid('@ 30 eons ago', 'interval');
f
(1 row)
-SELECT pg_input_error_message('garbage', 'interval');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type interval: "garbage"
+SELECT pg_input_error_info('garbage', 'interval');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("invalid input syntax for type interval: ""garbage""",,,22007)
(1 row)
-SELECT pg_input_error_message('@ 30 eons ago', 'interval');
- pg_input_error_message
----------------------------------------------------------
- invalid input syntax for type interval: "@ 30 eons ago"
+SELECT pg_input_error_info('@ 30 eons ago', 'interval');
+ pg_input_error_info
+-----------------------------------------------------------------------
+ ("invalid input syntax for type interval: ""@ 30 eons ago""",,,22007)
(1 row)
-- test interval operators
diff --git a/src/test/regress/expected/json.out b/src/test/regress/expected/json.out
index af96ce4180..7d574c25af 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -333,10 +333,10 @@ select pg_input_is_valid('{"a":true', 'json');
f
(1 row)
-select pg_input_error_message('{"a":true', 'json');
- pg_input_error_message
-------------------------------------
- invalid input syntax for type json
+select pg_input_error_info('{"a":true', 'json');
+ pg_input_error_info
+--------------------------------------------------------------------------------------
+ ("invalid input syntax for type json","The input string ended unexpectedly.",,22P02)
(1 row)
--constructors
diff --git a/src/test/regress/expected/json_encoding.out b/src/test/regress/expected/json_encoding.out
index 083621fb21..21b0d11787 100644
--- a/src/test/regress/expected/json_encoding.out
+++ b/src/test/regress/expected/json_encoding.out
@@ -261,9 +261,9 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
(1 row)
-- soft error for input-time failure
-select pg_input_error_message('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
- pg_input_error_message
-------------------------
-
+select pg_input_error_info('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
+ pg_input_error_info
+---------------------
+ (,,,)
(1 row)
diff --git a/src/test/regress/expected/json_encoding_1.out b/src/test/regress/expected/json_encoding_1.out
index 021d226f8d..39d439e684 100644
--- a/src/test/regress/expected/json_encoding_1.out
+++ b/src/test/regress/expected/json_encoding_1.out
@@ -257,9 +257,9 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
(1 row)
-- soft error for input-time failure
-select pg_input_error_message('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
- pg_input_error_message
--------------------------------------
- unsupported Unicode escape sequence
+select pg_input_error_info('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
+ pg_input_error_info
+-----------------------------------------------------------------------------------------------------------------------------------
+ ("unsupported Unicode escape sequence","Unicode escape value could not be translated to the server's encoding SQL_ASCII.",,22P05)
(1 row)
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index d3248aa0fd..cfda43589e 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -323,16 +323,16 @@ select pg_input_is_valid('{"a":true', 'jsonb');
f
(1 row)
-select pg_input_error_message('{"a":true', 'jsonb');
- pg_input_error_message
-------------------------------------
- invalid input syntax for type json
+select pg_input_error_info('{"a":true', 'jsonb');
+ pg_input_error_info
+--------------------------------------------------------------------------------------
+ ("invalid input syntax for type json","The input string ended unexpectedly.",,22P02)
(1 row)
-select pg_input_error_message('{"a":1e1000000}', 'jsonb');
- pg_input_error_message
---------------------------------
- value overflows numeric format
+select pg_input_error_info('{"a":1e1000000}', 'jsonb');
+ pg_input_error_info
+--------------------------------------------
+ ("value overflows numeric format",,,22003)
(1 row)
-- make sure jsonb is passed through json generators without being escaped
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index ca0cdf1ab2..eba11d485f 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -1035,18 +1035,18 @@ select '1?(2>3)'::jsonpath;
-- test non-error-throwing API
SELECT str as jsonpath,
pg_input_is_valid(str,'jsonpath') as ok,
- pg_input_error_message(str,'jsonpath') as errmsg
+ pg_input_error_info(str,'jsonpath') as errmsg
FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text,
'$ ? (@ like_regex "pattern" flag "a")',
'@ + 1',
'00',
'1a']) str;
- jsonpath | ok | errmsg
--------------------------------------------+----+-----------------------------------------------------------------------
- $ ? (@ like_regex "pattern" flag "smixq") | t |
- $ ? (@ like_regex "pattern" flag "a") | f | invalid input syntax for type jsonpath
- @ + 1 | f | @ is not allowed in root expressions
- 00 | f | trailing junk after numeric literal at or near "00" of jsonpath input
- 1a | f | trailing junk after numeric literal at or near "1a" of jsonpath input
+ jsonpath | ok | errmsg
+-------------------------------------------+----+----------------------------------------------------------------------------------------------------------------
+ $ ? (@ like_regex "pattern" flag "smixq") | t | (,,,)
+ $ ? (@ like_regex "pattern" flag "a") | f | ("invalid input syntax for type jsonpath","Unrecognized flag character ""a"" in LIKE_REGEX predicate.",,42601)
+ @ + 1 | f | ("@ is not allowed in root expressions",,,42601)
+ 00 | f | ("trailing junk after numeric literal at or near ""00"" of jsonpath input",,,42601)
+ 1a | f | ("trailing junk after numeric literal at or near ""1a"" of jsonpath input",,,42601)
(5 rows)
diff --git a/src/test/regress/expected/line.out b/src/test/regress/expected/line.out
index 6baea8fdbd..5682e49c4f 100644
--- a/src/test/regress/expected/line.out
+++ b/src/test/regress/expected/line.out
@@ -92,10 +92,10 @@ SELECT pg_input_is_valid('{1, 1}', 'line');
f
(1 row)
-SELECT pg_input_error_message('{1, 1}', 'line');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type line: "{1, 1}"
+SELECT pg_input_error_info('{1, 1}', 'line');
+ pg_input_error_info
+------------------------------------------------------------
+ ("invalid input syntax for type line: ""{1, 1}""",,,22P02)
(1 row)
SELECT pg_input_is_valid('{0, 0, 0}', 'line');
@@ -104,10 +104,10 @@ SELECT pg_input_is_valid('{0, 0, 0}', 'line');
f
(1 row)
-SELECT pg_input_error_message('{0, 0, 0}', 'line');
- pg_input_error_message
----------------------------------------------------------
- invalid line specification: A and B cannot both be zero
+SELECT pg_input_error_info('{0, 0, 0}', 'line');
+ pg_input_error_info
+---------------------------------------------------------------------
+ ("invalid line specification: A and B cannot both be zero",,,22P02)
(1 row)
SELECT pg_input_is_valid('{1, 1, a}', 'line');
@@ -116,10 +116,10 @@ SELECT pg_input_is_valid('{1, 1, a}', 'line');
f
(1 row)
-SELECT pg_input_error_message('{1, 1, a}', 'line');
- pg_input_error_message
--------------------------------------------------
- invalid input syntax for type line: "{1, 1, a}"
+SELECT pg_input_error_info('{1, 1, a}', 'line');
+ pg_input_error_info
+---------------------------------------------------------------
+ ("invalid input syntax for type line: ""{1, 1, a}""",,,22P02)
(1 row)
SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
@@ -128,10 +128,10 @@ SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
f
(1 row)
-SELECT pg_input_error_message('{1, 1, 1e400}', 'line');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+SELECT pg_input_error_info('{1, 1, 1e400}', 'line');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
@@ -140,9 +140,9 @@ SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
f
(1 row)
-SELECT pg_input_error_message('(1, 1), (1, 1e400)', 'line');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+SELECT pg_input_error_info('(1, 1), (1, 1e400)', 'line');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
diff --git a/src/test/regress/expected/lseg.out b/src/test/regress/expected/lseg.out
index afb323fe04..ffefbfb507 100644
--- a/src/test/regress/expected/lseg.out
+++ b/src/test/regress/expected/lseg.out
@@ -49,9 +49,9 @@ SELECT pg_input_is_valid('[(1,2),(3)]', 'lseg');
f
(1 row)
-SELECT pg_input_error_message('[(1,2),(3)]', 'lseg');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type lseg: "[(1,2),(3)]"
+SELECT pg_input_error_info('[(1,2),(3)]', 'lseg');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("invalid input syntax for type lseg: ""[(1,2),(3)]""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/macaddr.out b/src/test/regress/expected/macaddr.out
index cb646af79b..7ebc9da757 100644
--- a/src/test/regress/expected/macaddr.out
+++ b/src/test/regress/expected/macaddr.out
@@ -165,10 +165,10 @@ SELECT pg_input_is_valid('08:00:2b:01:02:ZZ', 'macaddr');
f
(1 row)
-SELECT pg_input_error_message('08:00:2b:01:02:ZZ', 'macaddr');
- pg_input_error_message
-------------------------------------------------------------
- invalid input syntax for type macaddr: "08:00:2b:01:02:ZZ"
+SELECT pg_input_error_info('08:00:2b:01:02:ZZ', 'macaddr');
+ pg_input_error_info
+--------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr: ""08:00:2b:01:02:ZZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
@@ -177,9 +177,9 @@ SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
f
(1 row)
-SELECT pg_input_error_message('08:00:2b:01:02:', 'macaddr');
- pg_input_error_message
-----------------------------------------------------------
- invalid input syntax for type macaddr: "08:00:2b:01:02:"
+SELECT pg_input_error_info('08:00:2b:01:02:', 'macaddr');
+ pg_input_error_info
+------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr: ""08:00:2b:01:02:""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/macaddr8.out b/src/test/regress/expected/macaddr8.out
index bf681988f8..b047eafade 100644
--- a/src/test/regress/expected/macaddr8.out
+++ b/src/test/regress/expected/macaddr8.out
@@ -359,10 +359,10 @@ SELECT pg_input_is_valid('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
f
(1 row)
-SELECT pg_input_error_message('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
- pg_input_error_message
--------------------------------------------------------------------
- invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:ZZ"
+SELECT pg_input_error_info('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
+ pg_input_error_info
+---------------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr8: ""08:00:2b:01:02:03:04:ZZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
@@ -371,9 +371,9 @@ SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
f
(1 row)
-SELECT pg_input_error_message('08:00:2b:01:02:03:04:', 'macaddr8');
- pg_input_error_message
------------------------------------------------------------------
- invalid input syntax for type macaddr8: "08:00:2b:01:02:03:04:"
+SELECT pg_input_error_info('08:00:2b:01:02:03:04:', 'macaddr8');
+ pg_input_error_info
+-------------------------------------------------------------------------------
+ ("invalid input syntax for type macaddr8: ""08:00:2b:01:02:03:04:""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/money.out b/src/test/regress/expected/money.out
index 46b2eab51a..b87106a0f8 100644
--- a/src/test/regress/expected/money.out
+++ b/src/test/regress/expected/money.out
@@ -338,10 +338,10 @@ SELECT pg_input_is_valid('\x0001', 'money');
f
(1 row)
-SELECT pg_input_error_message('\x0001', 'money');
- pg_input_error_message
------------------------------------------------
- invalid input syntax for type money: "\x0001"
+SELECT pg_input_error_info('\x0001', 'money');
+ pg_input_error_info
+--------------------------------------------------------------
+ ("invalid input syntax for type money: ""\\x0001""",,,22P02)
(1 row)
SELECT pg_input_is_valid('192233720368547758.07', 'money');
@@ -350,10 +350,10 @@ SELECT pg_input_is_valid('192233720368547758.07', 'money');
f
(1 row)
-SELECT pg_input_error_message('192233720368547758.07', 'money');
- pg_input_error_message
---------------------------------------------------------------
- value "192233720368547758.07" is out of range for type money
+SELECT pg_input_error_info('192233720368547758.07', 'money');
+ pg_input_error_info
+----------------------------------------------------------------------------
+ ("value ""192233720368547758.07"" is out of range for type money",,,22003)
(1 row)
-- documented minimums and maximums
diff --git a/src/test/regress/expected/multirangetypes.out b/src/test/regress/expected/multirangetypes.out
index e70896b754..64b8d5fdda 100644
--- a/src/test/regress/expected/multirangetypes.out
+++ b/src/test/regress/expected/multirangetypes.out
@@ -287,10 +287,10 @@ select pg_input_is_valid('{[1,2], [4,5]', 'int4multirange');
f
(1 row)
-select pg_input_error_message('{[1,2], [4,5]', 'int4multirange');
- pg_input_error_message
------------------------------------------------
- malformed multirange literal: "{[1,2], [4,5]"
+select pg_input_error_info('{[1,2], [4,5]', 'int4multirange');
+ pg_input_error_info
+---------------------------------------------------------------------------------------
+ ("malformed multirange literal: ""{[1,2], [4,5]""","Unexpected end of input.",,22P02)
(1 row)
select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
@@ -299,10 +299,10 @@ select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
f
(1 row)
-select pg_input_error_message('{[1,2], [4,zed]}', 'int4multirange');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+select pg_input_error_info('{[1,2], [4,zed]}', 'int4multirange');
+ pg_input_error_info
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index 56a3f3630a..f9c8334ecc 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -2312,10 +2312,10 @@ SELECT pg_input_is_valid('1e400000', 'numeric');
f
(1 row)
-SELECT pg_input_error_message('1e400000', 'numeric');
- pg_input_error_message
---------------------------------
- value overflows numeric format
+SELECT pg_input_error_info('1e400000', 'numeric');
+ pg_input_error_info
+--------------------------------------------
+ ("value overflows numeric format",,,22003)
(1 row)
SELECT pg_input_is_valid('1234.567', 'numeric(8,4)');
@@ -2330,16 +2330,16 @@ SELECT pg_input_is_valid('1234.567', 'numeric(7,4)');
f
(1 row)
-SELECT pg_input_error_message('1234.567', 'numeric(7,4)');
- pg_input_error_message
-------------------------
- numeric field overflow
+SELECT pg_input_error_info('1234.567', 'numeric(7,4)');
+ pg_input_error_info
+-----------------------------------------------------------------------------------------------------------------------
+ ("numeric field overflow","A field with precision 7, scale 4 must round to an absolute value less than 10^3.",,22003)
(1 row)
-SELECT pg_input_error_message('0x1234.567', 'numeric');
- pg_input_error_message
------------------------------------------------------
- invalid input syntax for type numeric: "0x1234.567"
+SELECT pg_input_error_info('0x1234.567', 'numeric');
+ pg_input_error_info
+-------------------------------------------------------------------
+ ("invalid input syntax for type numeric: ""0x1234.567""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/oid.out b/src/test/regress/expected/oid.out
index b664bab5f9..574e07c0fc 100644
--- a/src/test/regress/expected/oid.out
+++ b/src/test/regress/expected/oid.out
@@ -78,10 +78,10 @@ SELECT pg_input_is_valid('01XYZ', 'oid');
f
(1 row)
-SELECT pg_input_error_message('01XYZ', 'oid');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type oid: "01XYZ"
+SELECT pg_input_error_info('01XYZ', 'oid');
+ pg_input_error_info
+----------------------------------------------------------
+ ("invalid input syntax for type oid: ""01XYZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('9999999999', 'oid');
@@ -90,10 +90,10 @@ SELECT pg_input_is_valid('9999999999', 'oid');
f
(1 row)
-SELECT pg_input_error_message('9999999999', 'oid');
- pg_input_error_message
--------------------------------------------------
- value "9999999999" is out of range for type oid
+SELECT pg_input_error_info('9999999999', 'oid');
+ pg_input_error_info
+---------------------------------------------------------------
+ ("value ""9999999999"" is out of range for type oid",,,22003)
(1 row)
-- While we're here, check oidvector as well
@@ -109,10 +109,10 @@ SELECT pg_input_is_valid('01 01XYZ', 'oidvector');
f
(1 row)
-SELECT pg_input_error_message('01 01XYZ', 'oidvector');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type oid: "XYZ"
+SELECT pg_input_error_info('01 01XYZ', 'oidvector');
+ pg_input_error_info
+--------------------------------------------------------
+ ("invalid input syntax for type oid: ""XYZ""",,,22P02)
(1 row)
SELECT pg_input_is_valid('01 9999999999', 'oidvector');
@@ -121,10 +121,10 @@ SELECT pg_input_is_valid('01 9999999999', 'oidvector');
f
(1 row)
-SELECT pg_input_error_message('01 9999999999', 'oidvector');
- pg_input_error_message
--------------------------------------------------
- value "9999999999" is out of range for type oid
+SELECT pg_input_error_info('01 9999999999', 'oidvector');
+ pg_input_error_info
+---------------------------------------------------------------
+ ("value ""9999999999"" is out of range for type oid",,,22003)
(1 row)
SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
diff --git a/src/test/regress/expected/path.out b/src/test/regress/expected/path.out
index 529a5e6fc2..92e6469946 100644
--- a/src/test/regress/expected/path.out
+++ b/src/test/regress/expected/path.out
@@ -87,10 +87,10 @@ SELECT pg_input_is_valid('[(1,2),(3)]', 'path');
f
(1 row)
-SELECT pg_input_error_message('[(1,2),(3)]', 'path');
- pg_input_error_message
----------------------------------------------------
- invalid input syntax for type path: "[(1,2),(3)]"
+SELECT pg_input_error_info('[(1,2),(3)]', 'path');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("invalid input syntax for type path: ""[(1,2),(3)]""",,,22P02)
(1 row)
SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
@@ -99,9 +99,9 @@ SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
f
(1 row)
-SELECT pg_input_error_message('[(1,2,6),(3,4,6)]', 'path');
- pg_input_error_message
----------------------------------------------------------
- invalid input syntax for type path: "[(1,2,6),(3,4,6)]"
+SELECT pg_input_error_info('[(1,2,6),(3,4,6)]', 'path');
+ pg_input_error_info
+-----------------------------------------------------------------------
+ ("invalid input syntax for type path: ""[(1,2,6),(3,4,6)]""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/pg_lsn.out b/src/test/regress/expected/pg_lsn.out
index 01501f8c9b..cbbdc9a1d7 100644
--- a/src/test/regress/expected/pg_lsn.out
+++ b/src/test/regress/expected/pg_lsn.out
@@ -33,10 +33,10 @@ SELECT pg_input_is_valid('16AE7F7', 'pg_lsn');
f
(1 row)
-SELECT pg_input_error_message('16AE7F7', 'pg_lsn');
- pg_input_error_message
--------------------------------------------------
- invalid input syntax for type pg_lsn: "16AE7F7"
+SELECT pg_input_error_info('16AE7F7', 'pg_lsn');
+ pg_input_error_info
+---------------------------------------------------------------
+ ("invalid input syntax for type pg_lsn: ""16AE7F7""",,,22P02)
(1 row)
-- Min/Max aggregation
diff --git a/src/test/regress/expected/point.out b/src/test/regress/expected/point.out
index a716ceb881..76d053cc27 100644
--- a/src/test/regress/expected/point.out
+++ b/src/test/regress/expected/point.out
@@ -470,9 +470,9 @@ SELECT pg_input_is_valid('1,y', 'point');
f
(1 row)
-SELECT pg_input_error_message('1,y', 'point');
- pg_input_error_message
---------------------------------------------
- invalid input syntax for type point: "1,y"
+SELECT pg_input_error_info('1,y', 'point');
+ pg_input_error_info
+----------------------------------------------------------
+ ("invalid input syntax for type point: ""1,y""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/polygon.out b/src/test/regress/expected/polygon.out
index c7d565ad53..22b972f907 100644
--- a/src/test/regress/expected/polygon.out
+++ b/src/test/regress/expected/polygon.out
@@ -313,10 +313,10 @@ SELECT pg_input_is_valid('(2.0,0.8,0.1)', 'polygon');
f
(1 row)
-SELECT pg_input_error_message('(2.0,0.8,0.1)', 'polygon');
- pg_input_error_message
---------------------------------------------------------
- invalid input syntax for type polygon: "(2.0,0.8,0.1)"
+SELECT pg_input_error_info('(2.0,0.8,0.1)', 'polygon');
+ pg_input_error_info
+----------------------------------------------------------------------
+ ("invalid input syntax for type polygon: ""(2.0,0.8,0.1)""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
@@ -325,9 +325,9 @@ SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
f
(1 row)
-SELECT pg_input_error_message('(2.0,xyz)', 'polygon');
- pg_input_error_message
-----------------------------------------------------
- invalid input syntax for type polygon: "(2.0,xyz)"
+SELECT pg_input_error_info('(2.0,xyz)', 'polygon');
+ pg_input_error_info
+------------------------------------------------------------------
+ ("invalid input syntax for type polygon: ""(2.0,xyz)""",,,22P02)
(1 row)
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 95d1e5515f..1a91ee123f 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -2246,10 +2246,10 @@ SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem');
f
(1 row)
-SELECT pg_input_error_message('regress_priv_user1=r/', 'aclitem');
- pg_input_error_message
----------------------------------
- a name must follow the "/" sign
+SELECT pg_input_error_info('regress_priv_user1=r/', 'aclitem');
+ pg_input_error_info
+-----------------------------------------------
+ ("a name must follow the ""/"" sign",,,22P02)
(1 row)
SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem');
@@ -2258,10 +2258,10 @@ SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem')
f
(1 row)
-SELECT pg_input_error_message('regress_priv_user1=r/regress_no_such_user', 'aclitem');
- pg_input_error_message
---------------------------------------------
- role "regress_no_such_user" does not exist
+SELECT pg_input_error_info('regress_priv_user1=r/regress_no_such_user', 'aclitem');
+ pg_input_error_info
+----------------------------------------------------------
+ ("role ""regress_no_such_user"" does not exist",,,42704)
(1 row)
SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
@@ -2270,10 +2270,10 @@ SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
f
(1 row)
-SELECT pg_input_error_message('regress_priv_user1=rY', 'aclitem');
- pg_input_error_message
-----------------------------------------------------------
- invalid mode character: must be one of "arwdDxtXUCTcsAm"
+SELECT pg_input_error_info('regress_priv_user1=rY', 'aclitem');
+ pg_input_error_info
+------------------------------------------------------------------------
+ ("invalid mode character: must be one of ""arwdDxtXUCTcsAm""",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index a3e9e447af..0571e72534 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -188,10 +188,10 @@ select pg_input_is_valid('(1,4', 'int4range');
f
(1 row)
-select pg_input_error_message('(1,4', 'int4range');
- pg_input_error_message
----------------------------------
- malformed range literal: "(1,4"
+select pg_input_error_info('(1,4', 'int4range');
+ pg_input_error_info
+-------------------------------------------------------------------------
+ ("malformed range literal: ""(1,4""","Unexpected end of input.",,22P02)
(1 row)
select pg_input_is_valid('(4,1)', 'int4range');
@@ -200,10 +200,10 @@ select pg_input_is_valid('(4,1)', 'int4range');
f
(1 row)
-select pg_input_error_message('(4,1)', 'int4range');
- pg_input_error_message
--------------------------------------------------------------------
- range lower bound must be less than or equal to range upper bound
+select pg_input_error_info('(4,1)', 'int4range');
+ pg_input_error_info
+-------------------------------------------------------------------------------
+ ("range lower bound must be less than or equal to range upper bound",,,22000)
(1 row)
select pg_input_is_valid('(4,zed)', 'int4range');
@@ -212,10 +212,10 @@ select pg_input_is_valid('(4,zed)', 'int4range');
f
(1 row)
-select pg_input_error_message('(4,zed)', 'int4range');
- pg_input_error_message
-----------------------------------------------
- invalid input syntax for type integer: "zed"
+select pg_input_error_info('(4,zed)', 'int4range');
+ pg_input_error_info
+------------------------------------------------------------
+ ("invalid input syntax for type integer: ""zed""",,,22P02)
(1 row)
select pg_input_is_valid('[1,2147483647]', 'int4range');
@@ -224,10 +224,10 @@ select pg_input_is_valid('[1,2147483647]', 'int4range');
f
(1 row)
-select pg_input_error_message('[1,2147483647]', 'int4range');
- pg_input_error_message
-------------------------
- integer out of range
+select pg_input_error_info('[1,2147483647]', 'int4range');
+ pg_input_error_info
+----------------------------------
+ ("integer out of range",,,22003)
(1 row)
select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
@@ -236,10 +236,10 @@ select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
f
(1 row)
-select pg_input_error_message('[2000-01-01,5874897-12-31]', 'daterange');
- pg_input_error_message
-------------------------
- date out of range
+select pg_input_error_info('[2000-01-01,5874897-12-31]', 'daterange');
+ pg_input_error_info
+-------------------------------
+ ("date out of range",,,22008)
(1 row)
--
diff --git a/src/test/regress/expected/regproc.out b/src/test/regress/expected/regproc.out
index a034fbb346..13e3b7eda9 100644
--- a/src/test/regress/expected/regproc.out
+++ b/src/test/regress/expected/regproc.out
@@ -448,10 +448,10 @@ SELECT to_regnamespace('foo.bar');
(1 row)
-- Test soft-error API
-SELECT pg_input_error_message('ng_catalog.pg_class', 'regclass');
- pg_input_error_message
------------------------------------------------
- relation "ng_catalog.pg_class" does not exist
+SELECT pg_input_error_info('ng_catalog.pg_class', 'regclass');
+ pg_input_error_info
+-------------------------------------------------------------
+ ("relation ""ng_catalog.pg_class"" does not exist",,,42P01)
(1 row)
SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
@@ -460,87 +460,87 @@ SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
f
(1 row)
-SELECT pg_input_error_message('no_such_config', 'regconfig');
- pg_input_error_message
------------------------------------------------------------
- text search configuration "no_such_config" does not exist
+SELECT pg_input_error_info('no_such_config', 'regconfig');
+ pg_input_error_info
+-------------------------------------------------------------------------
+ ("text search configuration ""no_such_config"" does not exist",,,42704)
(1 row)
-SELECT pg_input_error_message('no_such_dictionary', 'regdictionary');
- pg_input_error_message
-------------------------------------------------------------
- text search dictionary "no_such_dictionary" does not exist
+SELECT pg_input_error_info('no_such_dictionary', 'regdictionary');
+ pg_input_error_info
+--------------------------------------------------------------------------
+ ("text search dictionary ""no_such_dictionary"" does not exist",,,42704)
(1 row)
-SELECT pg_input_error_message('Nonexistent', 'regnamespace');
- pg_input_error_message
--------------------------------------
- schema "nonexistent" does not exist
+SELECT pg_input_error_info('Nonexistent', 'regnamespace');
+ pg_input_error_info
+---------------------------------------------------
+ ("schema ""nonexistent"" does not exist",,,3F000)
(1 row)
-SELECT pg_input_error_message('ng_catalog.||/', 'regoper');
- pg_input_error_message
------------------------------------------
- operator does not exist: ng_catalog.||/
+SELECT pg_input_error_info('ng_catalog.||/', 'regoper');
+ pg_input_error_info
+-----------------------------------------------------
+ ("operator does not exist: ng_catalog.||/",,,42883)
(1 row)
-SELECT pg_input_error_message('-', 'regoper');
- pg_input_error_message
---------------------------------
- more than one operator named -
+SELECT pg_input_error_info('-', 'regoper');
+ pg_input_error_info
+--------------------------------------------
+ ("more than one operator named -",,,42725)
(1 row)
-SELECT pg_input_error_message('ng_catalog.+(int4,int4)', 'regoperator');
- pg_input_error_message
---------------------------------------------------
- operator does not exist: ng_catalog.+(int4,int4)
+SELECT pg_input_error_info('ng_catalog.+(int4,int4)', 'regoperator');
+ pg_input_error_info
+--------------------------------------------------------------
+ ("operator does not exist: ng_catalog.+(int4,int4)",,,42883)
(1 row)
-SELECT pg_input_error_message('-', 'regoperator');
- pg_input_error_message
------------------------------
- expected a left parenthesis
+SELECT pg_input_error_info('-', 'regoperator');
+ pg_input_error_info
+-----------------------------------------
+ ("expected a left parenthesis",,,22P02)
(1 row)
-SELECT pg_input_error_message('ng_catalog.now', 'regproc');
- pg_input_error_message
-------------------------------------------
- function "ng_catalog.now" does not exist
+SELECT pg_input_error_info('ng_catalog.now', 'regproc');
+ pg_input_error_info
+--------------------------------------------------------
+ ("function ""ng_catalog.now"" does not exist",,,42883)
(1 row)
-SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
- pg_input_error_message
----------------------------------------------------
- function "ng_catalog.abs(numeric)" does not exist
+SELECT pg_input_error_info('ng_catalog.abs(numeric)', 'regprocedure');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("function ""ng_catalog.abs(numeric)"" does not exist",,,42883)
(1 row)
-SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
- pg_input_error_message
-------------------------------
- expected a right parenthesis
+SELECT pg_input_error_info('ng_catalog.abs(numeric', 'regprocedure');
+ pg_input_error_info
+------------------------------------------
+ ("expected a right parenthesis",,,22P02)
(1 row)
-SELECT pg_input_error_message('regress_regrole_test', 'regrole');
- pg_input_error_message
---------------------------------------------
- role "regress_regrole_test" does not exist
+SELECT pg_input_error_info('regress_regrole_test', 'regrole');
+ pg_input_error_info
+----------------------------------------------------------
+ ("role ""regress_regrole_test"" does not exist",,,42704)
(1 row)
-SELECT pg_input_error_message('no_such_type', 'regtype');
- pg_input_error_message
-------------------------------------
- type "no_such_type" does not exist
+SELECT pg_input_error_info('no_such_type', 'regtype');
+ pg_input_error_info
+--------------------------------------------------
+ ("type ""no_such_type"" does not exist",,,42704)
(1 row)
-- Some cases that should be soft errors, but are not yet
-SELECT pg_input_error_message('incorrect type name syntax', 'regtype');
+SELECT pg_input_error_info('incorrect type name syntax', 'regtype');
ERROR: syntax error at or near "type"
-LINE 1: SELECT pg_input_error_message('incorrect type name syntax', ...
+LINE 1: SELECT pg_input_error_info('incorrect type name syntax', 're...
^
CONTEXT: invalid type name "incorrect type name syntax"
-SELECT pg_input_error_message('numeric(1,2,3)', 'regtype'); -- bogus typmod
+SELECT pg_input_error_info('numeric(1,2,3)', 'regtype'); -- bogus typmod
ERROR: invalid NUMERIC type modifier
-SELECT pg_input_error_message('way.too.many.names', 'regtype');
+SELECT pg_input_error_info('way.too.many.names', 'regtype');
ERROR: improper qualified name (too many dotted names): way.too.many.names
-SELECT pg_input_error_message('no_such_catalog.schema.name', 'regtype');
+SELECT pg_input_error_info('no_such_catalog.schema.name', 'regtype');
ERROR: cross-database references are not implemented: no_such_catalog.schema.name
diff --git a/src/test/regress/expected/rowtypes.out b/src/test/regress/expected/rowtypes.out
index 801d9e556b..f00453c31f 100644
--- a/src/test/regress/expected/rowtypes.out
+++ b/src/test/regress/expected/rowtypes.out
@@ -88,16 +88,16 @@ SELECT pg_input_is_valid('(1,zed)', 'complex');
f
(1 row)
-SELECT pg_input_error_message('(1,zed)', 'complex');
- pg_input_error_message
--------------------------------------------------------
- invalid input syntax for type double precision: "zed"
+SELECT pg_input_error_info('(1,zed)', 'complex');
+ pg_input_error_info
+---------------------------------------------------------------------
+ ("invalid input syntax for type double precision: ""zed""",,,22P02)
(1 row)
-SELECT pg_input_error_message('(1,1e400)', 'complex');
- pg_input_error_message
----------------------------------------------------
- "1e400" is out of range for type double precision
+SELECT pg_input_error_info('(1,1e400)', 'complex');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("""1e400"" is out of range for type double precision",,,22003)
(1 row)
create temp table quadtable(f1 int, q quad);
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index f028c1f10f..d6046de7bc 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -280,22 +280,22 @@ SELECT pg_input_is_valid(E'\\xDeAdBeE', 'bytea');
f
(1 row)
-SELECT pg_input_error_message(E'\\xDeAdBeE', 'bytea');
- pg_input_error_message
-------------------------------------------------
- invalid hexadecimal data: odd number of digits
+SELECT pg_input_error_info(E'\\xDeAdBeE', 'bytea');
+ pg_input_error_info
+------------------------------------------------------------
+ ("invalid hexadecimal data: odd number of digits",,,22023)
(1 row)
-SELECT pg_input_error_message(E'\\xDeAdBeEx', 'bytea');
- pg_input_error_message
---------------------------------
- invalid hexadecimal digit: "x"
+SELECT pg_input_error_info(E'\\xDeAdBeEx', 'bytea');
+ pg_input_error_info
+----------------------------------------------
+ ("invalid hexadecimal digit: ""x""",,,22023)
(1 row)
-SELECT pg_input_error_message(E'foo\\99bar', 'bytea');
- pg_input_error_message
--------------------------------------
- invalid input syntax for type bytea
+SELECT pg_input_error_info(E'foo\\99bar', 'bytea');
+ pg_input_error_info
+-------------------------------------------------
+ ("invalid input syntax for type bytea",,,22P02)
(1 row)
--
diff --git a/src/test/regress/expected/tid.out b/src/test/regress/expected/tid.out
index ff67ed43f0..bd9a57910f 100644
--- a/src/test/regress/expected/tid.out
+++ b/src/test/regress/expected/tid.out
@@ -24,10 +24,10 @@ SELECT pg_input_is_valid('(0)', 'tid');
f
(1 row)
-SELECT pg_input_error_message('(0)', 'tid');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type tid: "(0)"
+SELECT pg_input_error_info('(0)', 'tid');
+ pg_input_error_info
+--------------------------------------------------------
+ ("invalid input syntax for type tid: ""(0)""",,,22P02)
(1 row)
SELECT pg_input_is_valid('(0,-1)', 'tid');
@@ -36,10 +36,10 @@ SELECT pg_input_is_valid('(0,-1)', 'tid');
f
(1 row)
-SELECT pg_input_error_message('(0,-1)', 'tid');
- pg_input_error_message
----------------------------------------------
- invalid input syntax for type tid: "(0,-1)"
+SELECT pg_input_error_info('(0,-1)', 'tid');
+ pg_input_error_info
+-----------------------------------------------------------
+ ("invalid input syntax for type tid: ""(0,-1)""",,,22P02)
(1 row)
-- tests for functions related to TID handling
diff --git a/src/test/regress/expected/time.out b/src/test/regress/expected/time.out
index a44caededd..d982d591e1 100644
--- a/src/test/regress/expected/time.out
+++ b/src/test/regress/expected/time.out
@@ -133,16 +133,16 @@ SELECT pg_input_is_valid('15:36:39 America/New_York', 'time');
f
(1 row)
-SELECT pg_input_error_message('25:00:00', 'time');
- pg_input_error_message
-------------------------------------------------
- date/time field value out of range: "25:00:00"
+SELECT pg_input_error_info('25:00:00', 'time');
+ pg_input_error_info
+--------------------------------------------------------------
+ ("date/time field value out of range: ""25:00:00""",,,22008)
(1 row)
-SELECT pg_input_error_message('15:36:39 America/New_York', 'time');
- pg_input_error_message
------------------------------------------------------------------
- invalid input syntax for type time: "15:36:39 America/New_York"
+SELECT pg_input_error_info('15:36:39 America/New_York', 'time');
+ pg_input_error_info
+-------------------------------------------------------------------------------
+ ("invalid input syntax for type time: ""15:36:39 America/New_York""",,,22007)
(1 row)
--
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index eef2f7001c..7efa86d509 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -144,16 +144,16 @@ SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
f
(1 row)
-SELECT pg_input_error_message('garbage', 'timestamp');
- pg_input_error_message
-----------------------------------------------------
- invalid input syntax for type timestamp: "garbage"
+SELECT pg_input_error_info('garbage', 'timestamp');
+ pg_input_error_info
+------------------------------------------------------------------
+ ("invalid input syntax for type timestamp: ""garbage""",,,22007)
(1 row)
-SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
- pg_input_error_message
---------------------------------------------
- time zone "nehwon/lankhmar" not recognized
+SELECT pg_input_error_info('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
+ pg_input_error_info
+----------------------------------------------------------
+ ("time zone ""nehwon/lankhmar"" not recognized",,,22023)
(1 row)
-- Check date conversion and date arithmetic
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index b85a93a3c2..6e9c4152f4 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -195,16 +195,16 @@ SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
f
(1 row)
-SELECT pg_input_error_message('garbage', 'timestamptz');
- pg_input_error_message
--------------------------------------------------------------------
- invalid input syntax for type timestamp with time zone: "garbage"
+SELECT pg_input_error_info('garbage', 'timestamptz');
+ pg_input_error_info
+---------------------------------------------------------------------------------
+ ("invalid input syntax for type timestamp with time zone: ""garbage""",,,22007)
(1 row)
-SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
- pg_input_error_message
---------------------------------------------
- time zone "nehwon/lankhmar" not recognized
+SELECT pg_input_error_info('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
+ pg_input_error_info
+----------------------------------------------------------
+ ("time zone ""nehwon/lankhmar"" not recognized",,,22023)
(1 row)
-- Check date conversion and date arithmetic
diff --git a/src/test/regress/expected/timetz.out b/src/test/regress/expected/timetz.out
index 984285663b..8e8fd215f4 100644
--- a/src/test/regress/expected/timetz.out
+++ b/src/test/regress/expected/timetz.out
@@ -150,16 +150,16 @@ SELECT pg_input_is_valid('15:36:39 America/New_York', 'timetz');
f
(1 row)
-SELECT pg_input_error_message('25:00:00 PDT', 'timetz');
- pg_input_error_message
-----------------------------------------------------
- date/time field value out of range: "25:00:00 PDT"
+SELECT pg_input_error_info('25:00:00 PDT', 'timetz');
+ pg_input_error_info
+------------------------------------------------------------------
+ ("date/time field value out of range: ""25:00:00 PDT""",,,22008)
(1 row)
-SELECT pg_input_error_message('15:36:39 America/New_York', 'timetz');
- pg_input_error_message
---------------------------------------------------------------------------------
- invalid input syntax for type time with time zone: "15:36:39 America/New_York"
+SELECT pg_input_error_info('15:36:39 America/New_York', 'timetz');
+ pg_input_error_info
+----------------------------------------------------------------------------------------------
+ ("invalid input syntax for type time with time zone: ""15:36:39 America/New_York""",,,22007)
(1 row)
--
diff --git a/src/test/regress/expected/tstypes.out b/src/test/regress/expected/tstypes.out
index a8785cd708..ee885872d3 100644
--- a/src/test/regress/expected/tstypes.out
+++ b/src/test/regress/expected/tstypes.out
@@ -102,10 +102,10 @@ SELECT pg_input_is_valid($$''$$, 'tsvector');
f
(1 row)
-SELECT pg_input_error_message($$''$$, 'tsvector');
- pg_input_error_message
---------------------------------
- syntax error in tsvector: "''"
+SELECT pg_input_error_info($$''$$, 'tsvector');
+ pg_input_error_info
+----------------------------------------------
+ ("syntax error in tsvector: ""''""",,,42601)
(1 row)
--Base tsquery test
@@ -404,16 +404,16 @@ SELECT pg_input_is_valid('foo!', 'tsquery');
f
(1 row)
-SELECT pg_input_error_message('foo!', 'tsquery');
- pg_input_error_message
----------------------------------
- syntax error in tsquery: "foo!"
+SELECT pg_input_error_info('foo!', 'tsquery');
+ pg_input_error_info
+-----------------------------------------------
+ ("syntax error in tsquery: ""foo!""",,,42601)
(1 row)
-SELECT pg_input_error_message('a <100000> b', 'tsquery');
- pg_input_error_message
----------------------------------------------------------------------------------------
- distance in phrase operator must be an integer value between zero and 16384 inclusive
+SELECT pg_input_error_info('a <100000> b', 'tsquery');
+ pg_input_error_info
+---------------------------------------------------------------------------------------------------
+ ("distance in phrase operator must be an integer value between zero and 16384 inclusive",,,22023)
(1 row)
--comparisons
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index 0f47232009..7cfbd45b86 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -46,10 +46,10 @@ SELECT pg_input_is_valid('11', 'uuid');
f
(1 row)
-SELECT pg_input_error_message('11', 'uuid');
- pg_input_error_message
-------------------------------------------
- invalid input syntax for type uuid: "11"
+SELECT pg_input_error_info('11', 'uuid');
+ pg_input_error_info
+--------------------------------------------------------
+ ("invalid input syntax for type uuid: ""11""",,,22P02)
(1 row)
--inserting three input formats
diff --git a/src/test/regress/expected/varchar.out b/src/test/regress/expected/varchar.out
index 62b683d86f..96ce47490d 100644
--- a/src/test/regress/expected/varchar.out
+++ b/src/test/regress/expected/varchar.out
@@ -124,9 +124,9 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
f
(1 row)
-SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+SELECT pg_input_error_info('abcde', 'varchar(4)');
+ pg_input_error_info
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/varchar_1.out b/src/test/regress/expected/varchar_1.out
index 6690f81c0b..d65a4d80df 100644
--- a/src/test/regress/expected/varchar_1.out
+++ b/src/test/regress/expected/varchar_1.out
@@ -124,9 +124,9 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
f
(1 row)
-SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+SELECT pg_input_error_info('abcde', 'varchar(4)');
+ pg_input_error_info
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/varchar_2.out b/src/test/regress/expected/varchar_2.out
index ad8aa7c693..14f7655239 100644
--- a/src/test/regress/expected/varchar_2.out
+++ b/src/test/regress/expected/varchar_2.out
@@ -124,9 +124,9 @@ SELECT pg_input_is_valid('abcde', 'varchar(4)');
f
(1 row)
-SELECT pg_input_error_message('abcde', 'varchar(4)');
- pg_input_error_message
-----------------------------------------------
- value too long for type character varying(4)
+SELECT pg_input_error_info('abcde', 'varchar(4)');
+ pg_input_error_info
+----------------------------------------------------------
+ ("value too long for type character varying(4)",,,22001)
(1 row)
diff --git a/src/test/regress/expected/xid.out b/src/test/regress/expected/xid.out
index e62f701943..ddda75e57e 100644
--- a/src/test/regress/expected/xid.out
+++ b/src/test/regress/expected/xid.out
@@ -43,10 +43,10 @@ SELECT pg_input_is_valid('asdf', 'xid');
f
(1 row)
-SELECT pg_input_error_message('0xffffffffff', 'xid');
- pg_input_error_message
----------------------------------------------------
- value "0xffffffffff" is out of range for type xid
+SELECT pg_input_error_info('0xffffffffff', 'xid');
+ pg_input_error_info
+-----------------------------------------------------------------
+ ("value ""0xffffffffff"" is out of range for type xid",,,22003)
(1 row)
SELECT pg_input_is_valid('42', 'xid8');
@@ -61,10 +61,10 @@ SELECT pg_input_is_valid('asdf', 'xid8');
f
(1 row)
-SELECT pg_input_error_message('0xffffffffffffffffffff', 'xid8');
- pg_input_error_message
---------------------------------------------------------------
- value "0xffffffffffffffffffff" is out of range for type xid8
+SELECT pg_input_error_info('0xffffffffffffffffffff', 'xid8');
+ pg_input_error_info
+----------------------------------------------------------------------------
+ ("value ""0xffffffffffffffffffff"" is out of range for type xid8",,,22003)
(1 row)
-- equality
@@ -223,10 +223,10 @@ select pg_input_is_valid('31:12:', 'pg_snapshot');
f
(1 row)
-select pg_input_error_message('31:12:', 'pg_snapshot');
- pg_input_error_message
------------------------------------------------------
- invalid input syntax for type pg_snapshot: "31:12:"
+select pg_input_error_info('31:12:', 'pg_snapshot');
+ pg_input_error_info
+-------------------------------------------------------------------
+ ("invalid input syntax for type pg_snapshot: ""31:12:""",,,22P02)
(1 row)
select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
@@ -235,10 +235,10 @@ select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
f
(1 row)
-select pg_input_error_message('12:16:14,13', 'pg_snapshot');
- pg_input_error_message
-----------------------------------------------------------
- invalid input syntax for type pg_snapshot: "12:16:14,13"
+select pg_input_error_info('12:16:14,13', 'pg_snapshot');
+ pg_input_error_info
+------------------------------------------------------------------------
+ ("invalid input syntax for type pg_snapshot: ""12:16:14,13""",,,22P02)
(1 row)
create temp table snapshot_test (
diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out
index 3c357a9c7e..ad852dc2f7 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -31,9 +31,9 @@ SELECT pg_input_is_valid('<value>one</', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<value>one</', 'xml');
- pg_input_error_message
-------------------------
+SELECT message FROM pg_input_error_info('<value>one</', 'xml');
+ message
+---------------------
invalid XML content
(1 row)
@@ -43,8 +43,8 @@ SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_error_message
+SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ message
----------------------------------------------
invalid XML content: invalid XML declaration
(1 row)
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index 378b412db0..70fe34a04f 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -29,13 +29,13 @@ DETAIL: This functionality requires the server to be built with libxml support.
SELECT pg_input_is_valid('<value>one</', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
-SELECT pg_input_error_message('<value>one</', 'xml');
+SELECT message FROM pg_input_error_info('<value>one</', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
ERROR: unsupported XML feature
DETAIL: This functionality requires the server to be built with libxml support.
SELECT xmlcomment('test');
diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out
index 42055c5003..e01f431219 100644
--- a/src/test/regress/expected/xml_2.out
+++ b/src/test/regress/expected/xml_2.out
@@ -29,8 +29,8 @@ SELECT pg_input_is_valid('<value>one</', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<value>one</', 'xml');
- pg_input_error_message
+SELECT message FROM pg_input_error_info('<value>one</', 'xml');
+ message
------------------------
invalid XML content
(1 row)
@@ -41,8 +41,8 @@ SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
f
(1 row)
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
- pg_input_error_message
+SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+ message
----------------------------------------------
invalid XML content: invalid XML declaration
(1 row)
diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql
index 38e8dd440b..954c1225b4 100644
--- a/src/test/regress/sql/arrays.sql
+++ b/src/test/regress/sql/arrays.sql
@@ -117,7 +117,7 @@ SELECT a,b,c FROM arrtest;
SELECT pg_input_is_valid('{1,2,3}', 'integer[]');
SELECT pg_input_is_valid('{1,2', 'integer[]');
SELECT pg_input_is_valid('{1,zed}', 'integer[]');
-SELECT pg_input_error_message('{1,zed}', 'integer[]');
+SELECT pg_input_error_info('{1,zed}', 'integer[]');
-- test mixed slice/scalar subscripting
select '{{1,2,3},{4,5,6},{7,8,9}}'::int[];
diff --git a/src/test/regress/sql/bit.sql b/src/test/regress/sql/bit.sql
index 8814249c2a..5765ee8672 100644
--- a/src/test/regress/sql/bit.sql
+++ b/src/test/regress/sql/bit.sql
@@ -232,13 +232,13 @@ TABLE bit_defaults;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('01010001', 'bit(10)');
-SELECT pg_input_error_message('01010001', 'bit(10)');
+SELECT pg_input_error_info('01010001', 'bit(10)');
SELECT pg_input_is_valid('01010Z01', 'bit(8)');
-SELECT pg_input_error_message('01010Z01', 'bit(8)');
+SELECT pg_input_error_info('01010Z01', 'bit(8)');
SELECT pg_input_is_valid('x01010Z01', 'bit(32)');
-SELECT pg_input_error_message('x01010Z01', 'bit(32)');
+SELECT pg_input_error_info('x01010Z01', 'bit(32)');
SELECT pg_input_is_valid('01010Z01', 'varbit');
-SELECT pg_input_error_message('01010Z01', 'varbit');
+SELECT pg_input_error_info('01010Z01', 'varbit');
SELECT pg_input_is_valid('x01010Z01', 'varbit');
-SELECT pg_input_error_message('x01010Z01', 'varbit');
+SELECT pg_input_error_info('x01010Z01', 'varbit');
diff --git a/src/test/regress/sql/boolean.sql b/src/test/regress/sql/boolean.sql
index dfaa55dd0f..9126831dba 100644
--- a/src/test/regress/sql/boolean.sql
+++ b/src/test/regress/sql/boolean.sql
@@ -65,7 +65,7 @@ SELECT bool '' AS error;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('true', 'bool');
SELECT pg_input_is_valid('asdf', 'bool');
-SELECT pg_input_error_message('junk', 'bool');
+SELECT pg_input_error_info('junk', 'bool');
-- and, or, not in qualifications
diff --git a/src/test/regress/sql/box.sql b/src/test/regress/sql/box.sql
index 02e100391b..cbf967f3ae 100644
--- a/src/test/regress/sql/box.sql
+++ b/src/test/regress/sql/box.sql
@@ -284,6 +284,6 @@ RESET enable_bitmapscan;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('200', 'box');
-SELECT pg_input_error_message('200', 'box');
+SELECT pg_input_error_info('200', 'box');
SELECT pg_input_is_valid('((200,300),(500, xyz))', 'box');
-SELECT pg_input_error_message('((200,300),(500, xyz))', 'box');
+SELECT pg_input_error_info('((200,300),(500, xyz))', 'box');
diff --git a/src/test/regress/sql/char.sql b/src/test/regress/sql/char.sql
index 8aa43b0fb8..f8e3304053 100644
--- a/src/test/regress/sql/char.sql
+++ b/src/test/regress/sql/char.sql
@@ -75,7 +75,7 @@ SELECT * FROM CHAR_TBL;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('abcd ', 'char(4)');
SELECT pg_input_is_valid('abcde', 'char(4)');
-SELECT pg_input_error_message('abcde', 'char(4)');
+SELECT pg_input_error_info('abcde', 'char(4)');
--
-- Also test "char", which is an ad-hoc one-byte type. It can only
diff --git a/src/test/regress/sql/date.sql b/src/test/regress/sql/date.sql
index 89982dd2f8..8ced2c9bc5 100644
--- a/src/test/regress/sql/date.sql
+++ b/src/test/regress/sql/date.sql
@@ -197,8 +197,8 @@ SELECT date '5874898-01-01'; -- out of range
SELECT pg_input_is_valid('now', 'date');
SELECT pg_input_is_valid('garbage', 'date');
SELECT pg_input_is_valid('6874898-01-01', 'date');
-SELECT pg_input_error_message('garbage', 'date');
-SELECT pg_input_error_message('6874898-01-01', 'date');
+SELECT pg_input_error_info('garbage', 'date');
+SELECT pg_input_error_info('6874898-01-01', 'date');
RESET datestyle;
diff --git a/src/test/regress/sql/domain.sql b/src/test/regress/sql/domain.sql
index 1558bd9a33..8ee0170369 100644
--- a/src/test/regress/sql/domain.sql
+++ b/src/test/regress/sql/domain.sql
@@ -77,12 +77,12 @@ create domain weirdfloat float8 check((1 / value) < 10);
select pg_input_is_valid('1', 'positiveint');
select pg_input_is_valid('junk', 'positiveint');
select pg_input_is_valid('-1', 'positiveint');
-select pg_input_error_message('junk', 'positiveint');
-select pg_input_error_message('-1', 'positiveint');
-select pg_input_error_message('junk', 'weirdfloat');
-select pg_input_error_message('0.01', 'weirdfloat');
+select pg_input_error_info('junk', 'positiveint');
+select pg_input_error_info('-1', 'positiveint');
+select pg_input_error_info('junk', 'weirdfloat');
+select pg_input_error_info('0.01', 'weirdfloat');
-- We currently can't trap errors raised in the CHECK expression itself
-select pg_input_error_message('0', 'weirdfloat');
+select pg_input_error_info('0', 'weirdfloat');
drop domain positiveint;
drop domain weirdfloat;
diff --git a/src/test/regress/sql/enum.sql b/src/test/regress/sql/enum.sql
index c87656589b..417fa6eddd 100644
--- a/src/test/regress/sql/enum.sql
+++ b/src/test/regress/sql/enum.sql
@@ -18,8 +18,8 @@ SELECT 'mauve'::rainbow;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('red', 'rainbow');
SELECT pg_input_is_valid('mauve', 'rainbow');
-SELECT pg_input_error_message('mauve', 'rainbow');
-SELECT pg_input_error_message(repeat('too_long', 32), 'rainbow');
+SELECT pg_input_error_info('mauve', 'rainbow');
+SELECT pg_input_error_info(repeat('too_long', 32), 'rainbow');
--
-- adding new values
diff --git a/src/test/regress/sql/float4.sql b/src/test/regress/sql/float4.sql
index 061477726b..4f344997e9 100644
--- a/src/test/regress/sql/float4.sql
+++ b/src/test/regress/sql/float4.sql
@@ -40,7 +40,7 @@ INSERT INTO FLOAT4_TBL(f1) VALUES ('123 5');
SELECT pg_input_is_valid('34.5', 'float4');
SELECT pg_input_is_valid('xyz', 'float4');
SELECT pg_input_is_valid('1e400', 'float4');
-SELECT pg_input_error_message('1e400', 'float4');
+SELECT pg_input_error_info('1e400', 'float4');
-- special inputs
SELECT 'NaN'::float4;
diff --git a/src/test/regress/sql/float8.sql b/src/test/regress/sql/float8.sql
index c276a5324c..d5fd409580 100644
--- a/src/test/regress/sql/float8.sql
+++ b/src/test/regress/sql/float8.sql
@@ -38,7 +38,7 @@ INSERT INTO FLOAT8_TBL(f1) VALUES ('123 5');
SELECT pg_input_is_valid('34.5', 'float8');
SELECT pg_input_is_valid('xyz', 'float8');
SELECT pg_input_is_valid('1e4000', 'float8');
-SELECT pg_input_error_message('1e4000', 'float8');
+SELECT pg_input_error_info('1e4000', 'float8');
-- special inputs
SELECT 'NaN'::float8;
diff --git a/src/test/regress/sql/geometry.sql b/src/test/regress/sql/geometry.sql
index 309234b76c..70576ab7ff 100644
--- a/src/test/regress/sql/geometry.sql
+++ b/src/test/regress/sql/geometry.sql
@@ -526,6 +526,6 @@ SELECT * FROM polygon_tbl WHERE f1 @> '((1,1),(2,2),(2,1))'::polygon
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('(1', 'circle');
-SELECT pg_input_error_message('1,', 'circle');
+SELECT pg_input_error_info('1,', 'circle');
SELECT pg_input_is_valid('(1,2),-1', 'circle');
-SELECT pg_input_error_message('(1,2),-1', 'circle');
+SELECT pg_input_error_info('(1,2),-1', 'circle');
diff --git a/src/test/regress/sql/inet.sql b/src/test/regress/sql/inet.sql
index abfcd4242f..ae3f19d285 100644
--- a/src/test/regress/sql/inet.sql
+++ b/src/test/regress/sql/inet.sql
@@ -255,9 +255,9 @@ SELECT a FROM (VALUES
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('1234', 'cidr');
-SELECT pg_input_error_message('1234', 'cidr');
+SELECT pg_input_error_info('1234', 'cidr');
SELECT pg_input_is_valid('192.168.198.200/24', 'cidr');
-SELECT pg_input_error_message('192.168.198.200/24', 'cidr');
+SELECT pg_input_error_info('192.168.198.200/24', 'cidr');
SELECT pg_input_is_valid('1234', 'inet');
-SELECT pg_input_error_message('1234', 'inet');
+SELECT pg_input_error_info('1234', 'inet');
diff --git a/src/test/regress/sql/int2.sql b/src/test/regress/sql/int2.sql
index ce8ac97963..7da875c402 100644
--- a/src/test/regress/sql/int2.sql
+++ b/src/test/regress/sql/int2.sql
@@ -21,12 +21,12 @@ SELECT * FROM INT2_TBL;
SELECT pg_input_is_valid('34', 'int2');
SELECT pg_input_is_valid('asdf', 'int2');
SELECT pg_input_is_valid('50000', 'int2');
-SELECT pg_input_error_message('50000', 'int2');
+SELECT pg_input_error_info('50000', 'int2');
-- While we're here, check int2vector as well
SELECT pg_input_is_valid(' 1 3 5 ', 'int2vector');
-SELECT pg_input_error_message('1 asdf', 'int2vector');
-SELECT pg_input_error_message('50000', 'int2vector');
+SELECT pg_input_error_info('1 asdf', 'int2vector');
+SELECT pg_input_error_info('50000', 'int2vector');
SELECT * FROM INT2_TBL AS f(a, b);
diff --git a/src/test/regress/sql/int4.sql b/src/test/regress/sql/int4.sql
index 146963edfb..659d1ab939 100644
--- a/src/test/regress/sql/int4.sql
+++ b/src/test/regress/sql/int4.sql
@@ -21,7 +21,7 @@ SELECT * FROM INT4_TBL;
SELECT pg_input_is_valid('34', 'int4');
SELECT pg_input_is_valid('asdf', 'int4');
SELECT pg_input_is_valid('1000000000000', 'int4');
-SELECT pg_input_error_message('1000000000000', 'int4');
+SELECT pg_input_error_info('1000000000000', 'int4');
SELECT i.* FROM INT4_TBL i WHERE i.f1 <> int2 '0';
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index c85717c072..14bc99bd5e 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -20,7 +20,7 @@ SELECT * FROM INT8_TBL;
SELECT pg_input_is_valid('34', 'int8');
SELECT pg_input_is_valid('asdf', 'int8');
SELECT pg_input_is_valid('10000000000000000000', 'int8');
-SELECT pg_input_error_message('10000000000000000000', 'int8');
+SELECT pg_input_error_info('10000000000000000000', 'int8');
-- int8/int8 cmp
SELECT * FROM INT8_TBL WHERE q2 = 4567890123456789;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index af8e1ca0f4..171bbbf653 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -36,8 +36,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago');
SELECT pg_input_is_valid('1.5 weeks', 'interval');
SELECT pg_input_is_valid('garbage', 'interval');
SELECT pg_input_is_valid('@ 30 eons ago', 'interval');
-SELECT pg_input_error_message('garbage', 'interval');
-SELECT pg_input_error_message('@ 30 eons ago', 'interval');
+SELECT pg_input_error_info('garbage', 'interval');
+SELECT pg_input_error_info('@ 30 eons ago', 'interval');
-- test interval operators
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 21534ed959..68e4c9af8f 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -84,7 +84,7 @@ SELECT '{
-- test non-error-throwing input
select pg_input_is_valid('{"a":true}', 'json');
select pg_input_is_valid('{"a":true', 'json');
-select pg_input_error_message('{"a":true', 'json');
+select pg_input_error_info('{"a":true', 'json');
--constructors
-- array_to_json
diff --git a/src/test/regress/sql/json_encoding.sql b/src/test/regress/sql/json_encoding.sql
index f87b3bd4f3..e7d11fd25b 100644
--- a/src/test/regress/sql/json_encoding.sql
+++ b/src/test/regress/sql/json_encoding.sql
@@ -79,4 +79,4 @@ SELECT jsonb '{ "a": "null \\u0000 escape" }' ->> 'a' as not_an_escape;
-- soft error for input-time failure
-select pg_input_error_message('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
+select pg_input_error_info('{ "a": "\ud83d\ude04\ud83d\udc36" }', 'jsonb');
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 9f67f4c71d..651e9408ec 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -89,8 +89,8 @@ SELECT '{
-- test non-error-throwing input
select pg_input_is_valid('{"a":true}', 'jsonb');
select pg_input_is_valid('{"a":true', 'jsonb');
-select pg_input_error_message('{"a":true', 'jsonb');
-select pg_input_error_message('{"a":1e1000000}', 'jsonb');
+select pg_input_error_info('{"a":true', 'jsonb');
+select pg_input_error_info('{"a":1e1000000}', 'jsonb');
-- make sure jsonb is passed through json generators without being escaped
SELECT array_to_json(ARRAY [jsonb '{"a":1}', jsonb '{"b":[2,3]}']);
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 99d21d2af7..2fba9c1057 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -192,7 +192,7 @@ select '1?(2>3)'::jsonpath;
SELECT str as jsonpath,
pg_input_is_valid(str,'jsonpath') as ok,
- pg_input_error_message(str,'jsonpath') as errmsg
+ pg_input_error_info(str,'jsonpath') as errmsg
FROM unnest(ARRAY['$ ? (@ like_regex "pattern" flag "smixq")'::text,
'$ ? (@ like_regex "pattern" flag "a")',
'@ + 1',
diff --git a/src/test/regress/sql/line.sql b/src/test/regress/sql/line.sql
index f706a41184..42b37138e9 100644
--- a/src/test/regress/sql/line.sql
+++ b/src/test/regress/sql/line.sql
@@ -43,12 +43,12 @@ select '{nan, 1, nan}'::line = '{nan, 1, nan}'::line as true,
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('{1, 1}', 'line');
-SELECT pg_input_error_message('{1, 1}', 'line');
+SELECT pg_input_error_info('{1, 1}', 'line');
SELECT pg_input_is_valid('{0, 0, 0}', 'line');
-SELECT pg_input_error_message('{0, 0, 0}', 'line');
+SELECT pg_input_error_info('{0, 0, 0}', 'line');
SELECT pg_input_is_valid('{1, 1, a}', 'line');
-SELECT pg_input_error_message('{1, 1, a}', 'line');
+SELECT pg_input_error_info('{1, 1, a}', 'line');
SELECT pg_input_is_valid('{1, 1, 1e400}', 'line');
-SELECT pg_input_error_message('{1, 1, 1e400}', 'line');
+SELECT pg_input_error_info('{1, 1, 1e400}', 'line');
SELECT pg_input_is_valid('(1, 1), (1, 1e400)', 'line');
-SELECT pg_input_error_message('(1, 1), (1, 1e400)', 'line');
+SELECT pg_input_error_info('(1, 1), (1, 1e400)', 'line');
diff --git a/src/test/regress/sql/lseg.sql b/src/test/regress/sql/lseg.sql
index 0fece162e0..095a798017 100644
--- a/src/test/regress/sql/lseg.sql
+++ b/src/test/regress/sql/lseg.sql
@@ -25,4 +25,4 @@ select * from LSEG_TBL;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('[(1,2),(3)]', 'lseg');
-SELECT pg_input_error_message('[(1,2),(3)]', 'lseg');
+SELECT pg_input_error_info('[(1,2),(3)]', 'lseg');
diff --git a/src/test/regress/sql/macaddr.sql b/src/test/regress/sql/macaddr.sql
index 211397c8f3..56a24cd88f 100644
--- a/src/test/regress/sql/macaddr.sql
+++ b/src/test/regress/sql/macaddr.sql
@@ -44,6 +44,6 @@ DROP TABLE macaddr_data;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('08:00:2b:01:02:ZZ', 'macaddr');
-SELECT pg_input_error_message('08:00:2b:01:02:ZZ', 'macaddr');
+SELECT pg_input_error_info('08:00:2b:01:02:ZZ', 'macaddr');
SELECT pg_input_is_valid('08:00:2b:01:02:', 'macaddr');
-SELECT pg_input_error_message('08:00:2b:01:02:', 'macaddr');
+SELECT pg_input_error_info('08:00:2b:01:02:', 'macaddr');
diff --git a/src/test/regress/sql/macaddr8.sql b/src/test/regress/sql/macaddr8.sql
index b29f785b41..a796034cc5 100644
--- a/src/test/regress/sql/macaddr8.sql
+++ b/src/test/regress/sql/macaddr8.sql
@@ -90,6 +90,6 @@ DROP TABLE macaddr8_data;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
-SELECT pg_input_error_message('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
+SELECT pg_input_error_info('08:00:2b:01:02:03:04:ZZ', 'macaddr8');
SELECT pg_input_is_valid('08:00:2b:01:02:03:04:', 'macaddr8');
-SELECT pg_input_error_message('08:00:2b:01:02:03:04:', 'macaddr8');
+SELECT pg_input_error_info('08:00:2b:01:02:03:04:', 'macaddr8');
diff --git a/src/test/regress/sql/money.sql b/src/test/regress/sql/money.sql
index cd9a089e01..073aa0a38b 100644
--- a/src/test/regress/sql/money.sql
+++ b/src/test/regress/sql/money.sql
@@ -90,9 +90,9 @@ SELECT '($123,456.78)'::money;
-- test non-error-throwing API
SELECT pg_input_is_valid('\x0001', 'money');
-SELECT pg_input_error_message('\x0001', 'money');
+SELECT pg_input_error_info('\x0001', 'money');
SELECT pg_input_is_valid('192233720368547758.07', 'money');
-SELECT pg_input_error_message('192233720368547758.07', 'money');
+SELECT pg_input_error_info('192233720368547758.07', 'money');
-- documented minimums and maximums
SELECT '-92233720368547758.08'::money;
diff --git a/src/test/regress/sql/multirangetypes.sql b/src/test/regress/sql/multirangetypes.sql
index fc369a550c..724af7f8d3 100644
--- a/src/test/regress/sql/multirangetypes.sql
+++ b/src/test/regress/sql/multirangetypes.sql
@@ -61,9 +61,9 @@ select '{(a,a)}'::textmultirange;
-- Also try it with non-error-throwing API
select pg_input_is_valid('{[1,2], [4,5]}', 'int4multirange');
select pg_input_is_valid('{[1,2], [4,5]', 'int4multirange');
-select pg_input_error_message('{[1,2], [4,5]', 'int4multirange');
+select pg_input_error_info('{[1,2], [4,5]', 'int4multirange');
select pg_input_is_valid('{[1,2], [4,zed]}', 'int4multirange');
-select pg_input_error_message('{[1,2], [4,zed]}', 'int4multirange');
+select pg_input_error_info('{[1,2], [4,zed]}', 'int4multirange');
--
-- test the constructor
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index 2db7656e84..4abc47a363 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -1086,11 +1086,11 @@ SELECT * FROM num_input_test;
SELECT pg_input_is_valid('34.5', 'numeric');
SELECT pg_input_is_valid('34xyz', 'numeric');
SELECT pg_input_is_valid('1e400000', 'numeric');
-SELECT pg_input_error_message('1e400000', 'numeric');
+SELECT pg_input_error_info('1e400000', 'numeric');
SELECT pg_input_is_valid('1234.567', 'numeric(8,4)');
SELECT pg_input_is_valid('1234.567', 'numeric(7,4)');
-SELECT pg_input_error_message('1234.567', 'numeric(7,4)');
-SELECT pg_input_error_message('0x1234.567', 'numeric');
+SELECT pg_input_error_info('1234.567', 'numeric(7,4)');
+SELECT pg_input_error_info('0x1234.567', 'numeric');
--
-- Test precision and scale typemods
diff --git a/src/test/regress/sql/oid.sql b/src/test/regress/sql/oid.sql
index 39937c2f1d..e2bc32a8b6 100644
--- a/src/test/regress/sql/oid.sql
+++ b/src/test/regress/sql/oid.sql
@@ -31,16 +31,16 @@ SELECT * FROM OID_TBL;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('1234', 'oid');
SELECT pg_input_is_valid('01XYZ', 'oid');
-SELECT pg_input_error_message('01XYZ', 'oid');
+SELECT pg_input_error_info('01XYZ', 'oid');
SELECT pg_input_is_valid('9999999999', 'oid');
-SELECT pg_input_error_message('9999999999', 'oid');
+SELECT pg_input_error_info('9999999999', 'oid');
-- While we're here, check oidvector as well
SELECT pg_input_is_valid(' 1 2 4 ', 'oidvector');
SELECT pg_input_is_valid('01 01XYZ', 'oidvector');
-SELECT pg_input_error_message('01 01XYZ', 'oidvector');
+SELECT pg_input_error_info('01 01XYZ', 'oidvector');
SELECT pg_input_is_valid('01 9999999999', 'oidvector');
-SELECT pg_input_error_message('01 9999999999', 'oidvector');
+SELECT pg_input_error_info('01 9999999999', 'oidvector');
SELECT o.* FROM OID_TBL o WHERE o.f1 = 1234;
diff --git a/src/test/regress/sql/path.sql b/src/test/regress/sql/path.sql
index 42c90afe53..3dad134bbb 100644
--- a/src/test/regress/sql/path.sql
+++ b/src/test/regress/sql/path.sql
@@ -45,6 +45,6 @@ SELECT popen(f1) AS open_path FROM PATH_TBL;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('[(1,2),(3)]', 'path');
-SELECT pg_input_error_message('[(1,2),(3)]', 'path');
+SELECT pg_input_error_info('[(1,2),(3)]', 'path');
SELECT pg_input_is_valid('[(1,2,6),(3,4,6)]', 'path');
-SELECT pg_input_error_message('[(1,2,6),(3,4,6)]', 'path');
+SELECT pg_input_error_info('[(1,2,6),(3,4,6)]', 'path');
diff --git a/src/test/regress/sql/pg_lsn.sql b/src/test/regress/sql/pg_lsn.sql
index 3d57d66e0c..7ba54b9c1b 100644
--- a/src/test/regress/sql/pg_lsn.sql
+++ b/src/test/regress/sql/pg_lsn.sql
@@ -17,7 +17,7 @@ INSERT INTO PG_LSN_TBL VALUES ('/ABCD');
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('16AE7F7', 'pg_lsn');
-SELECT pg_input_error_message('16AE7F7', 'pg_lsn');
+SELECT pg_input_error_info('16AE7F7', 'pg_lsn');
-- Min/Max aggregation
SELECT MIN(f1), MAX(f1) FROM PG_LSN_TBL;
diff --git a/src/test/regress/sql/point.sql b/src/test/regress/sql/point.sql
index 7bd1ebe2b5..49c2e85de5 100644
--- a/src/test/regress/sql/point.sql
+++ b/src/test/regress/sql/point.sql
@@ -99,4 +99,4 @@ RESET enable_bitmapscan;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('1,y', 'point');
-SELECT pg_input_error_message('1,y', 'point');
+SELECT pg_input_error_info('1,y', 'point');
diff --git a/src/test/regress/sql/polygon.sql b/src/test/regress/sql/polygon.sql
index da644e34e3..2ca1a5ac2b 100644
--- a/src/test/regress/sql/polygon.sql
+++ b/src/test/regress/sql/polygon.sql
@@ -143,6 +143,6 @@ RESET enable_bitmapscan;
-- test non-error-throwing API for some core types
SELECT pg_input_is_valid('(2.0,0.8,0.1)', 'polygon');
-SELECT pg_input_error_message('(2.0,0.8,0.1)', 'polygon');
+SELECT pg_input_error_info('(2.0,0.8,0.1)', 'polygon');
SELECT pg_input_is_valid('(2.0,xyz)', 'polygon');
-SELECT pg_input_error_message('(2.0,xyz)', 'polygon');
+SELECT pg_input_error_info('(2.0,xyz)', 'polygon');
diff --git a/src/test/regress/sql/privileges.sql b/src/test/regress/sql/privileges.sql
index 0bcea77ff4..5db53b97ca 100644
--- a/src/test/regress/sql/privileges.sql
+++ b/src/test/regress/sql/privileges.sql
@@ -1433,11 +1433,11 @@ SELECT makeaclitem('regress_priv_user1'::regrole, 'regress_priv_user2'::regrole,
-- Test non-throwing aclitem I/O
SELECT pg_input_is_valid('regress_priv_user1=r/regress_priv_user2', 'aclitem');
SELECT pg_input_is_valid('regress_priv_user1=r/', 'aclitem');
-SELECT pg_input_error_message('regress_priv_user1=r/', 'aclitem');
+SELECT pg_input_error_info('regress_priv_user1=r/', 'aclitem');
SELECT pg_input_is_valid('regress_priv_user1=r/regress_no_such_user', 'aclitem');
-SELECT pg_input_error_message('regress_priv_user1=r/regress_no_such_user', 'aclitem');
+SELECT pg_input_error_info('regress_priv_user1=r/regress_no_such_user', 'aclitem');
SELECT pg_input_is_valid('regress_priv_user1=rY', 'aclitem');
-SELECT pg_input_error_message('regress_priv_user1=rY', 'aclitem');
+SELECT pg_input_error_info('regress_priv_user1=rY', 'aclitem');
--
-- Testing blanket default grants is very hazardous since it might change
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4e6a0912de..4729a1c2d4 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -43,15 +43,15 @@ select '(a,a)'::textrange;
-- Also try it with non-error-throwing API
select pg_input_is_valid('(1,4)', 'int4range');
select pg_input_is_valid('(1,4', 'int4range');
-select pg_input_error_message('(1,4', 'int4range');
+select pg_input_error_info('(1,4', 'int4range');
select pg_input_is_valid('(4,1)', 'int4range');
-select pg_input_error_message('(4,1)', 'int4range');
+select pg_input_error_info('(4,1)', 'int4range');
select pg_input_is_valid('(4,zed)', 'int4range');
-select pg_input_error_message('(4,zed)', 'int4range');
+select pg_input_error_info('(4,zed)', 'int4range');
select pg_input_is_valid('[1,2147483647]', 'int4range');
-select pg_input_error_message('[1,2147483647]', 'int4range');
+select pg_input_error_info('[1,2147483647]', 'int4range');
select pg_input_is_valid('[2000-01-01,5874897-12-31]', 'daterange');
-select pg_input_error_message('[2000-01-01,5874897-12-31]', 'daterange');
+select pg_input_error_info('[2000-01-01,5874897-12-31]', 'daterange');
--
-- create some test data and test the operators
diff --git a/src/test/regress/sql/regproc.sql b/src/test/regress/sql/regproc.sql
index 2cb8c9a253..e8c7394c5c 100644
--- a/src/test/regress/sql/regproc.sql
+++ b/src/test/regress/sql/regproc.sql
@@ -125,23 +125,23 @@ SELECT to_regnamespace('foo.bar');
-- Test soft-error API
-SELECT pg_input_error_message('ng_catalog.pg_class', 'regclass');
+SELECT pg_input_error_info('ng_catalog.pg_class', 'regclass');
SELECT pg_input_is_valid('ng_catalog."POSIX"', 'regcollation');
-SELECT pg_input_error_message('no_such_config', 'regconfig');
-SELECT pg_input_error_message('no_such_dictionary', 'regdictionary');
-SELECT pg_input_error_message('Nonexistent', 'regnamespace');
-SELECT pg_input_error_message('ng_catalog.||/', 'regoper');
-SELECT pg_input_error_message('-', 'regoper');
-SELECT pg_input_error_message('ng_catalog.+(int4,int4)', 'regoperator');
-SELECT pg_input_error_message('-', 'regoperator');
-SELECT pg_input_error_message('ng_catalog.now', 'regproc');
-SELECT pg_input_error_message('ng_catalog.abs(numeric)', 'regprocedure');
-SELECT pg_input_error_message('ng_catalog.abs(numeric', 'regprocedure');
-SELECT pg_input_error_message('regress_regrole_test', 'regrole');
-SELECT pg_input_error_message('no_such_type', 'regtype');
+SELECT pg_input_error_info('no_such_config', 'regconfig');
+SELECT pg_input_error_info('no_such_dictionary', 'regdictionary');
+SELECT pg_input_error_info('Nonexistent', 'regnamespace');
+SELECT pg_input_error_info('ng_catalog.||/', 'regoper');
+SELECT pg_input_error_info('-', 'regoper');
+SELECT pg_input_error_info('ng_catalog.+(int4,int4)', 'regoperator');
+SELECT pg_input_error_info('-', 'regoperator');
+SELECT pg_input_error_info('ng_catalog.now', 'regproc');
+SELECT pg_input_error_info('ng_catalog.abs(numeric)', 'regprocedure');
+SELECT pg_input_error_info('ng_catalog.abs(numeric', 'regprocedure');
+SELECT pg_input_error_info('regress_regrole_test', 'regrole');
+SELECT pg_input_error_info('no_such_type', 'regtype');
-- Some cases that should be soft errors, but are not yet
-SELECT pg_input_error_message('incorrect type name syntax', 'regtype');
-SELECT pg_input_error_message('numeric(1,2,3)', 'regtype'); -- bogus typmod
-SELECT pg_input_error_message('way.too.many.names', 'regtype');
-SELECT pg_input_error_message('no_such_catalog.schema.name', 'regtype');
+SELECT pg_input_error_info('incorrect type name syntax', 'regtype');
+SELECT pg_input_error_info('numeric(1,2,3)', 'regtype'); -- bogus typmod
+SELECT pg_input_error_info('way.too.many.names', 'regtype');
+SELECT pg_input_error_info('no_such_catalog.schema.name', 'regtype');
diff --git a/src/test/regress/sql/rowtypes.sql b/src/test/regress/sql/rowtypes.sql
index 0844e7488d..951f0e60e2 100644
--- a/src/test/regress/sql/rowtypes.sql
+++ b/src/test/regress/sql/rowtypes.sql
@@ -35,8 +35,8 @@ select '(Joe,Blow) /'::fullname; -- bad
SELECT pg_input_is_valid('(1,2)', 'complex');
SELECT pg_input_is_valid('(1,2', 'complex');
SELECT pg_input_is_valid('(1,zed)', 'complex');
-SELECT pg_input_error_message('(1,zed)', 'complex');
-SELECT pg_input_error_message('(1,1e400)', 'complex');
+SELECT pg_input_error_info('(1,zed)', 'complex');
+SELECT pg_input_error_info('(1,1e400)', 'complex');
create temp table quadtable(f1 int, q quad);
diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql
index 932f71cbca..7d774315c6 100644
--- a/src/test/regress/sql/strings.sql
+++ b/src/test/regress/sql/strings.sql
@@ -87,9 +87,9 @@ SELECT E'De\\123dBeEf'::bytea;
-- Test non-error-throwing API too
SELECT pg_input_is_valid(E'\\xDeAdBeE', 'bytea');
-SELECT pg_input_error_message(E'\\xDeAdBeE', 'bytea');
-SELECT pg_input_error_message(E'\\xDeAdBeEx', 'bytea');
-SELECT pg_input_error_message(E'foo\\99bar', 'bytea');
+SELECT pg_input_error_info(E'\\xDeAdBeE', 'bytea');
+SELECT pg_input_error_info(E'\\xDeAdBeEx', 'bytea');
+SELECT pg_input_error_info(E'foo\\99bar', 'bytea');
--
-- test conversions between various string types
diff --git a/src/test/regress/sql/tid.sql b/src/test/regress/sql/tid.sql
index 8196194c04..2d2dcb05c1 100644
--- a/src/test/regress/sql/tid.sql
+++ b/src/test/regress/sql/tid.sql
@@ -11,9 +11,9 @@ SELECT '(1,65536)'::tid; -- error
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('(0)', 'tid');
-SELECT pg_input_error_message('(0)', 'tid');
+SELECT pg_input_error_info('(0)', 'tid');
SELECT pg_input_is_valid('(0,-1)', 'tid');
-SELECT pg_input_error_message('(0,-1)', 'tid');
+SELECT pg_input_error_info('(0,-1)', 'tid');
-- tests for functions related to TID handling
diff --git a/src/test/regress/sql/time.sql b/src/test/regress/sql/time.sql
index b439cd6b41..a40e725eab 100644
--- a/src/test/regress/sql/time.sql
+++ b/src/test/regress/sql/time.sql
@@ -44,8 +44,8 @@ SELECT '25:00:00'::time; -- not allowed
SELECT pg_input_is_valid('12:00:00', 'time');
SELECT pg_input_is_valid('25:00:00', 'time');
SELECT pg_input_is_valid('15:36:39 America/New_York', 'time');
-SELECT pg_input_error_message('25:00:00', 'time');
-SELECT pg_input_error_message('15:36:39 America/New_York', 'time');
+SELECT pg_input_error_info('25:00:00', 'time');
+SELECT pg_input_error_info('15:36:39 America/New_York', 'time');
--
-- TIME simple math
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 2d5f01ab86..ca4c44130a 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -100,8 +100,8 @@ INSERT INTO TIMESTAMP_TBL VALUES ('19970710 173201 America/Does_not_exist');
SELECT pg_input_is_valid('now', 'timestamp');
SELECT pg_input_is_valid('garbage', 'timestamp');
SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
-SELECT pg_input_error_message('garbage', 'timestamp');
-SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
+SELECT pg_input_error_info('garbage', 'timestamp');
+SELECT pg_input_error_info('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamp');
-- Check date conversion and date arithmetic
INSERT INTO TIMESTAMP_TBL VALUES ('1997-06-10 18:32:01 PDT');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 6d10937d86..96cd82fa08 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -113,8 +113,8 @@ SELECT '205000-01-10 17:32:01 Europe/Helsinki'::timestamptz; -- non-DST
SELECT pg_input_is_valid('now', 'timestamptz');
SELECT pg_input_is_valid('garbage', 'timestamptz');
SELECT pg_input_is_valid('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
-SELECT pg_input_error_message('garbage', 'timestamptz');
-SELECT pg_input_error_message('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
+SELECT pg_input_error_info('garbage', 'timestamptz');
+SELECT pg_input_error_info('2001-01-01 00:00 Nehwon/Lankhmar', 'timestamptz');
-- Check date conversion and date arithmetic
INSERT INTO TIMESTAMPTZ_TBL VALUES ('1997-06-10 18:32:01 PDT');
diff --git a/src/test/regress/sql/timetz.sql b/src/test/regress/sql/timetz.sql
index b62aa3fe05..857408b88b 100644
--- a/src/test/regress/sql/timetz.sql
+++ b/src/test/regress/sql/timetz.sql
@@ -49,8 +49,8 @@ SELECT '25:00:00 PDT'::timetz; -- not allowed
SELECT pg_input_is_valid('12:00:00 PDT', 'timetz');
SELECT pg_input_is_valid('25:00:00 PDT', 'timetz');
SELECT pg_input_is_valid('15:36:39 America/New_York', 'timetz');
-SELECT pg_input_error_message('25:00:00 PDT', 'timetz');
-SELECT pg_input_error_message('15:36:39 America/New_York', 'timetz');
+SELECT pg_input_error_info('25:00:00 PDT', 'timetz');
+SELECT pg_input_error_info('15:36:39 America/New_York', 'timetz');
--
-- TIME simple math
diff --git a/src/test/regress/sql/tstypes.sql b/src/test/regress/sql/tstypes.sql
index b73dd1cb07..9343266fd5 100644
--- a/src/test/regress/sql/tstypes.sql
+++ b/src/test/regress/sql/tstypes.sql
@@ -22,7 +22,7 @@ SELECT $$'' '1' '2'$$::tsvector; -- error, empty lexeme is not allowed
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('foo', 'tsvector');
SELECT pg_input_is_valid($$''$$, 'tsvector');
-SELECT pg_input_error_message($$''$$, 'tsvector');
+SELECT pg_input_error_info($$''$$, 'tsvector');
--Base tsquery test
SELECT '1'::tsquery;
@@ -76,8 +76,8 @@ SELECT '!!a & !!b'::tsquery;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('foo', 'tsquery');
SELECT pg_input_is_valid('foo!', 'tsquery');
-SELECT pg_input_error_message('foo!', 'tsquery');
-SELECT pg_input_error_message('a <100000> b', 'tsquery');
+SELECT pg_input_error_info('foo!', 'tsquery');
+SELECT pg_input_error_info('a <100000> b', 'tsquery');
--comparisons
SELECT 'a' < 'b & c'::tsquery as "true";
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 37d954eda1..076cf1b21e 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -25,7 +25,7 @@ INSERT INTO guid1(guid_field) VALUES('11+11111-1111-1111-1111-111111111111');
-- test non-error-throwing API
SELECT pg_input_is_valid('11', 'uuid');
-SELECT pg_input_error_message('11', 'uuid');
+SELECT pg_input_error_info('11', 'uuid');
--inserting three input formats
INSERT INTO guid1(guid_field) VALUES('11111111-1111-1111-1111-111111111111');
diff --git a/src/test/regress/sql/varchar.sql b/src/test/regress/sql/varchar.sql
index df16da37a7..32dbee58b1 100644
--- a/src/test/regress/sql/varchar.sql
+++ b/src/test/regress/sql/varchar.sql
@@ -70,4 +70,4 @@ SELECT * FROM VARCHAR_TBL;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('abcd ', 'varchar(4)');
SELECT pg_input_is_valid('abcde', 'varchar(4)');
-SELECT pg_input_error_message('abcde', 'varchar(4)');
+SELECT pg_input_error_info('abcde', 'varchar(4)');
diff --git a/src/test/regress/sql/xid.sql b/src/test/regress/sql/xid.sql
index b6996588ef..3e5e880473 100644
--- a/src/test/regress/sql/xid.sql
+++ b/src/test/regress/sql/xid.sql
@@ -19,10 +19,10 @@ select 'asdf'::xid8;
-- Also try it with non-error-throwing API
SELECT pg_input_is_valid('42', 'xid');
SELECT pg_input_is_valid('asdf', 'xid');
-SELECT pg_input_error_message('0xffffffffff', 'xid');
+SELECT pg_input_error_info('0xffffffffff', 'xid');
SELECT pg_input_is_valid('42', 'xid8');
SELECT pg_input_is_valid('asdf', 'xid8');
-SELECT pg_input_error_message('0xffffffffffffffffffff', 'xid8');
+SELECT pg_input_error_info('0xffffffffffffffffffff', 'xid8');
-- equality
select '1'::xid = '1'::xid;
@@ -79,9 +79,9 @@ select '12:16:14,13'::pg_snapshot;
-- also try it with non-error-throwing API
select pg_input_is_valid('12:13:', 'pg_snapshot');
select pg_input_is_valid('31:12:', 'pg_snapshot');
-select pg_input_error_message('31:12:', 'pg_snapshot');
+select pg_input_error_info('31:12:', 'pg_snapshot');
select pg_input_is_valid('12:16:14,13', 'pg_snapshot');
-select pg_input_error_message('12:16:14,13', 'pg_snapshot');
+select pg_input_error_info('12:16:14,13', 'pg_snapshot');
create temp table snapshot_test (
nr integer,
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index ddff459297..24e40d2653 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -12,9 +12,9 @@ SELECT * FROM xmltest;
-- test non-throwing API, too
SELECT pg_input_is_valid('<value>one</value>', 'xml');
SELECT pg_input_is_valid('<value>one</', 'xml');
-SELECT pg_input_error_message('<value>one</', 'xml');
+SELECT message FROM pg_input_error_info('<value>one</', 'xml');
SELECT pg_input_is_valid('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
-SELECT pg_input_error_message('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
+SELECT message FROM pg_input_error_info('<?xml version="1.0" standalone="y"?><foo/>', 'xml');
SELECT xmlcomment('test');
--
2.25.1
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: verbose mode for pg_input_error_message?
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
2023-02-25 04:39 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
2023-02-25 23:29 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-26 01:07 ` Re: verbose mode for pg_input_error_message? Tom Lane <[email protected]>
2023-02-26 04:58 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
@ 2023-02-26 05:35 ` Michael Paquier <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Michael Paquier @ 2023-02-26 05:35 UTC (permalink / raw)
To: Nathan Bossart <[email protected]>; +Cc: Tom Lane <[email protected]>; Corey Huinker <[email protected]>; Andrew Dunstan <[email protected]>; PostgreSQL Hackers <[email protected]>
On Sat, Feb 25, 2023 at 08:58:17PM -0800, Nathan Bossart wrote:
> pg_input_error_info() seems more descriptive to me. I changed the name to
> that in v4.
error_info() is fine by me. My recent history is poor lately when it
comes to name new things.
+ values[0] = CStringGetTextDatum(escontext.error_data->message);
+
+ if (escontext.error_data->detail != NULL)
+ values[1] = CStringGetTextDatum(escontext.error_data->detail);
+ else
+ isnull[1] = true;
+
+ if (escontext.error_data->hint != NULL)
+ values[2] = CStringGetTextDatum(escontext.error_data->hint);
+ else
+ isnull[2] = true;
+
+ values[3] = CStringGetTextDatum(
+ unpack_sql_state(escontext.error_data->sqlerrcode));
I am OK with this data set as well. If somebody makes a case about
more fields in ErrorData, we could always consider these separately.
FWIW, I would like to change some of the regression tests as we are
bikeshedding the whole.
+SELECT pg_input_error_info(repeat('too_long', 32), 'rainbow');
For example, we could use the expanded display for this case in
enum.sql.
-- test non-error-throwing API
SELECT str as jsonpath,
pg_input_is_valid(str,'jsonpath') as ok,
- pg_input_error_message(str,'jsonpath') as errmsg
+ pg_input_error_info(str,'jsonpath') as errmsg
This case in jsonpath.sql is actually wrong, because we have more than
just the error message.
For the others, I would make the choice of expanding the calls of
pg_input_error_info() rather than just showing row outputs, though I
agree that this part is minor.
--
Michael
Attachments:
[application/pgp-signature] signature.asc (833B, ../../Y%2FrvmmK%[email protected]/2-signature.asc)
download
^ permalink raw reply [nested|flat] 18+ messages in thread
* [PATCH v6 04/17] hio: Don't pin the VM while holding buffer lock while extending
@ 2023-03-29 01:17 Andres Freund <[email protected]>
0 siblings, 0 replies; 18+ messages in thread
From: Andres Freund @ 2023-03-29 01:17 UTC (permalink / raw)
Discussion: http://postgr.es/m/[email protected]
---
src/backend/access/heap/hio.c | 120 +++++++++++++++++++++++-----------
1 file changed, 81 insertions(+), 39 deletions(-)
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 7479212d4e0..8b3dfa0ccae 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -134,14 +134,17 @@ ReadBufferBI(Relation relation, BlockNumber targetBlock,
* buffer2 may be InvalidBuffer, if only one buffer is involved. buffer1
* must not be InvalidBuffer. If both buffers are specified, block1 must
* be less than block2.
+ *
+ * Returns whether buffer locks were temporarily released.
*/
-static void
+static bool
GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
BlockNumber block1, BlockNumber block2,
Buffer *vmbuffer1, Buffer *vmbuffer2)
{
bool need_to_pin_buffer1;
bool need_to_pin_buffer2;
+ bool released_locks = false;
Assert(BufferIsValid(buffer1));
Assert(buffer2 == InvalidBuffer || block1 <= block2);
@@ -155,9 +158,10 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
&& PageIsAllVisible(BufferGetPage(buffer2))
&& !visibilitymap_pin_ok(block2, *vmbuffer2);
if (!need_to_pin_buffer1 && !need_to_pin_buffer2)
- return;
+ break;
/* We must unlock both buffers before doing any I/O. */
+ released_locks = true;
LockBuffer(buffer1, BUFFER_LOCK_UNLOCK);
if (buffer2 != InvalidBuffer && buffer2 != buffer1)
LockBuffer(buffer2, BUFFER_LOCK_UNLOCK);
@@ -183,6 +187,8 @@ GetVisibilityMapPins(Relation relation, Buffer buffer1, Buffer buffer2,
|| (need_to_pin_buffer1 && need_to_pin_buffer2))
break;
}
+
+ return released_locks;
}
/*
@@ -345,6 +351,7 @@ RelationGetBufferForTuple(Relation relation, Size len,
BlockNumber targetBlock,
otherBlock;
bool needLock;
+ bool unlockedTargetBuffer;
len = MAXALIGN(len); /* be conservative */
@@ -630,6 +637,9 @@ loop:
if (needLock)
UnlockRelationForExtension(relation, ExclusiveLock);
+ unlockedTargetBuffer = false;
+ targetBlock = BufferGetBlockNumber(buffer);
+
/*
* We need to initialize the empty new page. Double-check that it really
* is empty (this should never happen, but if it does we don't want to
@@ -639,75 +649,107 @@ loop:
if (!PageIsNew(page))
elog(ERROR, "page %u of relation \"%s\" should be empty but is not",
- BufferGetBlockNumber(buffer),
+ targetBlock,
RelationGetRelationName(relation));
PageInit(page, BufferGetPageSize(buffer), 0);
MarkBufferDirty(buffer);
/*
- * The page is empty, pin vmbuffer to set all_frozen bit.
+ * The page is empty, pin vmbuffer to set all_frozen bit. We don't want to
+ * do IO while the buffer is locked, so we unlock the page first if IO is
+ * needed (necessitating checks below).
*/
if (options & HEAP_INSERT_FROZEN)
{
- Assert(PageGetMaxOffsetNumber(BufferGetPage(buffer)) == 0);
- visibilitymap_pin(relation, BufferGetBlockNumber(buffer), vmbuffer);
+ Assert(PageGetMaxOffsetNumber(page) == 0);
+
+ if (!visibilitymap_pin_ok(targetBlock, *vmbuffer))
+ {
+ unlockedTargetBuffer = true;
+ LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+ visibilitymap_pin(relation, targetBlock, vmbuffer);
+ }
}
/*
- * Lock the other buffer. It's guaranteed to be of a lower page number
- * than the new page. To conform with the deadlock prevent rules, we ought
- * to lock otherBuffer first, but that would give other backends a chance
- * to put tuples on our page. To reduce the likelihood of that, attempt to
- * lock the other buffer conditionally, that's very likely to work.
- * Otherwise we need to lock buffers in the correct order, and retry if
- * the space has been used in the mean time.
+ * If we unlocked the target buffer above, it's unlikely, but possible,
+ * that another backend used space on this page.
+ *
+ * If we didn't, and otherBuffer is valid, we need to lock the other
+ * buffer. It's guaranteed to be of a lower page number than the new page.
+ * To conform with the deadlock prevent rules, we ought to lock
+ * otherBuffer first, but that would give other backends a chance to put
+ * tuples on our page. To reduce the likelihood of that, attempt to lock
+ * the other buffer conditionally, that's very likely to work. Otherwise
+ * we need to lock buffers in the correct order, and retry if the space
+ * has been used in the mean time.
*
* Alternatively, we could acquire the lock on otherBuffer before
* extending the relation, but that'd require holding the lock while
* performing IO, which seems worse than an unlikely retry.
*/
- if (otherBuffer != InvalidBuffer)
+ if (unlockedTargetBuffer)
+ {
+ if (otherBuffer != InvalidBuffer)
+ LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
+ LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
+ }
+ else if (otherBuffer != InvalidBuffer)
{
Assert(otherBuffer != buffer);
- targetBlock = BufferGetBlockNumber(buffer);
Assert(targetBlock > otherBlock);
if (unlikely(!ConditionalLockBuffer(otherBuffer)))
{
+ unlockedTargetBuffer = true;
LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
LockBuffer(otherBuffer, BUFFER_LOCK_EXCLUSIVE);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
}
+ }
- /*
- * Because the buffers were unlocked for a while, it's possible,
- * although unlikely, that an all-visible flag became set or that
- * somebody used up the available space in the new page. We can use
- * GetVisibilityMapPins to deal with the first case. In the second
- * case, just retry from start.
- */
- GetVisibilityMapPins(relation, otherBuffer, buffer,
- otherBlock, targetBlock, vmbuffer_other,
- vmbuffer);
-
- /*
- * Note that we have to check the available space even if our
- * conditional lock succeeded, because GetVisibilityMapPins might've
- * transiently released lock on the target buffer to acquire a VM pin
- * for the otherBuffer.
- */
- if (len > PageGetHeapFreeSpace(page))
+ /*
+ * If one of the buffers was unlocked, it's possible, although unlikely,
+ * that an all-visible flag became set. We can use GetVisibilityMapPins
+ * to deal with that.
+ */
+ if (unlockedTargetBuffer || otherBuffer != InvalidBuffer)
+ {
+ if (otherBuffer != InvalidBuffer)
{
- LockBuffer(otherBuffer, BUFFER_LOCK_UNLOCK);
+ if (GetVisibilityMapPins(relation, otherBuffer, buffer,
+ otherBlock, targetBlock, vmbuffer_other,
+ vmbuffer))
+ unlockedTargetBuffer = true;
+ }
+ else
+ {
+ if (GetVisibilityMapPins(relation, buffer, InvalidBuffer,
+ targetBlock, InvalidBlockNumber,
+ vmbuffer, InvalidBuffer))
+ unlockedTargetBuffer = true;
+ }
+ }
+
+ /*
+ * If the target buffer was temporarily unlocked since the relation
+ * extension, it's possible, although unlikely, that all the space on the
+ * page was already used. If so, we just retry from the start. If we
+ * didn't unlock, something has gone wrong if there's not enough space -
+ * the test at the top should have prevented reaching this case.
+ */
+ pageFreeSpace = PageGetHeapFreeSpace(page);
+ if (len > pageFreeSpace)
+ {
+ if (unlockedTargetBuffer)
+ {
+ if (otherBuffer != InvalidBuffer)
+ LockBuffer(otherBuffer, BUFFER_LOCK_UNLOCK);
UnlockReleaseBuffer(buffer);
goto loop;
}
- }
- else if (len > PageGetHeapFreeSpace(page))
- {
- /* We should not get here given the test at the top */
elog(PANIC, "tuple is too big: size %zu", len);
}
@@ -720,7 +762,7 @@ loop:
* current backend to make more insertions or not, which is probably a
* good bet most of the time. So for now, don't add it to FSM yet.
*/
- RelationSetTargetBlock(relation, BufferGetBlockNumber(buffer));
+ RelationSetTargetBlock(relation, targetBlock);
return buffer;
}
--
2.38.0
--cfn72vqnlbycypta
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v6-0005-bufmgr-Add-some-error-checking-around-pinning.patch"
^ permalink raw reply [nested|flat] 18+ messages in thread
end of thread, other threads:[~2023-03-29 01:17 UTC | newest]
Thread overview: 18+ 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]>
2023-01-04 21:18 Re: verbose mode for pg_input_error_message? Andrew Dunstan <[email protected]>
2023-01-10 23:41 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 18:40 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 19:30 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-23 21:47 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-24 22:36 ` Re: verbose mode for pg_input_error_message? Corey Huinker <[email protected]>
2023-02-25 04:39 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
2023-02-25 23:29 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-26 01:07 ` Re: verbose mode for pg_input_error_message? Tom Lane <[email protected]>
2023-02-26 04:58 ` Re: verbose mode for pg_input_error_message? Nathan Bossart <[email protected]>
2023-02-26 05:35 ` Re: verbose mode for pg_input_error_message? Michael Paquier <[email protected]>
2023-03-29 01:17 [PATCH v6 04/17] hio: Don't pin the VM while holding buffer lock while extending Andres Freund <[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