]> granicus.if.org Git - postgresql/commitdiff
GSSAPI encryption support
authorStephen Frost <sfrost@snowman.net>
Wed, 3 Apr 2019 19:02:33 +0000 (15:02 -0400)
committerStephen Frost <sfrost@snowman.net>
Wed, 3 Apr 2019 19:02:33 +0000 (15:02 -0400)
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.

For HBA, add "hostgssenc" and "hostnogssenc" entries that behave
similarly to their SSL counterparts.  "hostgssenc" requires either
"gss", "trust", or "reject" for its authentication.

Similarly, add a "gssencmode" 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.

Add a simple pg_stat_gssapi view similar to pg_stat_ssl, for monitoring
if GSSAPI authentication was used, what principal was used, and if
encryption is being used on the connection.

Finally, add documentation for everything new, and update existing
documentation on connection security.

Thanks to Michael Paquier for the Windows fixes.

Author: Robbie Harwood, with changes to the read/write functions by me.
Reviewed in various forms and at different times by: Michael Paquier,
   Andres Freund, David Steele.
Discussion: https://www.postgresql.org/message-id/flat/jlg1tgq1ktm.fsf@thriss.redhat.com

35 files changed:
doc/src/sgml/client-auth.sgml
doc/src/sgml/libpq.sgml
doc/src/sgml/monitoring.sgml
doc/src/sgml/runtime.sgml
src/backend/catalog/system_views.sql
src/backend/libpq/Makefile
src/backend/libpq/auth.c
src/backend/libpq/be-gssapi-common.c [new file with mode: 0644]
src/backend/libpq/be-gssapi-common.h [new file with mode: 0644]
src/backend/libpq/be-secure-gssapi.c [new file with mode: 0644]
src/backend/libpq/be-secure.c
src/backend/libpq/hba.c
src/backend/postmaster/pgstat.c
src/backend/postmaster/postmaster.c
src/backend/utils/adt/pgstatfuncs.c
src/bin/psql/command.c
src/include/catalog/pg_proc.dat
src/include/libpq/hba.h
src/include/libpq/libpq-be.h
src/include/libpq/libpq.h
src/include/libpq/pqcomm.h
src/include/pgstat.h
src/interfaces/libpq/Makefile
src/interfaces/libpq/exports.txt
src/interfaces/libpq/fe-auth.c
src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-gssapi-common.c [new file with mode: 0644]
src/interfaces/libpq/fe-gssapi-common.h [new file with mode: 0644]
src/interfaces/libpq/fe-secure-gssapi.c [new file with mode: 0644]
src/interfaces/libpq/fe-secure.c
src/interfaces/libpq/libpq-fe.h
src/interfaces/libpq/libpq-int.h
src/test/kerberos/t/002_enc.pl [new file with mode: 0644]
src/test/regress/expected/rules.out
src/tools/msvc/Mkvcbuild.pm

index c7e4d3817f10d8c3a6f1d61203e54bb6ef231f18..45a3cf3defd0d91a91cb3d51854ede43623b3d72 100644 (file)
@@ -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>
+hostgssenc <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>  <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
+hostnogssenc <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> encrypted or
+       non-<acronym>GSSAPI</acronym> encrypted connection attempts.
       </para>
      <note>
       <para>
@@ -176,6 +179,42 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>hostgssenc</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>hostgssenc</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>hostnogssenc</literal></term>
+     <listitem>
+      <para>
+       This record type has the opposite behavior of <literal>hostgssenc</literal>;
+       it only matches connection attempts made over
+       TCP/IP that do not use <acronym>GSSAPI</acronym> encryption.
+      </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>
@@ -703,15 +743,18 @@ 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
-# address to be considered, so it matches any host.
+# matched first), but allow GSSAPI-encrypted connections from anywhere else
+# on the Internet.  The zero mask causes no bits of the host IP address to
+# be considered, so it matches any host.  Unencrypted GSSAPI connections 
+# (which "fall through" to the third line since "hostgssenc" only matches
+# encrypted GSSAPI connections) are allowed, but only from 192.168.12.10.  
 #
 # TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    all             all             192.168.54.1/32         reject
-host    all             all             0.0.0.0/0               gss
+hostgssenc 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
@@ -1058,13 +1101,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 for 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>hostgssenc</literal>) or <acronym>SSL</acronym> encryption are
+    used, the data sent along the database connection will be encrypted;
+    otherwise, it will not.
    </para>
 
    <para>
index c1d1b6b2db344d1e93f370e54afa1da193cc3b83..0863a02411da6f5efef279108d02a0a177548359 100644 (file)
@@ -1316,6 +1316,63 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-gssencmode" xreflabel="gssencmode">
+      <term><literal>gssencmode</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.  This is the
+            default when <productname>PostgreSQL</productname> has been
+            compiled with <acronym>GSSAPI</acronym> support.
+           </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>gssencmode</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>
@@ -7948,7 +8005,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
index 6679260508200d9b01073bc032e1d22e079e26ab..b946e13fdc377efe5343d1edc65a1fe0f2fa3eb8 100644 (file)
@@ -336,6 +336,14 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   11:34   0:00 postgres: ser
       </entry>
      </row>
 
+     <row>
+      <entry><structname>pg_stat_gssapi</structname><indexterm><primary>pg_stat_gssapi</primary></indexterm></entry>
+      <entry>One row per connection (regular and replication), showing information about
+       GSSAPI authentication and encryption used on this connection.
+       See <xref linkend="pg-stat-gssapi-view"/> for details.
+      </entry>
+     </row>
+
      <row>
       <entry><structname>pg_stat_progress_create_index</structname><indexterm><primary>pg_stat_progress_create_index</primary></indexterm></entry>
       <entry>One row for each backend running <command>CREATE INDEX</command>, showing
@@ -2281,6 +2289,55 @@ SELECT pid, wait_event_type, wait_event FROM pg_stat_activity WHERE wait_event i
    connection.
   </para>
 
+  <table id="pg-stat-gssapi-view" xreflabel="pg_stat_gssapi">
+   <title><structname>pg_stat_gssapi</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>pid</structfield></entry>
+     <entry><type>integer</type></entry>
+     <entry>Process ID of a backend</entry>
+    </row>
+    <row>
+     <entry><structfield>gss_authenticated</structfield></entry>
+     <entry><type>boolean</type></entry>
+     <entry>True if GSSAPI authentication was used for this connection</entry>
+    </row>
+    <row>
+     <entry><structfield>principal</structfield></entry>
+     <entry><type>text</type></entry>
+     <entry>Principal used to authenticate this connection, or NULL
+      if GSSAPI was not used to authenticate this connection.  This
+      field is truncated if the principal is longer than
+      <symbol>NAMEDATALEN</symbol> (64 characters in a standard build).
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>encrypted</structfield></entry>
+     <entry><type>boolean</type></entry>
+     <entry>True if GSSAPI encryption is in use on this connection</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   The <structname>pg_stat_gssapi</structname> view will contain one row per
+   backend, showing information about GSSAPI usage on this connection. It can
+   be joined to <structname>pg_stat_activity</structname> or
+   <structname>pg_stat_replication</structname> on the
+   <structfield>pid</structfield> column to get more details about the
+   connection.
+  </para>
+
 
   <table id="pg-stat-archiver-view" xreflabel="pg_stat_archiver">
    <title><structname>pg_stat_archiver</structname> View</title>
index d786ebfb71dbe034583a6e2eef5aea7c824f0fd6..fde9dbc13468dca455ac62da168ff78ee3215227 100644 (file)
@@ -2037,9 +2037,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
@@ -2047,6 +2051,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>hostgssenc</literal> connections
+    (<xref linkend="auth-pg-hba-conf"/>) and use <literal>gss</literal>
+    authentication with them.  The TCP client must connect
+    using <literal>gssencmode=require</literal>.
+  </para>
  </sect1>
 
  <sect1 id="encryption-options">
@@ -2143,8 +2155,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>hostgssenc</literal>).  Also, clients can specify that they
+      connect to servers only on GSSAPI-encrypted connections
+      (<literal>gssencmode=require</literal>).
+     </para>
+
+     <para>
+      <application>Stunnel</application> or
+      <application>SSH</application> can also be used to encrypt
+      transmissions.
      </para>
     </listitem>
   </varlistentry>
@@ -2561,6 +2589,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 is 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>
 
index 3f2a7ef015850ececf328d66bda2c50d29ec6a70..72f786d6f8ad06a0806201246e0a4c5a14c92682 100644 (file)
@@ -787,6 +787,14 @@ CREATE VIEW pg_stat_ssl AS
             S.ssl_issuer_dn AS issuer_dn
     FROM pg_stat_get_activity(NULL) AS S;
 
+CREATE VIEW pg_stat_gssapi AS
+    SELECT
+            S.pid,
+            S.gss_auth AS gss_authenticated,
+            S.gss_princ AS principal,
+            S.gss_enc AS encrypted
+    FROM pg_stat_get_activity(NULL) AS S;
+
 CREATE VIEW pg_replication_slots AS
     SELECT
             L.slot_name,
index 3dbec23e30a7db6f10fa9777f4172c1f5c823c36..47efef0682de5aef9aaa0ac35f74018b0020bb4e 100644 (file)
@@ -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
index 6f03c7c2a5ebe337978c0b115daa60d46a6a5528..62466be70235e42e289c5c6fa5fe13dd73439c12 100644 (file)
@@ -36,6 +36,7 @@
 #include "port/pg_bswap.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
+#include "utils/memutils.h"
 #include "utils/timestamp.h"
 
 
@@ -172,12 +173,9 @@ bool               pg_krb_caseins_users;
  *----------------------------------------------------------------
  */
 #ifdef ENABLE_GSS
-#if defined(HAVE_GSSAPI_H)
-#include <gssapi.h>
-#else
-#include <gssapi/gssapi.h>
-#endif
+#include "be-gssapi-common.h"
 
+static int     pg_GSS_checkauth(Port *port);
 static int     pg_GSS_recvauth(Port *port);
 #endif                                                 /* ENABLE_GSS */
 
@@ -383,6 +381,17 @@ ClientAuthentication(Port *port)
                                         errmsg("connection requires a valid client certificate")));
        }
 
+#ifdef ENABLE_GSS
+       if (port->gss->enc && port->hba->auth_method != uaReject &&
+               port->hba->auth_method != uaImplicitReject &&
+               port->hba->auth_method != uaTrust &&
+               port->hba->auth_method != uaGSS)
+       {
+               ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+                                               errmsg("GSSAPI encryption can only be used with gss, trust, or reject authentication methods")));
+       }
+#endif
+
        /*
         * Now proceed to do the actual authentication check
         */
@@ -523,8 +532,14 @@ ClientAuthentication(Port *port)
 
                case uaGSS:
 #ifdef ENABLE_GSS
-                       sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
-                       status = pg_GSS_recvauth(port);
+                       port->gss->auth = true;
+                       if (port->gss->enc)
+                               status = pg_GSS_checkauth(port);
+                       else
+                       {
+                               sendAuthRequest(port, AUTH_REQ_GSS, NULL, 0);
+                               status = pg_GSS_recvauth(port);
+                       }
 #else
                        Assert(false);
 #endif
@@ -1031,68 +1046,6 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail)
  *----------------------------------------------------------------
  */
 #ifdef ENABLE_GSS
-
-#if defined(WIN32) && !defined(_MSC_VER)
-/*
- * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
- * that contain the OIDs required. Redefine here, values copied
- * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
- */
-static const gss_OID_desc GSS_C_NT_USER_NAME_desc =
-{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"};
-static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc;
-#endif
-
-
-/*
- * Generate an error for GSSAPI authentication.  The caller should apply
- * _() to errmsg to make it translatable.
- */
-static void
-pg_GSS_error(int severity, const char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
-{
-       gss_buffer_desc gmsg;
-       OM_uint32       lmin_s,
-                               msg_ctx;
-       char            msg_major[128],
-                               msg_minor[128];
-
-       /* Fetch major status message */
-       msg_ctx = 0;
-       gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE,
-                                          GSS_C_NO_OID, &msg_ctx, &gmsg);
-       strlcpy(msg_major, gmsg.value, sizeof(msg_major));
-       gss_release_buffer(&lmin_s, &gmsg);
-
-       if (msg_ctx)
-
-               /*
-                * More than one message available. XXX: Should we loop and read all
-                * messages? (same below)
-                */
-               ereport(WARNING,
-                               (errmsg_internal("incomplete GSS error report")));
-
-       /* Fetch mechanism minor status message */
-       msg_ctx = 0;
-       gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE,
-                                          GSS_C_NO_OID, &msg_ctx, &gmsg);
-       strlcpy(msg_minor, gmsg.value, sizeof(msg_minor));
-       gss_release_buffer(&lmin_s, &gmsg);
-
-       if (msg_ctx)
-               ereport(WARNING,
-                               (errmsg_internal("incomplete GSS minor error report")));
-
-       /*
-        * errmsg_internal, since translation of the first part must be done
-        * before calling this function anyway.
-        */
-       ereport(severity,
-                       (errmsg_internal("%s", errmsg),
-                        errdetail_internal("%s: %s", msg_major, msg_minor)));
-}
-
 static int
 pg_GSS_recvauth(Port *port)
 {
@@ -1101,7 +1054,6 @@ pg_GSS_recvauth(Port *port)
                                lmin_s,
                                gflags;
        int                     mtype;
-       int                     ret;
        StringInfoData buf;
        gss_buffer_desc gbuf;
 
@@ -1254,10 +1206,23 @@ pg_GSS_recvauth(Port *port)
                 */
                gss_release_cred(&min_stat, &port->gss->cred);
        }
+       return pg_GSS_checkauth(port);
+}
+
+/*
+ * Check whether the GSSAPI-authenticated user is allowed to connect as the
+ * claimed username.
+ */
+static int
+pg_GSS_checkauth(Port *port)
+{
+       int                     ret;
+       OM_uint32       maj_stat,
+                               min_stat,
+                               lmin_s;
+       gss_buffer_desc gbuf;
 
        /*
-        * GSS_S_COMPLETE indicates that authentication is now complete.
-        *
         * Get the name of the user that authenticated, and compare it to the pg
         * username that was specified for the connection.
         */
@@ -1267,6 +1232,12 @@ pg_GSS_recvauth(Port *port)
                                         _("retrieving GSS user name failed"),
                                         maj_stat, min_stat);
 
+       /*
+        * Copy the original name of the authenticated principal into our backend
+        * memory for display later.
+        */
+       port->gss->princ = MemoryContextStrdup(TopMemoryContext, gbuf.value);
+
        /*
         * Split the username at the realm separator
         */
diff --git a/src/backend/libpq/be-gssapi-common.c b/src/backend/libpq/be-gssapi-common.c
new file mode 100644 (file)
index 0000000..40ada14
--- /dev/null
@@ -0,0 +1,74 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-gssapi-common.c
+ *     Common code for GSSAPI authentication and encryption
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *       src/backend/libpq/be-gssapi-common.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "be-gssapi-common.h"
+
+/*
+ * Helper function for getting all strings of a GSSAPI error (of specified
+ * stat).  Call once for GSS_CODE and once for MECH_CODE.
+ */
+static void
+pg_GSS_error_int(char *s, size_t len, OM_uint32 stat, int type)
+{
+       gss_buffer_desc gmsg;
+       size_t          i = 0;
+       OM_uint32       lmin_s,
+                               msg_ctx = 0;
+
+       gmsg.value = NULL;
+       gmsg.length = 0;
+
+       do
+       {
+               gss_display_status(&lmin_s, stat, type,
+                                                  GSS_C_NO_OID, &msg_ctx, &gmsg);
+               strlcpy(s + i, gmsg.value, len - i);
+               i += gmsg.length;
+               gss_release_buffer(&lmin_s, &gmsg);
+       }
+       while (msg_ctx && i < len);
+
+       if (msg_ctx || i == len)
+               ereport(WARNING,
+                               (errmsg_internal("incomplete GSS error report")));
+}
+
+/*
+ * Fetch and report all error messages from GSSAPI.  To avoid allocation,
+ * total error size is capped (at 128 bytes for each of major and minor).  No
+ * known mechanisms will produce error messages beyond this cap.
+ */
+void
+pg_GSS_error(int severity, const char *errmsg,
+                        OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+       char            msg_major[128],
+                               msg_minor[128];
+
+       /* Fetch major status message */
+       pg_GSS_error_int(msg_major, sizeof(msg_major), maj_stat, GSS_C_GSS_CODE);
+
+       /* Fetch mechanism minor status message */
+       pg_GSS_error_int(msg_minor, sizeof(msg_minor), min_stat, GSS_C_MECH_CODE);
+
+       /*
+        * errmsg_internal, since translation of the first part must be done
+        * before calling this function anyway.
+        */
+       ereport(severity,
+                       (errmsg_internal("%s", errmsg),
+                        errdetail_internal("%s: %s", msg_major, msg_minor)));
+}
diff --git a/src/backend/libpq/be-gssapi-common.h b/src/backend/libpq/be-gssapi-common.h
new file mode 100644 (file)
index 0000000..f6e90ea
--- /dev/null
@@ -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 (file)
index 0000000..6089d62
--- /dev/null
@@ -0,0 +1,627 @@
+/*-------------------------------------------------------------------------
+ *
+ * be-secure-gssapi.c
+ *  GSSAPI encryption support
+ *
+ * Portions Copyright (c) 2018-2018, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *  src/backend/libpq/be-secure-gssapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "be-gssapi-common.h"
+
+#include "libpq/auth.h"
+#include "libpq/libpq.h"
+#include "libpq/libpq-be.h"
+#include "libpq/pqformat.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+
+#include <unistd.h>
+
+
+/*
+ * Handle the encryption/decryption of data using GSSAPI.
+ *
+ * In the encrypted data stream on the wire, we break up the data
+ * into packets where each packet starts with a sizeof(uint32)-byte
+ * length (not allowed to be larger than the buffer sizes defined
+ * below) and then the encrypted data of that length immediately
+ * following.
+ *
+ * Encrypted data typically ends up being larger than the same data
+ * unencrypted, so we use fixed-size buffers for handling the
+ * encryption/decryption which are larger than PQComm's buffer will
+ * typically be to minimize the times where we have to make multiple
+ * packets and therefore sets of recv/send calls for a single
+ * read/write call to us.
+ *
+ * NOTE: The client and server have to agree on the max packet size,
+ * because we have to pass an entire packet to GSSAPI at a time and we
+ * don't want the other side to send arbitrairly huge packets as we
+ * would have to allocate memory for them to then pass them to GSSAPI.
+ */
+#define PQ_GSS_SEND_BUFFER_SIZE 16384
+#define PQ_GSS_RECV_BUFFER_SIZE 16384
+
+/* PqGSSSendBuffer is for *encrypted* data */
+static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE];
+static int     PqGSSSendPointer;       /* Next index to store a byte in
+                                                                * PqGSSSendBuffer */
+static int     PqGSSSendStart;         /* Next index to send a byte in
+                                                                * PqGSSSendBuffer */
+
+/* PqGSSRecvBuffer is for *encrypted* data */
+static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int     PqGSSRecvLength;        /* End of data available in PqGSSRecvBuffer */
+
+/* PqGSSResultBuffer is for *unencrypted* data */
+static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int     PqGSSResultPointer; /* Next index to read a byte from
+                                                                * PqGSSResultBuffer */
+static int     PqGSSResultLength;      /* End of data available in PqGSSResultBuffer */
+
+uint32         max_packet_size;        /* Maximum size we can encrypt and fit the
+                                                                * results into our output buffer */
+
+/*
+ * Attempt to write len bytes of data from ptr along a GSSAPI-encrypted connection.
+ *
+ * Connection must be fully established (including authentication step) before
+ * calling.  Returns the bytes actually consumed once complete.  Data is
+ * internally buffered; in the case of an incomplete write, the amount of data we
+ * processed (encrypted into our output buffer to be sent) will be returned.  If
+ * an error occurs or we would block, a negative value is returned and errno is
+ * set appropriately.
+ *
+ * To continue writing in the case of EWOULDBLOCK and similar, call this function
+ * again with matching ptr and len parameters.
+ */
+ssize_t
+be_gssapi_write(Port *port, void *ptr, size_t len)
+{
+       size_t          bytes_to_encrypt = len;
+       size_t          bytes_encrypted = 0;
+
+       /*
+        * Loop through encrypting data and sending it out until
+        * secure_raw_write() complains (which would likely mean that the socket
+        * is non-blocking and the requested send() would block, or there was some
+        * kind of actual error) and then return.
+        */
+       while (bytes_to_encrypt || PqGSSSendPointer)
+       {
+               OM_uint32       major,
+                                       minor;
+               gss_buffer_desc input,
+                                       output;
+               int                     conf = 0;
+               uint32          netlen;
+               pg_gssinfo *gss = port->gss;
+
+               /*
+                * Check if we have data in the encrypted output buffer that needs to
+                * be sent, and if so, try to send it.  If we aren't able to, return
+                * that back up to the caller.
+                */
+               if (PqGSSSendPointer)
+               {
+                       ssize_t         ret;
+                       ssize_t         amount = PqGSSSendPointer - PqGSSSendStart;
+
+                       ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, amount);
+                       if (ret <= 0)
+                       {
+                               /*
+                                * If we encrypted some data and it's in our output buffer,
+                                * but send() is saying that we would block, then tell the
+                                * caller how far we got with encrypting the data so that they
+                                * can call us again with whatever is left, at which point we
+                                * will try to send the remaining encrypted data first and
+                                * then move on to encrypting the rest of the data.
+                                */
+                               if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
+                                       return bytes_encrypted;
+                               else
+                                       return ret;
+                       }
+
+                       /*
+                        * Check if this was a partial write, and if so, move forward that
+                        * far in our buffer and try again.
+                        */
+                       if (ret != amount)
+                       {
+                               PqGSSSendStart += ret;
+                               continue;
+                       }
+
+                       /* All encrypted data was sent, our buffer is empty now. */
+                       PqGSSSendPointer = PqGSSSendStart = 0;
+               }
+
+               /*
+                * Check if there are any bytes left to encrypt.  If not, we're done.
+                */
+               if (!bytes_to_encrypt)
+                       return bytes_encrypted;
+
+               /*
+                * max_packet_size is the maximum amount of unencrypted data that,
+                * when encrypted, will fit into our encrypted-data output buffer.
+                *
+                * If we are being asked to send more than max_packet_size unencrypted
+                * data, then we will loop and create multiple packets, each with
+                * max_packet_size unencrypted data encrypted in them (at least, until
+                * secure_raw_write returns a failure saying we would be blocked, at
+                * which point we will let the caller know how far we got).
+                */
+               if (bytes_to_encrypt > max_packet_size)
+                       input.length = max_packet_size;
+               else
+                       input.length = bytes_to_encrypt;
+
+               input.value = (char *) ptr + bytes_encrypted;
+
+               output.value = NULL;
+               output.length = 0;
+
+               /* Create the next encrypted packet */
+               major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
+                                                &input, &conf, &output);
+               if (major != GSS_S_COMPLETE)
+                       pg_GSS_error(FATAL, gettext_noop("GSSAPI wrap error"), major, minor);
+
+               if (conf == 0)
+                       ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+
+               if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+                       ereport(FATAL, (errmsg("GSSAPI tried to send packet of size: %ld", output.length)));
+
+               bytes_encrypted += input.length;
+               bytes_to_encrypt -= input.length;
+
+               /* 4 network-order length bytes, then payload */
+               netlen = htonl(output.length);
+               memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32));
+               PqGSSSendPointer += sizeof(uint32);
+
+               memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+               PqGSSSendPointer += output.length;
+       }
+
+       return bytes_encrypted;
+}
+
+/*
+ * Read up to len bytes from a GSSAPI-encrypted connection into ptr.  Call
+ * only after the connection has been fully established (i.e., GSSAPI
+ * authentication is complete).  On success, returns the number of bytes
+ * written into ptr; otherwise, returns -1 and sets errno appropriately.
+ */
+ssize_t
+be_gssapi_read(Port *port, void *ptr, size_t len)
+{
+       OM_uint32       major,
+                               minor;
+       gss_buffer_desc input,
+                               output;
+       ssize_t         ret;
+       size_t          bytes_to_return = len;
+       size_t          bytes_returned = 0;
+       int                     conf = 0;
+       pg_gssinfo *gss = port->gss;
+
+       /*
+        * The goal here is to read an incoming encrypted packet, one at a time,
+        * decrypt it into our out buffer, returning to the caller what they asked
+        * for, and then saving anything else for the next call.
+        *
+        * First we look to see if we have unencrypted bytes available and, if so,
+        * copy those to the result.  If the caller asked for more than we had
+        * immediately available, then we try to read a packet off the wire and
+        * decrypt it.  If the read would block, then return the amount of
+        * unencrypted data we copied into the caller's ptr.
+        */
+       while (bytes_to_return)
+       {
+               /* Check if we have data in our buffer that we can return immediately */
+               if (PqGSSResultPointer < PqGSSResultLength)
+               {
+                       int                     bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
+                       int                     bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
+
+                       /*
+                        * Copy the data from our output buffer into the caller's buffer,
+                        * at the point where we last left off filling their buffer
+                        */
+                       memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultPointer, bytes_to_copy);
+                       PqGSSResultPointer += bytes_to_copy;
+                       bytes_to_return -= bytes_to_copy;
+                       bytes_returned += bytes_to_copy;
+
+                       /* Check if our result buffer is now empty and, if so, reset */
+                       if (PqGSSResultPointer == PqGSSResultLength)
+                               PqGSSResultPointer = PqGSSResultLength = 0;
+
+                       continue;
+               }
+
+               /*
+                * At this point, our output buffer should be empty with more bytes
+                * being requested to be read.  We are now ready to load the next
+                * packet and decrypt it (entirely) into our buffer.
+                *
+                * If we get a partial read back while trying to read a packet off the
+                * wire then we return the number of unencrypted bytes we were able to
+                * copy (if any, if we didn't copy any, then we return whatever
+                * secure_raw_read returned when we called it; likely -1) into the
+                * caller's ptr and wait to be called again, until we get a full
+                * packet to decrypt.
+                */
+
+               /* Check if we have the size of the packet already in our buffer. */
+               if (PqGSSRecvLength < sizeof(uint32))
+               {
+                       /*
+                        * We were not able to get the length of the packet last time, so
+                        * we need to do that first.
+                        */
+                       ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength,
+                                                                 sizeof(uint32) - PqGSSRecvLength);
+                       if (ret < 0)
+                               return bytes_returned ? bytes_returned : ret;
+
+                       PqGSSRecvLength += ret;
+
+                       /*
+                        * If we only got part of the packet length, then return however
+                        * many unencrypted bytes we copied to the caller and wait to be
+                        * called again.
+                        */
+                       if (PqGSSRecvLength < sizeof(uint32))
+                               return bytes_returned;
+               }
+
+               /*
+                * We have the length of the next packet at this point, so pull it out
+                * and then read whatever we have left of the packet to read.
+                */
+               input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+
+               /* Check for over-length packet */
+               if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
+                       ereport(FATAL, (errmsg("Over-size GSSAPI packet sent by the client.")));
+
+               /*
+                * Read as much of the packet as we are able to on this call into
+                * wherever we left off from the last time we were called.
+                */
+               ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength,
+                                                         input.length - (PqGSSRecvLength - sizeof(uint32)));
+               if (ret < 0)
+                       return bytes_returned ? bytes_returned : ret;
+
+               PqGSSRecvLength += ret;
+
+               /*
+                * If we got less than the rest of the packet then we need to return
+                * and be called again.  If we didn't have any bytes to return on this
+                * run then return -1 and set errno to EWOULDBLOCK.
+                */
+               if (PqGSSRecvLength - sizeof(uint32) < input.length)
+               {
+                       if (!bytes_returned)
+                       {
+                               errno = EWOULDBLOCK;
+                               return -1;
+                       }
+
+                       return bytes_returned;
+               }
+
+               /*
+                * We now have the full packet and we can perform the decryption and
+                * refill our output buffer, then loop back up to pass that back to
+                * the user.
+                */
+               output.value = NULL;
+               output.length = 0;
+               input.value = PqGSSRecvBuffer + sizeof(uint32);
+
+               major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf, NULL);
+               if (major != GSS_S_COMPLETE)
+                       pg_GSS_error(FATAL, gettext_noop("GSSAPI unwrap error"),
+                                                major, minor);
+
+               if (conf == 0)
+                       ereport(FATAL, (errmsg("GSSAPI did not provide confidentiality")));
+
+               memcpy(PqGSSResultBuffer, output.value, output.length);
+
+               PqGSSResultLength = output.length;
+
+               /* Our buffer is now empty, reset it */
+               PqGSSRecvLength = 0;
+
+               gss_release_buffer(&minor, &output);
+       }
+
+       return bytes_returned;
+}
+
+/*
+ * Read the specified number of bytes off the wire, waiting using
+ * WaitLatchOrSocket if we would block.
+ *
+ * Results are read into PqGSSRecvBuffer.
+ *
+ * Will always return either -1, to indicate a permanent error, or len.
+ */
+static ssize_t
+read_or_wait(Port *port, ssize_t len)
+{
+       ssize_t         ret;
+
+       /*
+        * Keep going until we either read in everything we were asked to, or we
+        * error out.
+        */
+       while (PqGSSRecvLength != len)
+       {
+               ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, len - PqGSSRecvLength);
+
+               /*
+                * If we got back an error and it wasn't just EWOULDBLOCK/EAGAIN, then
+                * give up.
+                */
+               if (ret < 0 && !(errno == EWOULDBLOCK || errno == EAGAIN))
+                       return -1;
+
+               /*
+                * Ok, we got back either a positive value, zero, or a negative result
+                * but EWOULDBLOCK or EAGAIN was set.
+                *
+                * If it was zero or negative, then we try to wait on the socket to be
+                * readable again.
+                */
+               if (ret <= 0)
+               {
+                       /*
+                        * If we got back less than zero, indicating an error, and that
+                        * wasn't just a EWOULDBOCK/EAGAIN, then give up.
+                        */
+                       if (ret < 0 && !(errno == EWOULDBLOCK || errno == EAGAIN))
+                               return -1;
+
+                       /*
+                        * We got back either zero, or -1 with EWOULDBLOCK/EAGAIN, so wait
+                        * on socket to be readable again.
+                        */
+                       WaitLatchOrSocket(MyLatch,
+                                                         WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH,
+                                                         port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
+
+                       /*
+                        * If we got back zero bytes, and then waited on the socket to be
+                        * readable and got back zero bytes on a second read, then this is
+                        * EOF and the client hung up on us.
+                        *
+                        * If we did get data here, then we can just fall through and
+                        * handle it just as if we got data the first time.
+                        *
+                        * Otherwise loop back to the top and try again.
+                        */
+                       if (ret == 0)
+                       {
+                               ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, len - PqGSSRecvLength);
+                               if (ret == 0)
+                                       return -1;
+                       }
+                       else
+                               continue;
+               }
+
+               PqGSSRecvLength += ret;
+       }
+
+       return len;
+}
+
+/*
+ * Start up a GSSAPI-encrypted connection.  This performs GSSAPI
+ * authentication; after this function completes, it is safe to call
+ * be_gssapi_read and be_gssapi_write.  Returns -1 and logs on failure;
+ * otherwise, returns 0 and marks the connection as ready for GSSAPI
+ * encryption.
+ *
+ * Note that unlike the be_gssapi_read/be_gssapi_write functions, this
+ * function WILL block on the socket to be ready for read/write (using
+ * WaitLatchOrSocket) as appropriate while establishing the GSSAPI
+ * session.
+ */
+ssize_t
+secure_open_gssapi(Port *port)
+{
+       bool            complete_next = false;
+       OM_uint32       major,
+                               minor;
+
+       /* initialize state variables */
+       PqGSSSendPointer = PqGSSSendStart = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
+
+       /*
+        * Use the configured keytab, if there is one.  Unfortunately, Heimdal
+        * doesn't support the cred store extensions, so use the env var.
+        */
+       if (pg_krb_server_keyfile != NULL && strlen(pg_krb_server_keyfile) > 0)
+               setenv("KRB5_KTNAME", pg_krb_server_keyfile, 1);
+
+       while (true)
+       {
+               ssize_t         ret;
+               gss_buffer_desc input,
+                                       output = GSS_C_EMPTY_BUFFER;
+
+               /*
+                * The client always sends first, so try to go ahead and read the
+                * length and wait on the socket to be readable again if that fails.
+                */
+               ret = read_or_wait(port, sizeof(uint32));
+               if (ret < 0)
+                       return ret;
+
+               /*
+                * Get the length for this packet from the length header.
+                */
+               input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+
+               /* Done with the length, reset our buffer */
+               PqGSSRecvLength = 0;
+
+               /*
+                * During initialization, packets are always fully consumed and
+                * shouldn't ever be over PQ_GSS_RECV_BUFFER_SIZE in length.
+                *
+                * Verify on our side that the client doesn't do something funny.
+                */
+               if (input.length > PQ_GSS_RECV_BUFFER_SIZE)
+                       ereport(FATAL, (errmsg("Over-size GSSAPI packet sent by the client: %ld", input.length)));
+
+               /*
+                * Get the rest of the packet so we can pass it to GSSAPI to accept
+                * the context.
+                */
+               ret = read_or_wait(port, input.length);
+               if (ret < 0)
+                       return ret;
+
+               input.value = PqGSSRecvBuffer;
+
+               /* Process incoming data.  (The client sends first.) */
+               major = gss_accept_sec_context(&minor, &port->gss->ctx,
+                                                                          GSS_C_NO_CREDENTIAL, &input,
+                                                                          GSS_C_NO_CHANNEL_BINDINGS,
+                                                                          &port->gss->name, NULL, &output, NULL,
+                                                                          NULL, NULL);
+               if (GSS_ERROR(major))
+               {
+                       pg_GSS_error(ERROR, gettext_noop("GSSAPI context error"),
+                                                major, minor);
+                       gss_release_buffer(&minor, &output);
+                       return -1;
+               }
+               else if (!(major & GSS_S_CONTINUE_NEEDED))
+               {
+                       /*
+                        * rfc2744 technically permits context negotiation to be complete
+                        * both with and without a packet to be sent.
+                        */
+                       complete_next = true;
+               }
+
+               /* Done handling the incoming packet, reset our buffer */
+               PqGSSRecvLength = 0;
+
+               /*
+                * Check if we have data to send and, if we do, make sure to send it
+                * all
+                */
+               if (output.length != 0)
+               {
+                       uint32          netlen = htonl(output.length);
+
+                       if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+                               ereport(FATAL, (errmsg("GSSAPI tried to send oversize packet")));
+
+                       memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
+                       PqGSSSendPointer += sizeof(uint32);
+
+                       memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+                       PqGSSSendPointer += output.length;
+
+                       while (PqGSSSendStart != sizeof(uint32) + output.length)
+                       {
+                               ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, sizeof(uint32) + output.length - PqGSSSendStart);
+                               if (ret <= 0)
+                               {
+                                       WaitLatchOrSocket(MyLatch,
+                                                                         WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH,
+                                                                         port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
+                                       continue;
+                               }
+
+                               PqGSSSendStart += ret;
+                       }
+
+                       /* Done sending the packet, reset our buffer */
+                       PqGSSSendStart = PqGSSSendPointer = 0;
+
+                       gss_release_buffer(&minor, &output);
+               }
+
+               /*
+                * If we got back that the connection is finished being set up, now
+                * that's we've sent the last packet, exit our loop.
+                */
+               if (complete_next)
+                       break;
+       }
+
+       /*
+        * Determine the max packet size which will fit in our buffer, after
+        * accounting for the length
+        */
+       major = gss_wrap_size_limit(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAULT,
+                                                               PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
+
+       if (GSS_ERROR(major))
+               pg_GSS_error(FATAL, gettext_noop("GSSAPI size check error"),
+                                        major, minor);
+
+       port->gss->enc = true;
+
+       return 0;
+}
+
+/*
+ * Return if GSSAPI authentication was used on this connection.
+ */
+bool
+be_gssapi_get_auth(Port *port)
+{
+       if (!port || !port->gss)
+               return false;
+
+       return port->gss->auth;
+}
+
+/*
+ * Return if GSSAPI encryption is enabled and being used on this connection.
+ */
+bool
+be_gssapi_get_enc(Port *port)
+{
+       if (!port || !port->gss)
+               return false;
+
+       return port->gss->enc;
+}
+
+/*
+ * Return the GSSAPI principal used for authentication on this connection.
+ */
+const char *
+be_gssapi_get_princ(Port *port)
+{
+       if (!port || !port->gss->auth)
+               return NULL;
+
+       return port->gss->princ;
+}
index a7def3168d1285dc93fce68b121bfe4963edf44e..b90eb0ab6b7ec210013820aa2245ad8b1ba8e281 100644 (file)
@@ -159,6 +159,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);
@@ -264,6 +272,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);
index ce9bca868ccf0d7b545b6494c097e203ddc81532..37d5ad44a54e1c59bb060c19d5c5bf8240c8d46b 100644 (file)
@@ -994,7 +994,9 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
        }
        else if (strcmp(token->string, "host") == 0 ||
                         strcmp(token->string, "hostssl") == 0 ||
-                        strcmp(token->string, "hostnossl") == 0)
+                        strcmp(token->string, "hostnossl") == 0 ||
+                        strcmp(token->string, "hostgssenc") == 0 ||
+                        strcmp(token->string, "hostnogssenc") == 0)
        {
 
                if (token->string[4] == 's')    /* "hostssl" */
@@ -1022,10 +1024,23 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
                        *err_msg = "hostssl record cannot match because SSL is not supported by this build";
 #endif
                }
-               else if (token->string[4] == 'n')       /* "hostnossl" */
+               else if (token->string[4] == 'g')       /* "hostgssenc" */
                {
-                       parsedline->conntype = ctHostNoSSL;
+                       parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+                       ereport(elevel,
+                                       (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                        errmsg("hostgssenc record cannot match because GSSAPI is not supported by this build"),
+                                        errhint("Compile with --with-gssapi to use GSSAPI connections."),
+                                        errcontext("line %d of configuration file \"%s\"",
+                                                               line_num, HbaFileName)));
+                       *err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
+#endif
                }
+               else if (token->string[4] == 'n' && token->string[6] == 's')
+                       parsedline->conntype = ctHostNoSSL;
+               else if (token->string[4] == 'n' && token->string[6] == 'g')
+                       parsedline->conntype = ctHostNoGSS;
                else
                {
                        /* "host" */
@@ -1404,6 +1419,19 @@ parse_hba_line(TokenizedLine *tok_line, int elevel)
                *err_msg = "gssapi authentication is not supported on local sockets";
                return NULL;
        }
+       if (parsedline->conntype == ctHostGSS &&
+               parsedline->auth_method != uaGSS &&
+               parsedline->auth_method != uaReject &&
+               parsedline->auth_method != uaTrust)
+       {
+               ereport(elevel,
+                               (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                errmsg("GSSAPI encryption only supports gss, trust, or reject authentication"),
+                                errcontext("line %d of configuration file \"%s\"",
+                                                       line_num, HbaFileName)));
+               *err_msg = "GSSAPI encryption only supports gss, trust, or reject authenticaion";
+               return NULL;
+       }
 
        if (parsedline->conntype != ctLocal &&
                parsedline->auth_method == uaPeer)
@@ -2078,6 +2106,17 @@ check_hba(hbaPort *port)
                                        continue;
                        }
 
+                       /* Check GSSAPI state */
+#ifdef ENABLE_GSS
+                       if (port->gss->enc && hba->conntype == ctHostNoGSS)
+                               continue;
+                       else if (!port->gss->enc && hba->conntype == ctHostGSS)
+                               continue;
+#else
+                       if (hba->conntype == ctHostGSS)
+                               continue;
+#endif
+
                        /* Check IP address */
                        switch (hba->ip_cmp_method)
                        {
@@ -2414,6 +2453,12 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
                        case ctHostNoSSL:
                                typestr = "hostnossl";
                                break;
+                       case ctHostGSS:
+                               typestr = "hostgssenc";
+                               break;
+                       case ctHostNoGSS:
+                               typestr = "hostnogssenc";
+                               break;
                }
                if (typestr)
                        values[index++] = CStringGetTextDatum(typestr);
index 2a8472b91aee2453a8c34ac916ed93743dcbf62a..0355fa65fb899b6cb6823fae5d971042e1db033a 100644 (file)
@@ -2634,6 +2634,9 @@ static Size BackendActivityBufferSize = 0;
 #ifdef USE_SSL
 static PgBackendSSLStatus *BackendSslStatusBuffer = NULL;
 #endif
+#ifdef ENABLE_GSS
+static PgBackendGSSStatus *BackendGssStatusBuffer = NULL;
+#endif
 
 
 /*
@@ -2766,6 +2769,28 @@ CreateSharedBackendStatus(void)
                }
        }
 #endif
+
+#ifdef ENABLE_GSS
+       /* Create or attach to the shared GSSAPI status buffer */
+       size = mul_size(sizeof(PgBackendGSSStatus), NumBackendStatSlots);
+       BackendGssStatusBuffer = (PgBackendGSSStatus *)
+               ShmemInitStruct("Backend GSS Status Buffer", size, &found);
+
+       if (!found)
+       {
+               PgBackendGSSStatus *ptr;
+
+               MemSet(BackendGssStatusBuffer, 0, size);
+
+               /* Initialize st_gssstatus pointers. */
+               ptr = BackendGssStatusBuffer;
+               for (i = 0; i < NumBackendStatSlots; i++)
+               {
+                       BackendStatusArray[i].st_gssstatus = ptr;
+                       ptr++;
+               }
+       }
+#endif
 }
 
 
@@ -2953,6 +2978,24 @@ pgstat_bestart(void)
 #else
        beentry->st_ssl = false;
 #endif
+
+#ifdef ENABLE_GSS
+       if (MyProcPort && MyProcPort->gss != NULL)
+       {
+               beentry->st_gss = true;
+               beentry->st_gssstatus->gss_auth = be_gssapi_get_auth(MyProcPort);
+               beentry->st_gssstatus->gss_enc = be_gssapi_get_enc(MyProcPort);
+
+               if (beentry->st_gssstatus->gss_auth)
+                       strlcpy(beentry->st_gssstatus->gss_princ, be_gssapi_get_princ(MyProcPort), NAMEDATALEN);
+       }
+       else
+       {
+               beentry->st_gss = false;
+       }
+#else
+       beentry->st_gss = false;
+#endif
        beentry->st_state = STATE_UNDEFINED;
        beentry->st_appname[0] = '\0';
        beentry->st_activity_raw[0] = '\0';
@@ -3595,6 +3638,9 @@ pgstat_get_wait_client(WaitEventClient w)
                case WAIT_EVENT_WAL_SENDER_WRITE_DATA:
                        event_name = "WalSenderWriteData";
                        break;
+               case WAIT_EVENT_GSS_OPEN_SERVER:
+                       event_name = "GSSOpenServer";
+                       break;
                        /* no default case, so that compiler will warn */
        }
 
index fe599632d3d478554a4e51b9189dac673433f4b5..067487fdcb0b2f06f796b750c69b930ca9af40d7 100644 (file)
@@ -1889,9 +1889,12 @@ initMasks(fd_set *rmask)
  * if that's what you want.  Return STATUS_ERROR if you don't want to
  * send anything to the client, which would typically be appropriate
  * if we detect a communications failure.)
+ *
+ * Set secure_done when negotiation of an encrypted layer (currently, TLS or
+ * GSSAPI) is already completed.
  */
 static int
-ProcessStartupPacket(Port *port, bool SSLdone)
+ProcessStartupPacket(Port *port, bool secure_done)
 {
        int32           len;
        void       *buf;
@@ -1924,9 +1927,10 @@ ProcessStartupPacket(Port *port, bool SSLdone)
        if (pq_getbytes(((char *) &len) + 1, 3) == EOF)
        {
                /* Got a partial length word, so bleat about that */
-               ereport(COMMERROR,
-                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
-                                errmsg("incomplete startup packet")));
+               if (!secure_done)
+                       ereport(COMMERROR,
+                                       (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                        errmsg("incomplete startup packet")));
                return STATUS_ERROR;
        }
 
@@ -1975,7 +1979,7 @@ ProcessStartupPacket(Port *port, bool SSLdone)
                return STATUS_ERROR;
        }
 
-       if (proto == NEGOTIATE_SSL_CODE && !SSLdone)
+       if (proto == NEGOTIATE_SSL_CODE && !secure_done)
        {
                char            SSLok;
 
@@ -2008,6 +2012,32 @@ retry1:
                /* but not another SSL negotiation request */
                return ProcessStartupPacket(port, true);
        }
+       else if (proto == NEGOTIATE_GSS_CODE && !secure_done)
+       {
+               char            GSSok = 'N';
+#ifdef ENABLE_GSS
+               /* No GSSAPI encryption when on Unix socket */
+               if (!IS_AF_UNIX(port->laddr.addr.ss_family))
+                       GSSok = 'G';
+#endif
+
+               while (send(port->sock, &GSSok, 1, 0) != 1)
+               {
+                       if (errno == EINTR)
+                               continue;
+                       ereport(COMMERROR,
+                                       (errcode_for_socket_access(),
+                                        errmsg("failed to send GSSAPI negotiation response: %m)")));
+                       return STATUS_ERROR;    /* close the connection */
+               }
+
+#ifdef ENABLE_GSS
+               if (GSSok == 'G' && secure_open_gssapi(port) == -1)
+                       return STATUS_ERROR;
+#endif
+               /* Won't ever see more than one negotiation request */
+               return ProcessStartupPacket(port, true);
+       }
 
        /* Could add additional special packet types here */
 
index 7c2afe64272244c80cfb57b6b81e7a0b239258bb..9a1d07bee3304c9f80abdeb31379ba40005a2842 100644 (file)
@@ -545,7 +545,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 Datum
 pg_stat_get_activity(PG_FUNCTION_ARGS)
 {
-#define PG_STAT_GET_ACTIVITY_COLS      26
+#define PG_STAT_GET_ACTIVITY_COLS      29
        int                     num_backends = pgstat_fetch_stat_numbackends();
        int                     curr_backend;
        int                     pid = PG_ARGISNULL(0) ? -1 : PG_GETARG_INT32(0);
@@ -859,6 +859,21 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
                                values[18] = BoolGetDatum(false);       /* ssl */
                                nulls[19] = nulls[20] = nulls[21] = nulls[22] = nulls[23] = nulls[24] = nulls[25] = true;
                        }
+
+                       /* GSSAPI information */
+                       if (beentry->st_gss)
+                       {
+                               values[26] = BoolGetDatum(beentry->st_gssstatus->gss_auth); /* gss_auth */
+                               values[27] = CStringGetTextDatum(beentry->st_gssstatus->gss_princ);
+                               values[28] = BoolGetDatum(beentry->st_gssstatus->gss_enc);      /* GSS Encryption in use */
+                       }
+                       else
+                       {
+                               values[26] = BoolGetDatum(false);       /* gss_auth */
+                               nulls[27] = true;       /* No GSS principal */
+                               values[28] = BoolGetDatum(false);       /* GSS Encryption not in
+                                                                                                        * use */
+                       }
                }
                else
                {
@@ -883,6 +898,9 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
                        nulls[23] = true;
                        nulls[24] = true;
                        nulls[25] = true;
+                       nulls[26] = true;
+                       nulls[27] = true;
+                       nulls[28] = true;
                }
 
                tuplestore_putvalues(tupstore, tupdesc, values, nulls);
index 72188b7f3ef34f8e0c52397112d47cc658fae545..bc3d10e5158404fffa0b6f3f2ee62a4b9c23ea58 100644 (file)
@@ -160,6 +160,7 @@ static void print_with_linenumbers(FILE *output, char *lines,
 static void minimal_error_message(PGresult *res);
 
 static void printSSLInfo(void);
+static void printGSSInfo(void);
 static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
 static char *pset_value_string(const char *param, struct printQueryOpt *popt);
 
@@ -621,6 +622,7 @@ exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
                                                   db, PQuser(pset.db), host, PQport(pset.db));
                        }
                        printSSLInfo();
+                       printGSSInfo();
                }
        }
 
@@ -3184,6 +3186,7 @@ connection_warnings(bool in_startup)
                checkWin32Codepage();
 #endif
                printSSLInfo();
+               printGSSInfo();
        }
 }
 
@@ -3216,6 +3219,20 @@ printSSLInfo(void)
                   (compression && strcmp(compression, "off") != 0) ? _("on") : _("off"));
 }
 
+/*
+ * printGSSInfo
+ *
+ * Prints information about the current GSSAPI connection, if GSSAPI encryption is in use
+ */
+static void
+printGSSInfo(void)
+{
+       if (!PQgssEncInUse(pset.db))
+               return;                                 /* no GSSAPI encryption in use */
+
+       printf(_("GSSAPI Encrypted connection\n"));
+}
+
 
 /*
  * checkWin32Codepage
index a7050edca09b642b893a4c3c24ef74d8bb244d5d..fb257c17c8924935af2dfed400839997eb23d780 100644 (file)
   proname => 'pg_stat_get_activity', prorows => '100', proisstrict => 'f',
   proretset => 't', provolatile => 's', proparallel => 'r',
   prorettype => 'record', proargtypes => 'int4',
-  proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,bool,text,numeric,text}',
-  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,sslcompression,ssl_client_dn,ssl_client_serial,ssl_issuer_dn}',
+  proallargtypes => '{int4,oid,int4,oid,text,text,text,text,text,timestamptz,timestamptz,timestamptz,timestamptz,inet,text,int4,xid,xid,text,bool,text,text,int4,bool,text,numeric,text,bool,text,bool}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{pid,datid,pid,usesysid,application_name,state,query,wait_event_type,wait_event,xact_start,query_start,backend_start,state_change,client_addr,client_hostname,client_port,backend_xid,backend_xmin,backend_type,ssl,sslversion,sslcipher,sslbits,sslcompression,ssl_client_dn,ssl_client_serial,ssl_issuer_dn,gss_auth,gss_princ,gss_enc}',
   prosrc => 'pg_stat_get_activity' },
 { oid => '3318',
   descr => 'statistics: information about progress of backends running maintenance command',
index c65eb9dc8a5a57f19472fc4012aad157333abb5c..186e433574818a0eef72cd922c4d9ff2d7e17721 100644 (file)
@@ -55,7 +55,9 @@ typedef enum ConnType
        ctLocal,
        ctHost,
        ctHostSSL,
-       ctHostNoSSL
+       ctHostNoSSL,
+       ctHostGSS,
+       ctHostNoGSS,
 } ConnType;
 
 typedef enum ClientCertMode
index 248055f10b4eb986907888961d03bc91b01a1ec6..059218c85a3df31ed5de0334ec66fa6b9847742e 100644 (file)
@@ -86,6 +86,10 @@ typedef struct
        gss_cred_id_t cred;                     /* GSSAPI connection cred's */
        gss_ctx_id_t ctx;                       /* GSSAPI connection context */
        gss_name_t      name;                   /* GSSAPI client name */
+       char       *princ;                      /* GSSAPI Principal used for auth, NULL if
+                                                                * GSSAPI auth was not used */
+       bool            auth;                   /* GSSAPI Authentication used */
+       bool            enc;                    /* GSSAPI encryption in use */
 #endif
 } pg_gssinfo;
 #endif
@@ -164,6 +168,9 @@ typedef struct Port
        int                     keepalives_interval;
        int                     keepalives_count;
 
+       /*
+        * GSSAPI structures.
+        */
 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
 
        /*
@@ -262,6 +269,13 @@ extern void be_tls_get_peer_subject_name(Port *port, char *ptr, size_t len);
 extern void be_tls_get_peer_issuer_name(Port *port, char *ptr, size_t len);
 extern void be_tls_get_peer_serial(Port *port, char *ptr, size_t len);
 
+/*
+ * Return information about the GSSAPI authenticated connection
+ */
+extern bool be_gssapi_get_auth(Port *port);
+extern bool be_gssapi_get_enc(Port *port);
+extern const char *be_gssapi_get_princ(Port *port);
+
 /*
  * Get the server certificate hash for SCRAM channel binding type
  * tls-server-end-point.
@@ -279,6 +293,12 @@ extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
 
 #endif /* USE_SSL */
 
+#ifdef ENABLE_GSS
+/* Read and write to a GSSAPI-encrypted connection. */
+extern ssize_t be_gssapi_read(Port *port, void *ptr, size_t len);
+extern ssize_t be_gssapi_write(Port *port, void *ptr, size_t len);
+#endif                                                 /* ENABLE_GSS */
+
 extern ProtocolVersion FrontendProtocol;
 
 /* TCP keepalives configuration. These are no-ops on an AF_UNIX socket. */
index 755819cc5846300c89aa5921cbd47bbc38b21087..41f9257aa9d9d7fcaffa09780ea98c823f391696 100644 (file)
@@ -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;
 
index 5b84bdda99117ac30ebeb181acd4fb0a84efdd7f..baf6a4b6c029ab27ca5265fd0fb99ffc9a35ff50 100644 (file)
@@ -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 */
index 53d4a9c43194064a4b3de89074d5e4145a2c5942..5888242f757dec70fff74bcde90ed1f5198be42b 100644 (file)
@@ -801,7 +801,8 @@ typedef enum
        WAIT_EVENT_SSL_OPEN_SERVER,
        WAIT_EVENT_WAL_RECEIVER_WAIT_START,
        WAIT_EVENT_WAL_SENDER_WAIT_WAL,
-       WAIT_EVENT_WAL_SENDER_WRITE_DATA
+       WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+       WAIT_EVENT_GSS_OPEN_SERVER,
 } WaitEventClient;
 
 /* ----------
@@ -989,6 +990,23 @@ typedef struct PgBackendSSLStatus
        char            ssl_issuer_dn[NAMEDATALEN];
 } PgBackendSSLStatus;
 
+/*
+ * PgBackendGSSStatus
+ *
+ * For each backend, we keep the GSS status in a separate struct, that
+ * is only filled in if GSS is enabled.
+ *
+ * All char arrays must be null-terminated.
+ */
+typedef struct PgBackendGSSStatus
+{
+       /* Information about GSSAPI connection */
+       char            gss_princ[NAMEDATALEN]; /* GSSAPI Principal used to auth */
+       bool            gss_auth;               /* If GSSAPI authentication was used */
+       bool            gss_enc;                /* If encryption is being used */
+
+} PgBackendGSSStatus;
+
 
 /* ----------
  * PgBackendStatus
@@ -1043,6 +1061,10 @@ typedef struct PgBackendStatus
        bool            st_ssl;
        PgBackendSSLStatus *st_sslstatus;
 
+       /* Information about GSSAPI connection */
+       bool            st_gss;
+       PgBackendGSSStatus *st_gssstatus;
+
        /* current state */
        BackendState st_state;
 
index 025542dfe9fd7125148dad5ba86cee6bf9539bc2..c734965d63f86d1c74507582aa63ee6cf5fd36a4 100644 (file)
@@ -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
index cc9ee9ce6b82506ec733d66aef273176a0f9d749..7c808e5215ce8396d4b4307f2e9718081a8d3387 100644 (file)
@@ -174,3 +174,5 @@ PQresultVerboseErrorMessage 171
 PQencryptPasswordConn     172
 PQresultMemorySize        173
 PQhostaddr                174
+PQgssEncInUse             175
+PQgetgssctx               176
index 4cbe64ceb58469200d0c2ee5a8df7eeb7c4176c8..624e02bcaae037d9e46f6ac6a7725c2c9f522b4c 100644 (file)
  * GSSAPI authentication system.
  */
 
-#if defined(WIN32) && !defined(_MSC_VER)
-/*
- * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
- * that contain the OIDs required. Redefine here, values copied
- * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
- */
-static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc =
-{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
-static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc;
-#endif
-
-/*
- * Fetch all errors of a specific type and append to "str".
- */
-static void
-pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
-                                OM_uint32 stat, int type)
-{
-       OM_uint32       lmin_s;
-       gss_buffer_desc lmsg;
-       OM_uint32       msg_ctx = 0;
-
-       do
-       {
-               gss_display_status(&lmin_s, stat, type,
-                                                  GSS_C_NO_OID, &msg_ctx, &lmsg);
-               appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value);
-               gss_release_buffer(&lmin_s, &lmsg);
-       } while (msg_ctx);
-}
-
-/*
- * GSSAPI errors contain two parts; put both into conn->errorMessage.
- */
-static void
-pg_GSS_error(const char *mprefix, PGconn *conn,
-                        OM_uint32 maj_stat, OM_uint32 min_stat)
-{
-       resetPQExpBuffer(&conn->errorMessage);
-
-       /* Fetch major error codes */
-       pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE);
-
-       /* Add the minor codes as well */
-       pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE);
-}
+#include "fe-gssapi-common.h"
 
 /*
  * Continue GSS authentication with next token as needed.
@@ -195,10 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen)
 static int
 pg_GSS_startup(PGconn *conn, int payloadlen)
 {
-       OM_uint32       maj_stat,
-                               min_stat;
-       int                     maxlen;
-       gss_buffer_desc temp_gbuf;
+       int                     ret;
        char       *host = conn->connhost[conn->whichhost].host;
 
        if (!(host && host[0] != '\0'))
@@ -215,33 +167,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen)
                return STATUS_ERROR;
        }
 
-       /*
-        * Import service principal name so the proper ticket can be acquired by
-        * the GSSAPI system.
-        */
-       maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2;
-       temp_gbuf.value = (char *) malloc(maxlen);
-       if (!temp_gbuf.value)
-       {
-               printfPQExpBuffer(&conn->errorMessage,
-                                                 libpq_gettext("out of memory\n"));
-               return STATUS_ERROR;
-       }
-       snprintf(temp_gbuf.value, maxlen, "%s@%s",
-                        conn->krbsrvname, host);
-       temp_gbuf.length = strlen(temp_gbuf.value);
-
-       maj_stat = gss_import_name(&min_stat, &temp_gbuf,
-                                                          GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam);
-       free(temp_gbuf.value);
-
-       if (maj_stat != GSS_S_COMPLETE)
-       {
-               pg_GSS_error(libpq_gettext("GSSAPI name import error"),
-                                        conn,
-                                        maj_stat, min_stat);
-               return STATUS_ERROR;
-       }
+       ret = pg_GSS_load_servicename(conn);
+       if (ret != STATUS_OK)
+               return ret;
 
        /*
         * Initial packet is the same as a continuation packet with no initial
@@ -977,7 +905,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
                        printfPQExpBuffer(&conn->errorMessage,
                                                          libpq_gettext("SSPI authentication not supported\n"));
                        return STATUS_ERROR;
-#endif                                                 /* !define(ENABLE_GSSAPI) */
+#endif                                                 /* !define(ENABLE_GSS) */
 #endif                                                 /* ENABLE_SSPI */
 
 
index e3bf6a7449fa5dd721330ee9f8b703d567b94efd..68cf42245709f1eca8bc820a460b2ad45703ae3a 100644 (file)
@@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
 #else
 #define DefaultSSLMode "disable"
 #endif
+#ifdef ENABLE_GSS
+#include "fe-gssapi-common.h"
+#define DefaultGSSMode "prefer"
+#else
+#define DefaultGSSMode "disable"
+#endif
 
 /* ----------
  * Definition of the conninfo parameters and their fallback resources.
@@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
                "Require-Peer", "", 10,
        offsetof(struct pg_conn, requirepeer)},
 
+       /*
+        * Expose gssencmode similarly to sslmode - we can still handle "disable"
+        * and "prefer".
+        */
+       {"gssencmode", "PGGSSMODE", DefaultGSSMode, NULL,
+               "GSS-Mode", "", 7,              /* sizeof("disable") == 7 */
+       offsetof(struct pg_conn, gssencmode)},
+
 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
        /* Kerberos and GSSAPI authentication support specifying the service name */
        {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@@ -1226,6 +1240,39 @@ connectOptions2(PGconn *conn)
                        goto oom_error;
        }
 
+       /*
+        * validate gssencmode option
+        */
+       if (conn->gssencmode)
+       {
+               if (strcmp(conn->gssencmode, "disable") != 0 &&
+                       strcmp(conn->gssencmode, "prefer") != 0 &&
+                       strcmp(conn->gssencmode, "require") != 0)
+               {
+                       conn->status = CONNECTION_BAD;
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("invalid gssencmode value: \"%s\"\n"),
+                                                         conn->gssencmode);
+                       return false;
+               }
+#ifndef ENABLE_GSS
+               if (strcmp(conn->gssencmode, "require") == 0)
+               {
+                       conn->status = CONNECTION_BAD;
+                       printfPQExpBuffer(
+                                                         &conn->errorMessage,
+                                                         libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
+                       return false;
+               }
+#endif
+       }
+       else
+       {
+               conn->gssencmode = strdup(DefaultGSSMode);
+               if (!conn->gssencmode)
+                       goto oom_error;
+       }
+
        /*
         * Resolve special "auto" client_encoding from the locale
         */
@@ -1827,6 +1874,11 @@ connectDBStart(PGconn *conn)
         */
        resetPQExpBuffer(&conn->errorMessage);
 
+#ifdef ENABLE_GSS
+       if (conn->gssencmode[0] == 'd') /* "disable" */
+               conn->try_gss = false;
+#endif
+
        /*
         * Set up to try to connect to the first host.  (Setting whichhost = -1 is
         * a bit of a cheat, but PQconnectPoll will advance it to 0 before
@@ -2099,6 +2151,7 @@ PQconnectPoll(PGconn *conn)
                case CONNECTION_NEEDED:
                case CONNECTION_CHECK_WRITABLE:
                case CONNECTION_CONSUME:
+               case CONNECTION_GSS_STARTUP:
                        break;
 
                default:
@@ -2640,17 +2693,57 @@ keep_going:                                             /* We will come back to here until there is
                                }
 #endif                                                 /* HAVE_UNIX_SOCKETS */
 
+                               if (IS_AF_UNIX(conn->raddr.addr.ss_family))
+                               {
+                                       /* Don't request SSL or GSSAPI over Unix sockets */
 #ifdef USE_SSL
+                                       conn->allow_ssl_try = false;
+#endif
+#ifdef ENABLE_GSS
+                                       conn->try_gss = false;
+#endif
+                               }
+
+#ifdef ENABLE_GSS
 
                                /*
-                                * If SSL is enabled and we haven't already got it running,
-                                * request it instead of sending the startup message.
+                                * If GSSAPI is enabled and we have a ccache, try to set it up
+                                * before sending startup messages.  If it's already
+                                * operating, don't try SSL and instead just build the startup
+                                * packet.
                                 */
-                               if (IS_AF_UNIX(conn->raddr.addr.ss_family))
+                               if (conn->try_gss && !conn->gctx)
+                                       conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
+                               if (conn->try_gss && !conn->gctx)
                                {
-                                       /* Don't bother requesting SSL over a Unix socket */
-                                       conn->allow_ssl_try = false;
+                                       ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE);
+
+                                       if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
+                                       {
+                                               appendPQExpBuffer(&conn->errorMessage,
+                                                                                 libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
+                                                                                 SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
+                                               goto error_return;
+                                       }
+
+                                       /* Ok, wait for response */
+                                       conn->status = CONNECTION_GSS_STARTUP;
+                                       return PGRES_POLLING_READING;
                                }
+                               else if (!conn->gctx && conn->gssencmode[0] == 'r')
+                               {
+                                       appendPQExpBuffer(&conn->errorMessage,
+                                                                         libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache, no server support, or using a local socket)\n"));
+                                       goto error_return;
+                               }
+#endif
+
+#ifdef USE_SSL
+
+                               /*
+                                * If SSL is enabled and we haven't already got it running,
+                                * request it instead of sending the startup message.
+                                */
                                if (conn->allow_ssl_try && !conn->wait_ssl_try &&
                                        !conn->ssl_in_use)
                                {
@@ -2844,6 +2937,98 @@ keep_going:                                              /* We will come back to here until there is
 #endif                                                 /* USE_SSL */
                        }
 
+               case CONNECTION_GSS_STARTUP:
+                       {
+#ifdef ENABLE_GSS
+                               PostgresPollingStatusType pollres;
+
+                               /*
+                                * If we haven't yet, get the postmaster's response to our
+                                * negotiation packet
+                                */
+                               if (conn->try_gss && !conn->gctx)
+                               {
+                                       char            gss_ok;
+                                       int                     rdresult = pqReadData(conn);
+
+                                       if (rdresult < 0)
+                                               /* pqReadData fills in error message */
+                                               goto error_return;
+                                       else if (rdresult == 0)
+                                               /* caller failed to wait for data */
+                                               return PGRES_POLLING_READING;
+                                       if (pqGetc(&gss_ok, conn) < 0)
+                                               /* shouldn't happen... */
+                                               return PGRES_POLLING_READING;
+
+                                       if (gss_ok == 'E')
+                                       {
+                                               /*
+                                                * Server failure of some sort.  Assume it's a
+                                                * protocol version support failure, and let's see if
+                                                * we can't recover (if it's not, we'll get a better
+                                                * error message on retry).  Server gets fussy if we
+                                                * don't hang up the socket, though.
+                                                */
+                                               conn->try_gss = false;
+                                               pqDropConnection(conn, true);
+                                               conn->status = CONNECTION_NEEDED;
+                                               goto keep_going;
+                                       }
+
+                                       /* mark byte consumed */
+                                       conn->inStart = conn->inCursor;
+
+                                       if (gss_ok == 'N')
+                                       {
+                                               /* Server doesn't want GSSAPI; fall back if we can */
+                                               if (conn->gssencmode[0] == 'r')
+                                               {
+                                                       appendPQExpBufferStr(&conn->errorMessage,
+                                                                                                libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
+                                                       goto error_return;
+                                               }
+
+                                               conn->try_gss = false;
+                                               conn->status = CONNECTION_MADE;
+                                               return PGRES_POLLING_WRITING;
+                                       }
+                                       else if (gss_ok != 'G')
+                                       {
+                                               appendPQExpBuffer(&conn->errorMessage,
+                                                                                 libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
+                                                                                 gss_ok);
+                                               goto error_return;
+                                       }
+                               }
+
+                               /* Begin or continue GSSAPI negotiation */
+                               pollres = pqsecure_open_gss(conn);
+                               if (pollres == PGRES_POLLING_OK)
+                               {
+                                       /* All set for startup packet */
+                                       conn->status = CONNECTION_MADE;
+                                       return PGRES_POLLING_WRITING;
+                               }
+                               else if (pollres == PGRES_POLLING_FAILED &&
+                                                conn->gssencmode[0] == 'p')
+                               {
+                                       /*
+                                        * We failed, but we can retry on "prefer".  Have to drop
+                                        * the current connection to do so, though.
+                                        */
+                                       conn->try_gss = false;
+                                       pqDropConnection(conn, true);
+                                       conn->status = CONNECTION_NEEDED;
+                                       goto keep_going;
+                               }
+                               return pollres;
+#else                                                  /* !ENABLE_GSS */
+                               /* unreachable */
+                               goto error_return;
+#endif                                                 /* ENABLE_GSS */
+                       }
+
                        /*
                         * Handle authentication exchange: wait for postmaster messages
                         * and respond as necessary.
@@ -2997,6 +3182,26 @@ keep_going:                                              /* We will come back to here until there is
                                        /* Check to see if we should mention pgpassfile */
                                        pgpassfileWarning(conn);
 
+#ifdef ENABLE_GSS
+
+                                       /*
+                                        * If gssencmode is "prefer" and we're using GSSAPI, retry
+                                        * without it.
+                                        */
+                                       if (conn->gssenc && conn->gssencmode[0] == 'p')
+                                       {
+                                               OM_uint32       minor;
+
+                                               /* postmaster expects us to drop the connection */
+                                               conn->try_gss = false;
+                                               conn->gssenc = false;
+                                               gss_delete_sec_context(&minor, &conn->gctx, NULL);
+                                               pqDropConnection(conn, true);
+                                               conn->status = CONNECTION_NEEDED;
+                                               goto keep_going;
+                                       }
+#endif
+
 #ifdef USE_SSL
 
                                        /*
@@ -3564,6 +3769,9 @@ makeEmptyPGconn(void)
        conn->verbosity = PQERRORS_DEFAULT;
        conn->show_context = PQSHOW_CONTEXT_ERRORS;
        conn->sock = PGINVALID_SOCKET;
+#ifdef ENABLE_GSS
+       conn->try_gss = true;
+#endif
 
        /*
         * We try to send at least 8K at a time, which is the usual size of pipe
@@ -3695,10 +3903,28 @@ freePGconn(PGconn *conn)
                free(conn->requirepeer);
        if (conn->connip)
                free(conn->connip);
+       if (conn->gssencmode)
+               free(conn->gssencmode);
 #if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
        if (conn->krbsrvname)
                free(conn->krbsrvname);
 #endif
+#ifdef ENABLE_GSS
+       if (conn->gcred != GSS_C_NO_CREDENTIAL)
+       {
+               OM_uint32       minor;
+
+               gss_release_cred(&minor, &conn->gcred);
+               conn->gcred = GSS_C_NO_CREDENTIAL;
+       }
+       if (conn->gctx)
+       {
+               OM_uint32       minor;
+
+               gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER);
+               conn->gctx = NULL;
+       }
+#endif
 #if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
        if (conn->gsslib)
                free(conn->gsslib);
diff --git a/src/interfaces/libpq/fe-gssapi-common.c b/src/interfaces/libpq/fe-gssapi-common.c
new file mode 100644 (file)
index 0000000..3192f91
--- /dev/null
@@ -0,0 +1,130 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-gssapi-common.c
+ *     The front-end (client) GSSAPI common code
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *      src/interfaces/libpq/fe-gssapi-common.c
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "fe-gssapi-common.h"
+
+#include "libpq-int.h"
+#include "pqexpbuffer.h"
+
+/*
+ * Fetch all errors of a specific type and append to "str".
+ */
+static void
+pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
+                                OM_uint32 stat, int type)
+{
+       OM_uint32       lmin_s;
+       gss_buffer_desc lmsg;
+       OM_uint32       msg_ctx = 0;
+
+       do
+       {
+               gss_display_status(&lmin_s, stat, type,
+                                                  GSS_C_NO_OID, &msg_ctx, &lmsg);
+               appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value);
+               gss_release_buffer(&lmin_s, &lmsg);
+       } while (msg_ctx);
+}
+
+/*
+ * GSSAPI errors contain two parts; put both into conn->errorMessage.
+ */
+void
+pg_GSS_error(const char *mprefix, PGconn *conn,
+                        OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+       resetPQExpBuffer(&conn->errorMessage);
+
+       /* Fetch major error codes */
+       pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE);
+
+       /* Add the minor codes as well */
+       pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE);
+}
+
+/*
+ * Check if we can acquire credentials at all (and yield them if so).
+ */
+bool
+pg_GSS_have_ccache(gss_cred_id_t *cred_out)
+{
+       OM_uint32       major,
+                               minor;
+       gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
+
+       major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET,
+                                                        GSS_C_INITIATE, &cred, NULL, NULL);
+       if (major != GSS_S_COMPLETE)
+       {
+               *cred_out = NULL;
+               return false;
+       }
+       *cred_out = cred;
+       return true;
+}
+
+/*
+ * Try to load service name for a connection
+ */
+int
+pg_GSS_load_servicename(PGconn *conn)
+{
+       OM_uint32       maj_stat,
+                               min_stat;
+       int                     maxlen;
+       gss_buffer_desc temp_gbuf;
+       char       *host;
+
+       if (conn->gtarg_nam != NULL)
+               /* Already taken care of - move along */
+               return STATUS_OK;
+
+       host = PQhost(conn);
+       if (!(host && host[0] != '\0'))
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("host name must be specified\n"));
+               return STATUS_ERROR;
+       }
+
+       /*
+        * Import service principal name so the proper ticket can be acquired by
+        * the GSSAPI system.
+        */
+       maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2;
+       temp_gbuf.value = (char *) malloc(maxlen);
+       if (!temp_gbuf.value)
+       {
+               printfPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("out of memory\n"));
+               return STATUS_ERROR;
+       }
+       snprintf(temp_gbuf.value, maxlen, "%s@%s",
+                        conn->krbsrvname, host);
+       temp_gbuf.length = strlen(temp_gbuf.value);
+
+       maj_stat = gss_import_name(&min_stat, &temp_gbuf,
+                                                          GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam);
+       free(temp_gbuf.value);
+
+       if (maj_stat != GSS_S_COMPLETE)
+       {
+               pg_GSS_error(libpq_gettext("GSSAPI name import error"),
+                                        conn,
+                                        maj_stat, min_stat);
+               return STATUS_ERROR;
+       }
+       return STATUS_OK;
+}
diff --git a/src/interfaces/libpq/fe-gssapi-common.h b/src/interfaces/libpq/fe-gssapi-common.h
new file mode 100644 (file)
index 0000000..b429e79
--- /dev/null
@@ -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 (file)
index 0000000..ea1c1cd
--- /dev/null
@@ -0,0 +1,635 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-secure-gssapi.c
+ *   The front-end (client) encryption support for GSSAPI
+ *
+ * Portions Copyright (c) 2016-2018, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *  src/interfaces/libpq/fe-secure-gssapi.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "libpq-int.h"
+#include "fe-gssapi-common.h"
+
+#include "port/pg_bswap.h"
+
+/*
+ * Require encryption support, as well as mutual authentication and
+ * tamperproofing measures.
+ */
+#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \
+       GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG
+
+/*
+ * We use fixed-size buffers for handling the encryption/decryption
+ * which are larger than PQComm's buffer will typically be to minimize
+ * the times where we have to make multiple packets and therefore sets
+ * of recv/send calls for a single read/write call to us.
+ *
+ * NOTE: The client and server have to agree on the max packet size,
+ * because we have to pass an entire packet to GSSAPI at a time and we
+ * don't want the other side to send arbitrairly huge packets as we
+ * would have to allocate memory for them to then pass them to GSSAPI.
+ */
+#define PQ_GSS_SEND_BUFFER_SIZE 16384
+#define PQ_GSS_RECV_BUFFER_SIZE 16384
+
+/* PqGSSSendBuffer is for *encrypted* data */
+static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE];
+static int     PqGSSSendPointer;       /* Next index to store a byte in
+                                                                * PqGSSSendBuffer */
+static int     PqGSSSendStart;         /* Next index to send a byte in
+                                                                * PqGSSSendBuffer */
+
+/* PqGSSRecvBuffer is for *encrypted* data */
+static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int     PqGSSRecvPointer;       /* Next index to read a byte from
+                                                                * PqGSSRecvBuffer */
+static int     PqGSSRecvLength;        /* End of data available in PqGSSRecvBuffer */
+
+/* PqGSSResultBuffer is for *unencrypted* data */
+static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE];
+static int     PqGSSResultPointer; /* Next index to read a byte from
+                                                                * PqGSSResultBuffer */
+static int     PqGSSResultLength;      /* End of data available in PqGSSResultBuffer */
+
+uint32         max_packet_size;        /* Maximum size we can encrypt and fit the
+                                                                * results into our output buffer */
+
+/*
+ * Write len bytes of data from ptr along a GSSAPI-encrypted connection.  Note
+ * that the connection must be already set up for GSSAPI encryption (i.e.,
+ * GSSAPI transport negotiation is complete).  Returns len when all data has
+ * been written; retry when errno is EWOULDBLOCK or similar with the same
+ * values of ptr and len.  On non-socket failures, will log an error message.
+ */
+ssize_t
+pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
+{
+       gss_buffer_desc input,
+                               output = GSS_C_EMPTY_BUFFER;
+       OM_uint32       major,
+                               minor;
+       ssize_t         ret = -1;
+       size_t          bytes_to_encrypt = len;
+       size_t          bytes_encrypted = 0;
+
+       /*
+        * Loop through encrypting data and sending it out until
+        * pqsecure_raw_write() complains (which would likely mean that the socket
+        * is non-blocking and the requested send() would block, or there was some
+        * kind of actual error) and then return.
+        */
+       while (bytes_to_encrypt || PqGSSSendPointer)
+       {
+               int                     conf = 0;
+               uint32          netlen;
+
+               /*
+                * Check if we have data in the encrypted output buffer that needs to
+                * be sent, and if so, try to send it.  If we aren't able to, return
+                * that back up to the caller.
+                */
+               if (PqGSSSendPointer)
+               {
+                       ssize_t         ret;
+                       ssize_t         amount = PqGSSSendPointer - PqGSSSendStart;
+
+                       ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
+                       if (ret < 0)
+                       {
+                               /*
+                                * If we encrypted some data and it's in our output buffer,
+                                * but send() is saying that we would block, then tell the
+                                * client how far we got with encrypting the data so that they
+                                * can call us again with whatever is left, at which point we
+                                * will try to send the remaining encrypted data first and
+                                * then move on to encrypting the rest of the data.
+                                */
+                               if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
+                                       return bytes_encrypted;
+                               else
+                                       return ret;
+                       }
+
+                       /*
+                        * Partial write, move forward that far in our buffer and try
+                        * again
+                        */
+                       if (ret != amount)
+                       {
+                               PqGSSSendStart += ret;
+                               continue;
+                       }
+
+                       /* All encrypted data was sent, our buffer is empty now. */
+                       PqGSSSendPointer = PqGSSSendStart = 0;
+               }
+
+               /*
+                * Check if there are any bytes left to encrypt.  If not, we're done.
+                */
+               if (!bytes_to_encrypt)
+                       return bytes_encrypted;
+
+               /*
+                * Check how much we are being asked to send, if it's too much, then
+                * we will have to loop and possibly be called multiple times to get
+                * through all the data.
+                */
+               if (bytes_to_encrypt > max_packet_size)
+                       input.length = max_packet_size;
+               else
+                       input.length = bytes_to_encrypt;
+
+               input.value = (char *) ptr + bytes_encrypted;
+
+               output.value = NULL;
+               output.length = 0;
+
+               /* Create the next encrypted packet */
+               major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+                                                &input, &conf, &output);
+               if (major != GSS_S_COMPLETE)
+               {
+                       pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
+                       goto cleanup;
+               }
+               else if (conf == 0)
+               {
+                       printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+                                                                                                                                "GSSAPI did not provide confidentiality\n"));
+                       goto cleanup;
+               }
+
+               if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+               {
+                       printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+                                                                                                                                "GSSAPI attempt to send oversize packet\n"));
+                       goto cleanup;
+               }
+
+               bytes_encrypted += input.length;
+               bytes_to_encrypt -= input.length;
+
+               /* 4 network-order bytes of length, then payload */
+               netlen = htonl(output.length);
+               memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32));
+               PqGSSSendPointer += sizeof(uint32);
+
+               memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+               PqGSSSendPointer += output.length;
+       }
+
+       ret = bytes_encrypted;
+
+cleanup:
+       if (output.value != NULL)
+               gss_release_buffer(&minor, &output);
+       return ret;
+}
+
+/*
+ * Read up to len bytes of data into ptr from a GSSAPI-encrypted connection.
+ * Note that GSSAPI transport must already have been negotiated.  Returns the
+ * number of bytes read into ptr; otherwise, returns -1.  Retry with the same
+ * ptr and len when errno is EWOULDBLOCK or similar.
+ */
+ssize_t
+pg_GSS_read(PGconn *conn, void *ptr, size_t len)
+{
+       OM_uint32       major,
+                               minor;
+       gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
+                               output = GSS_C_EMPTY_BUFFER;
+       ssize_t         ret = 0;
+       size_t          bytes_to_return = len;
+       size_t          bytes_returned = 0;
+
+       /*
+        * The goal here is to read an incoming encrypted packet, one at a time,
+        * decrypt it into our out buffer, returning to the caller what they asked
+        * for, and then saving anything else for the next call.
+        *
+        * We get a read request, we look if we have cleartext bytes available
+        * and, if so, copy those to the result, and then we try to decrypt the
+        * next packet.
+        *
+        * We should not try to decrypt the next packet until the read buffer is
+        * completely empty.
+        *
+        * If the caller asks for more bytes than one decrypted packet, then we
+        * should try to return all bytes asked for.
+        */
+       while (bytes_to_return)
+       {
+               int                     conf = 0;
+
+               /* Check if we have data in our buffer that we can return immediately */
+               if (PqGSSResultPointer < PqGSSResultLength)
+               {
+                       int                     bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
+                       int                     bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
+
+                       /*
+                        * Copy the data from our output buffer into the caller's buffer,
+                        * at the point where we last left off filling their buffer
+                        */
+                       memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultPointer, bytes_to_copy);
+                       PqGSSResultPointer += bytes_to_copy;
+                       bytes_to_return -= bytes_to_copy;
+                       bytes_returned += bytes_to_copy;
+
+                       /* Check if our result buffer is now empty and, if so, reset */
+                       if (PqGSSResultPointer == PqGSSResultLength)
+                               PqGSSResultPointer = PqGSSResultLength = 0;
+
+                       continue;
+               }
+
+               /*
+                * At this point, our output buffer should be empty with more bytes
+                * being requested to be read.  We are now ready to load the next
+                * packet and decrypt it (entirely) into our buffer.
+                *
+                * If we get a partial read back while trying to read a packet off the
+                * wire then we return back what bytes we were able to return and wait
+                * to be called again, until we get a full packet to decrypt.
+                */
+
+               /* Check if we got a partial read just trying to get the length */
+               if (PqGSSRecvLength < sizeof(uint32))
+               {
+                       /* Try to get whatever of the length we still need */
+                       ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
+                                                                       sizeof(uint32) - PqGSSRecvLength);
+                       if (ret < 0)
+                               return bytes_returned ? bytes_returned : ret;
+
+                       PqGSSRecvLength += ret;
+                       if (PqGSSRecvLength < sizeof(uint32))
+                               return bytes_returned;
+               }
+
+               /*
+                * We should have the whole length at this point, so pull it out and
+                * then read whatever we have left of the packet
+                */
+               input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+
+               /* Check for over-length packet */
+               if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
+               {
+                       printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+                                                                                                                                "GSSAPI did not provide confidentiality\n"));
+                       ret = -1;
+                       goto cleanup;
+               }
+
+               /*
+                * Read as much of the packet as we are able to on this call into
+                * wherever we left off from the last time we were called.
+                */
+               ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
+                                                               input.length - (PqGSSRecvLength - sizeof(uint32)));
+               if (ret < 0)
+                       return bytes_returned ? bytes_returned : ret;
+
+               /*
+                * If we got less than the rest of the packet then we need to return
+                * and be called again.
+                */
+               PqGSSRecvLength += ret;
+               if (PqGSSRecvLength - sizeof(uint32) < input.length)
+                       return bytes_returned ? bytes_returned : -1;
+
+               /*
+                * We now have the full packet and we can perform the decryption and
+                * refill our output buffer, then loop back up to pass that back to
+                * the user.
+                */
+               output.value = NULL;
+               output.length = 0;
+               input.value = PqGSSRecvBuffer + sizeof(uint32);
+
+               major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
+               if (major != GSS_S_COMPLETE)
+               {
+                       pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
+                                                major, minor);
+                       ret = -1;
+                       goto cleanup;
+               }
+               else if (conf == 0)
+               {
+                       printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
+                                                                                                                                "GSSAPI did not provide confidentiality\n"));
+                       ret = -1;
+                       goto cleanup;
+               }
+
+               memcpy(PqGSSResultBuffer, output.value, output.length);
+               PqGSSResultLength = output.length;
+
+               /* Our buffer is now empty, reset it */
+               PqGSSRecvPointer = PqGSSRecvLength = 0;
+
+               gss_release_buffer(&minor, &output);
+       }
+
+       ret = bytes_returned;
+
+cleanup:
+       if (output.value != NULL)
+               gss_release_buffer(&minor, &output);
+       return ret;
+}
+
+/*
+ * Simple wrapper for reading from pqsecure_raw_read.
+ *
+ * This takes the same arguments as pqsecure_raw_read, plus an output parameter
+ * to return the number of bytes read.  This handles if blocking would occur and
+ * if we detect EOF on the connection.
+ */
+static PostgresPollingStatusType
+gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
+{
+       *ret = pqsecure_raw_read(conn, recv_buffer, length);
+       if (*ret < 0 && errno == EWOULDBLOCK)
+               return PGRES_POLLING_READING;
+       else if (*ret < 0)
+               return PGRES_POLLING_FAILED;
+
+       /* Check for EOF */
+       if (*ret == 0)
+       {
+               int                     result = pqReadReady(conn);
+
+               if (result < 0)
+                       return PGRES_POLLING_FAILED;
+
+               if (!result)
+                       return PGRES_POLLING_READING;
+
+               *ret = pqsecure_raw_read(conn, recv_buffer, length);
+               if (*ret == 0)
+                       return PGRES_POLLING_FAILED;
+       }
+
+       return PGRES_POLLING_OK;
+}
+
+/*
+ * Negotiate GSSAPI transport for a connection.  When complete, returns
+ * PGRES_POLLING_OK.  Will return PGRES_POLLING_READING or
+ * PGRES_POLLING_WRITING as appropriate whenever it would block, and
+ * PGRES_POLLING_FAILED if transport could not be negotiated.
+ */
+PostgresPollingStatusType
+pqsecure_open_gss(PGconn *conn)
+{
+       static int      first = 1;
+       ssize_t         ret;
+       OM_uint32       major,
+                               minor;
+       uint32          netlen;
+       PostgresPollingStatusType result;
+       gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
+                               output = GSS_C_EMPTY_BUFFER;
+
+       /* Check for data that needs to be written */
+       if (first)
+       {
+               PqGSSSendPointer = PqGSSSendStart = PqGSSRecvPointer = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
+               first = 0;
+       }
+
+       /*
+        * Check if we have anything to send from a prior call and if so, send it.
+        */
+       if (PqGSSSendPointer)
+       {
+               ssize_t         amount = PqGSSSendPointer - PqGSSSendStart;
+
+               ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
+               if (ret < 0 && errno == EWOULDBLOCK)
+                       return PGRES_POLLING_WRITING;
+
+               if (ret != amount)
+               {
+                       PqGSSSendStart += amount;
+                       return PGRES_POLLING_WRITING;
+               }
+
+               PqGSSSendPointer = PqGSSSendStart = 0;
+       }
+
+       /*
+        * Client sends first, and sending creates a context, therefore this will
+        * be false the first time through, and then when we get called again we
+        * will check for incoming data.
+        */
+       if (conn->gctx)
+       {
+               /* Process any incoming data we might have */
+
+               /* See if we are still trying to get the length */
+               if (PqGSSRecvLength < sizeof(uint32))
+               {
+                       /* Attempt to get the length first */
+                       result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, sizeof(uint32) - PqGSSRecvLength, &ret);
+                       if (result != PGRES_POLLING_OK)
+                               return result;
+
+                       PqGSSRecvLength += ret;
+
+                       if (PqGSSRecvLength < sizeof(uint32))
+                               return PGRES_POLLING_READING;
+               }
+
+               /*
+                * Check if we got an error packet
+                *
+                * This is safe to do because we shouldn't ever get a packet over 8192
+                * and therefore the actual length bytes, being that they are in
+                * network byte order, for any real packet will be two zero bytes.
+                */
+               if (PqGSSRecvBuffer[0] == 'E')
+               {
+                       /*
+                        * For an error message, the length is after the E, so read one
+                        * more byte to get the full length
+                        */
+                       result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, 1, &ret);
+                       if (result != PGRES_POLLING_OK)
+                               return result;
+
+                       PqGSSRecvLength += ret;
+
+                       if (PqGSSRecvLength < 1 + sizeof(uint32))
+                               return PGRES_POLLING_READING;
+
+                       input.length = ntohl(*(uint32 *) PqGSSRecvBuffer + 1);
+                       if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32) - 1)
+                       {
+                               printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Over-size error packet sent by the server."));
+                               return PGRES_POLLING_FAILED;
+                       }
+
+                       result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, input.length - PqGSSRecvLength - 1 - sizeof(uint32), &ret);
+                       if (result != PGRES_POLLING_OK)
+                               return result;
+
+                       PqGSSRecvLength += ret;
+
+                       if (PqGSSRecvLength < 1 + sizeof(uint32) + input.length)
+                               return PGRES_POLLING_READING;
+
+                       printfPQExpBuffer(&conn->errorMessage,
+                                                         libpq_gettext("Server error: %s"),
+                                                         PqGSSRecvBuffer + 1 + sizeof(int32));
+
+                       return PGRES_POLLING_FAILED;
+               }
+
+               /*
+                * We should have the whole length at this point, so pull it out and
+                * then read whatever we have left of the packet
+                */
+
+               /* Get the length and check for over-length packet */
+               input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
+               if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
+               {
+                       printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Over-size GSSAPI packet sent by the server: %ld"), input.length);
+                       return PGRES_POLLING_FAILED;
+               }
+
+               /*
+                * Read as much of the packet as we are able to on this call into
+                * wherever we left off from the last time we were called.
+                */
+               result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
+                                                 input.length - (PqGSSRecvLength - sizeof(uint32)), &ret);
+               if (result != PGRES_POLLING_OK)
+                       return result;
+
+               PqGSSRecvLength += ret;
+
+               /*
+                * If we got less than the rest of the packet then we need to return
+                * and be called again.
+                */
+               if (PqGSSRecvLength - sizeof(uint32) < input.length)
+                       return PGRES_POLLING_READING;
+
+               input.value = PqGSSRecvBuffer + sizeof(uint32);
+       }
+
+       /* Load the service name (no-op if already done */
+       ret = pg_GSS_load_servicename(conn);
+       if (ret != STATUS_OK)
+               return PGRES_POLLING_FAILED;
+
+       /*
+        * Call GSS init context, either with an empty input, or with a complete
+        * packet from the server.
+        */
+       major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx,
+                                                                conn->gtarg_nam, GSS_C_NO_OID,
+                                                                GSS_REQUIRED_FLAGS, 0, 0, &input, NULL,
+                                                                &output, NULL, NULL);
+
+       /* GSS Init Sec Context uses the whole packet, so clear it */
+       PqGSSRecvPointer = PqGSSRecvLength = 0;
+
+       if (GSS_ERROR(major))
+       {
+               pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
+                                        conn, major, minor);
+               return PGRES_POLLING_FAILED;
+       }
+       else if (output.length == 0)
+       {
+               /*
+                * We're done - hooray!  Kind of gross, but we need to disable SSL
+                * here so that we don't accidentally tunnel one over the other.
+                */
+#ifdef USE_SSL
+               conn->allow_ssl_try = false;
+#endif
+               gss_release_cred(&minor, &conn->gcred);
+               conn->gcred = GSS_C_NO_CREDENTIAL;
+               conn->gssenc = true;
+
+               /*
+                * Determine the max packet size which will fit in our buffer, after
+                * accounting for the length
+                */
+               major = gss_wrap_size_limit(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
+                                                                       PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
+
+               if (GSS_ERROR(major))
+                       pg_GSS_error(libpq_gettext("GSSAPI size check error"), conn,
+                                                major, minor);
+
+               return PGRES_POLLING_OK;
+       }
+
+       /* Must have output.length > 0 */
+       if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
+       {
+               pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
+                                        conn, major, minor);
+               return PGRES_POLLING_FAILED;
+       }
+
+       /* Queue the token for writing */
+       netlen = htonl(output.length);
+
+       memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
+       PqGSSSendPointer += sizeof(uint32);
+
+       memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
+       PqGSSSendPointer += output.length;
+
+       gss_release_buffer(&minor, &output);
+
+       /* Asked to be called again to write data */
+       return PGRES_POLLING_WRITING;
+}
+
+/*
+ * GSSAPI Information functions.
+ */
+
+/*
+ * Return the GSSAPI Context itself.
+ */
+void *
+PQgetgssctx(PGconn *conn)
+{
+       if (!conn)
+               return NULL;
+
+       return conn->gctx;
+}
+
+/*
+ * Return true if GSSAPI encryption is in use.
+ */
+int
+PQgssEncInUse(PGconn *conn)
+{
+       if (!conn || !conn->gctx)
+               return 0;
+
+       return conn->gssenc;
+}
index 4658e27caa68f4de6133fefcdd95d37772f3b8e4..b8191b4c8f8a8f5314745b0fd97de8150a300d06 100644 (file)
@@ -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);
@@ -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);
@@ -420,6 +434,23 @@ PQsslAttributeNames(PGconn *conn)
 }
 #endif                                                 /* USE_SSL */
 
+/* Dummy version of GSSAPI information functions, when built without GSS support */
+#ifndef ENABLE_GSS
+
+void *
+PQgetgssctx(PGconn *conn)
+{
+       return NULL;
+}
+
+int
+PQgssEncInUse(PGconn *conn)
+{
+       return 0;
+}
+
+#endif                                                 /* ENABLE_GSS */
+
 
 #if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
 
index 97bc98b1f3cb05d67e9b2192c773f8abc7ca2be3..27047ddd1f3b5089bd6259d5461b6143c7cadc06 100644 (file)
@@ -65,8 +65,9 @@ typedef enum
        CONNECTION_NEEDED,                      /* Internal state: connect() needed */
        CONNECTION_CHECK_WRITABLE,      /* Check if we could make a writable
                                                                 * connection. */
-       CONNECTION_CONSUME                      /* Wait for any pending message and consume
+       CONNECTION_CONSUME,                     /* Wait for any pending message and consume
                                                                 * them. */
+       CONNECTION_GSS_STARTUP          /* Negotiating GSSAPI. */
 } ConnStatusType;
 
 typedef enum
@@ -346,6 +347,12 @@ extern void PQinitSSL(int do_init);
 /* More detailed way to tell libpq whether it needs to initialize OpenSSL */
 extern void PQinitOpenSSL(int do_ssl, int do_crypto);
 
+/* Return true if GSSAPI encryption is in use */
+extern int PQgssEncInUse(PGconn *conn);
+
+/* Returns GSSAPI context if GSSAPI is in use */
+extern void *PQgetgssctx(PGconn *conn);
+
 /* Set verbosity for PQerrorMessage and PQresultErrorMessage */
 extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);
 
index dbe0f7e5c0bb73415061d130eacc9d0eacd504f2..84222f2c7ca0082818bcd30ac2c8d2f1a8c1371e 100644 (file)
@@ -480,9 +480,15 @@ struct pg_conn
 #endif                                                 /* USE_OPENSSL */
 #endif                                                 /* USE_SSL */
 
+       char       *gssencmode;         /* GSS mode (require,prefer,disable) */
 #ifdef ENABLE_GSS
        gss_ctx_id_t gctx;                      /* GSS context */
        gss_name_t      gtarg_nam;              /* GSS target name */
+
+       /* The following are encryption-only */
+       bool            try_gss;                /* GSS attempting permitted */
+       bool            gssenc;                 /* GSS encryption is usable */
+       gss_cred_id_t gcred;            /* GSS credential temp storage. */
 #endif
 
 #ifdef ENABLE_SSPI
@@ -749,6 +755,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
                                                                                                int *names_examined,
                                                                                                char **first_name);
 
+/* === GSSAPI === */
+
+#ifdef ENABLE_GSS
+
+/*
+ * Establish a GSSAPI-encrypted connection.
+ */
+extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn);
+
+/*
+ * Read and write functions for GSSAPI-encrypted connections, with internal
+ * buffering to handle nonblocking sockets.
+ */
+extern ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len);
+extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
+#endif
+
 /* === miscellaneous macros === */
 
 /*
diff --git a/src/test/kerberos/t/002_enc.pl b/src/test/kerberos/t/002_enc.pl
new file mode 100644 (file)
index 0000000..1a7dc30
--- /dev/null
@@ -0,0 +1,197 @@
+use strict;
+use warnings;
+use TestLib;
+use PostgresNode;
+use Test::More;
+use File::Path 'remove_tree';
+
+if ($ENV{with_gssapi} eq 'yes')
+{
+       plan tests => 5;
+}
+else
+{
+       plan skip_all => 'GSSAPI/Kerberos not supported by this build';
+}
+
+my ($krb5_bin_dir, $krb5_sbin_dir);
+
+if ($^O eq 'darwin')
+{
+       $krb5_bin_dir  = '/usr/local/opt/krb5/bin';
+       $krb5_sbin_dir = '/usr/local/opt/krb5/sbin';
+}
+elsif ($^O eq 'freebsd')
+{
+       $krb5_bin_dir  = '/usr/local/bin';
+       $krb5_sbin_dir = '/usr/local/sbin';
+}
+elsif ($^O eq 'linux')
+{
+       $krb5_sbin_dir = '/usr/sbin';
+}
+
+my $krb5_config  = 'krb5-config';
+my $kinit        = 'kinit';
+my $kdb5_util    = 'kdb5_util';
+my $kadmin_local = 'kadmin.local';
+my $krb5kdc      = 'krb5kdc';
+
+if ($krb5_bin_dir && -d $krb5_bin_dir)
+{
+       $krb5_config = $krb5_bin_dir . '/' . $krb5_config;
+       $kinit       = $krb5_bin_dir . '/' . $kinit;
+}
+if ($krb5_sbin_dir && -d $krb5_sbin_dir)
+{
+       $kdb5_util    = $krb5_sbin_dir . '/' . $kdb5_util;
+       $kadmin_local = $krb5_sbin_dir . '/' . $kadmin_local;
+       $krb5kdc      = $krb5_sbin_dir . '/' . $krb5kdc;
+}
+
+my $host     = 'auth-test-localhost.postgresql.example.com';
+my $hostaddr = '127.0.0.1';
+my $realm = 'EXAMPLE.COM';
+
+my $krb5_conf   = "${TestLib::tmp_check}/krb5.conf";
+my $kdc_conf    = "${TestLib::tmp_check}/kdc.conf";
+my $krb5_log    = "${TestLib::tmp_check}/krb5libs.log";
+my $kdc_log     = "${TestLib::tmp_check}/krb5kdc.log";
+my $kdc_port    = int(rand() * 16384) + 49152;
+my $kdc_datadir = "${TestLib::tmp_check}/krb5kdc";
+my $kdc_pidfile = "${TestLib::tmp_check}/krb5kdc.pid";
+my $keytab      = "${TestLib::tmp_check}/krb5.keytab";
+
+note "setting up Kerberos";
+
+my ($stdout, $krb5_version);
+run_log [ $krb5_config, '--version' ], '>', \$stdout
+  or BAIL_OUT("could not execute krb5-config");
+BAIL_OUT("Heimdal is not supported") if $stdout =~ m/heimdal/;
+$stdout =~ m/Kerberos 5 release ([0-9]+\.[0-9]+)/
+  or BAIL_OUT("could not get Kerberos version");
+$krb5_version = $1;
+
+append_to_file(
+       $krb5_conf,
+       qq![logging]
+default = FILE:$krb5_log
+kdc = FILE:$kdc_log
+
+[libdefaults]
+default_realm = $realm
+
+[realms]
+$realm = {
+    kdc = $hostaddr:$kdc_port
+}!);
+
+append_to_file(
+       $kdc_conf,
+       qq![kdcdefaults]
+!);
+
+# For new-enough versions of krb5, use the _listen settings rather
+# than the _ports settings so that we can bind to localhost only.
+if ($krb5_version >= 1.15)
+{
+       append_to_file(
+               $kdc_conf,
+               qq!kdc_listen = $hostaddr:$kdc_port
+kdc_tcp_listen = $hostaddr:$kdc_port
+!);
+}
+else
+{
+       append_to_file(
+               $kdc_conf,
+               qq!kdc_ports = $kdc_port
+kdc_tcp_ports = $kdc_port
+!);
+}
+append_to_file(
+       $kdc_conf,
+       qq!
+[realms]
+$realm = {
+    database_name = $kdc_datadir/principal
+    admin_keytab = FILE:$kdc_datadir/kadm5.keytab
+    acl_file = $kdc_datadir/kadm5.acl
+    key_stash_file = $kdc_datadir/_k5.$realm
+}!);
+
+remove_tree $kdc_datadir;
+mkdir $kdc_datadir or die;
+
+$ENV{'KRB5_CONFIG'}      = $krb5_conf;
+$ENV{'KRB5_KDC_PROFILE'} = $kdc_conf;
+
+my $service_principal = "$ENV{with_krb_srvnam}/$host";
+
+system_or_bail $kdb5_util, 'create', '-s', '-P', 'secret0';
+
+my $test1_password = 'secret1';
+system_or_bail $kadmin_local, '-q', "addprinc -pw $test1_password test1";
+
+system_or_bail $kadmin_local, '-q', "addprinc -randkey $service_principal";
+system_or_bail $kadmin_local, '-q', "ktadd -k $keytab $service_principal";
+
+system_or_bail $krb5kdc, '-P', $kdc_pidfile;
+
+END
+{
+       kill 'INT', `cat $kdc_pidfile` if -f $kdc_pidfile;
+}
+
+note "setting up PostgreSQL instance";
+
+my $node = get_new_node('node');
+$node->init;
+$node->append_conf('postgresql.conf', "listen_addresses = 'localhost'");
+$node->append_conf('postgresql.conf', "krb_server_keyfile = '$keytab'");
+$node->start;
+
+$node->safe_psql('postgres', 'CREATE USER test1;');
+
+note "running tests";
+
+sub test_access
+{
+       my ($node, $gssencmode, $expected_res, $test_name) = @_;
+
+       my $res = $node->psql(
+               "postgres",
+               "SELECT 1",
+               extra_params => [
+                       "-d",
+                       $node->connstr("postgres") . " host=$host hostaddr=$hostaddr gssencmode=$gssencmode",
+                       "-U", "test1",
+               ]);
+       is($res, $expected_res, $test_name);
+       return;
+}
+
+unlink($node->data_dir . "/pg_ident.conf");
+$node->append_conf("pg_ident.conf", 'mymap /^(.*)@EXAMPLE.COM$ \1');
+run_log [ $kinit, 'test1' ], \$test1_password or BAIL_OUT($?);
+
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf',
+                                  qq{hostgssenc all all $hostaddr/32 gss map=mymap});
+$node->restart;
+test_access($node, "require", 0, "GSS-encrypted access");
+test_access($node, "disable", 2, "GSS encryption disabled");
+
+unlink($node->data_dir . "/pg_hba.conf");
+$node->append_conf("pg_hba.conf", qq{hostgssenc all all $hostaddr/32 trust});
+$node->restart;
+test_access($node, "require", 0, "GSS encryption without auth");
+
+unlink($node->data_dir . "/pg_hba.conf");
+$node->append_conf("pg_hba.conf",
+                                  qq{hostnogssenc all all localhost gss map=mymap});
+$node->restart;
+test_access($node, "prefer", 0, "GSS unencrypted fallback");
+
+# Check that the client can prevent fallback.
+test_access($node, "require", 2, "GSS unencrypted fallback prevention");
index dae772d33a2206e0715a81c6144850cc032e9439..bf7fca54ee42597a4ae5a34173a38c900bf3dbc3 100644 (file)
@@ -1739,7 +1739,7 @@ pg_stat_activity| SELECT s.datid,
     s.backend_xmin,
     s.query,
     s.backend_type
-   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn)
+   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc)
      LEFT JOIN pg_database d ON ((s.datid = d.oid)))
      LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
 pg_stat_all_indexes| SELECT c.oid AS relid,
@@ -1830,6 +1830,11 @@ pg_stat_database_conflicts| SELECT d.oid AS datid,
     pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin,
     pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock
    FROM pg_database d;
+pg_stat_gssapi| SELECT s.pid,
+    s.gss_auth AS gss_authenticated,
+    s.gss_princ AS principal,
+    s.gss_enc AS encrypted
+   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc);
 pg_stat_progress_cluster| SELECT s.pid,
     s.datid,
     d.datname,
@@ -1926,7 +1931,7 @@ pg_stat_replication| SELECT s.pid,
     w.sync_priority,
     w.sync_state,
     w.reply_time
-   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn)
+   FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc)
      JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, sync_state, reply_time) ON ((s.pid = w.pid)))
      LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
 pg_stat_ssl| SELECT s.pid,
@@ -1938,7 +1943,7 @@ pg_stat_ssl| SELECT s.pid,
     s.ssl_client_dn AS client_dn,
     s.ssl_client_serial AS client_serial,
     s.ssl_issuer_dn AS issuer_dn
-   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn);
+   FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, wait_event_type, wait_event, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, ssl_issuer_dn, gss_auth, gss_princ, gss_enc);
 pg_stat_subscription| SELECT su.oid AS subid,
     su.subname,
     st.pid,
index c0c6ff6751f46e37afef0aec9378be1d21fbff2b..f466df88477dda63861da61fb56af2596827a5a7 100644 (file)
@@ -194,6 +194,11 @@ sub mkvcbuild
                $postgres->RemoveFile('src/backend/libpq/be-secure-common.c');
                $postgres->RemoveFile('src/backend/libpq/be-secure-openssl.c');
        }
+       if (!$solution->{options}->{gss})
+       {
+               $postgres->RemoveFile('src/backend/libpq/be-gssapi-common.c');
+               $postgres->RemoveFile('src/backend/libpq/be-secure-gssapi.c');
+       }
 
        my $snowball = $solution->AddProject('dict_snowball', 'dll', '',
                'src/backend/snowball');
@@ -254,6 +259,11 @@ sub mkvcbuild
                $libpq->RemoveFile('src/interfaces/libpq/fe-secure-common.c');
                $libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
        }
+       if (!$solution->{options}->{gss})
+       {
+               $libpq->RemoveFile('src/interfaces/libpq/fe-gssapi-common.c');
+               $libpq->RemoveFile('src/interfaces/libpq/fe-secure-gssapi.c');
+       }
 
        my $libpqwalreceiver =
          $solution->AddProject('libpqwalreceiver', 'dll', '',