Thread: Kerberos v5 support

Kerberos v5 support

From
Garrett Wollman
Date:
I thought I sent this back in September, but I can't find anything in
the mailing-list archives, so I am assuming it fell into a black hole.

Enclosed please find a set of patches, relative to 7.0.2, which will
result in Kerberos v5 support which both compiles and works (as in,
I've successfully authenticated as a remote client).

Our pg_hba.conf file then looks like:

local        all                                           trust
host         all         0.0.0.0        0.0.0.0         krb5

However, that `trust' is tempered by changes to the startup scripts
(not included here) which force the local-domain socket to mode 600,
thereby ensuring that all normal clients connect via an authenticated
network connection.

You can see from some of the comments that I'd like this to be made
stronger in a number of ways.  This patch set simply gets pgsql up to
the minimum acceptable level of security for our environment and
application.

My original message follows.

------------------------------------------------------------------------

The enclosed patches fix the Kerberos v5 support in libpq and the
backend.  It was totally broken before, now it's still broken but
works.  (That is to say: before, it didn't work, and now it does work
but doesn't provide the level of security it should.  Still better
than plaintext passwords.)  So far as I can tell, the original code
was written for an early beta version, and rotted severely in the
intervening years.  We actually need authentication to work, which is
why I'm doing this now.

A few notes:

- See the comment near pg_an_to_ln() about one part of the brokenness.

- As implemented, this code will not work over PF_LOCAL sockets.

- Some things don't work correctly in the absence of a `local all
trust' line in pg_hba.conf, and PGUSER needs to be set in order for
*that* to work.

- E2E encryption would really be preferable.  It looks fairly easy to
do in the fe->be side of the protocol, but it's not obviously possible
for the other direction.  In any event, I wanted to confine my changes
to the smallest number of source files, so I didn't make any effort to
implement this.  Either way, a protocol change is required.

-GAWollman

# This is a shell archive.  Save it in a file, remove anything before
# this line, and then unpack it by entering "sh file".  Note, it may
# create directories; files and directories will be owned by you and
# have default permissions.
#
# This archive contains:
#
#    patch-be
#    patch-bf
#    patch-bg
#    patch-bh
#
echo x - patch-be
sed 's/^X//' >patch-be << 'END-of-patch-be'
X--- backend/libpq/auth.c.orig    Wed Apr 12 13:15:13 2000
X+++ backend/libpq/auth.c    Wed Sep 27 23:33:17 2000
X@@ -142,7 +142,4 @@
X
X #ifdef KRB5
X-/* This needs to be ifdef'd out because krb5.h doesn't exist.  This needs
X-   to be fixed.
X-*/
X /*----------------------------------------------------------------
X  * MIT Kerberos authentication system - protocol version 5
X@@ -150,5 +147,15 @@
X  */
X
X-#include "krb5/krb5.h"
X+#include "krb5.h"
X+#ifndef PG_KRB_SRVNAM
X+#define    PG_KRB_SRVNAM    "pgsql"
X+#endif
X+#ifndef PG_KRB_KEYTAB
X+#define PG_KRB_KEYTAB    "FILE:/etc/keytab.pgsql"
X+#endif
X+
X+static krb5_context    mycontext;
X+static int        mycontext_inited;
X+static krb5_keytab    keytab;
X
X /*
X@@ -156,12 +163,11 @@
X  *                  name
X  *
X- * XXX Assumes that the first aname component is the user name.  This is NOT
X- *       necessarily so, since an aname can actually be something out of your
X- *       worst X.400 nightmare, like
X- *          ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
X- *       Note that the MIT an_to_ln code does the same thing if you don't
X- *       provide an aname mapping database...it may be a better idea to use
X- *       krb5_an_to_ln, except that it punts if multiple components are found,
X- *       and we can't afford to punt.
X+ * XXX - this is totally broken (and potentially insecure on the server side).
X+ * The correct mechanism is to use the entire principal name, and make
X+ * the server do a table lookup to discover the mapping.
X+ * (In the protocol as it stands, if user jrl@A.EXAMPLE.COM authenticates to
X+ * a server in the B.EXAMPLE.COM realm, the server will accept him as
X+ * local-user `jrl' regardless of whether or not jrl@A.EXAMPLE.COM is
X+ * the same user as jrl@B.EXAMPLE.COM.)
X  */
X static char *
X@@ -200,68 +206,117 @@
X pg_krb5_recvauth(Port *port)
X {
X-    char        servbuf[MAXHOSTNAMELEN + 1 +
X-                                    sizeof(PG_KRB_SRVNAM)];
X-    char       *hostp,
X-               *kusername = (char *) NULL;
X+    char        hostbuf[MAXHOSTNAMELEN + 1];
X+    char        *kusername = (char *) NULL;
X     krb5_error_code code;
X-    krb5_principal client,
X-                server;
X-    krb5_address sender_addr;
X-    krb5_rdreq_key_proc keyproc = (krb5_rdreq_key_proc) NULL;
X-    krb5_pointer keyprocarg = (krb5_pointer) NULL;
X+    krb5_principal    server;
X+    krb5_auth_context authctx;
X+    krb5_authenticator *them;
X+
X+    if (!mycontext_inited)
X+    {
X+        code = krb5_init_context(&mycontext);
X+        if (code)
X+        {
X+            snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X+                 "pg_krb5_recvauth: krb5_init_context: %s\n",
X+                 error_message(code));
X+            fputs(PQerrormsg, stderr);
X+            pqdebug("%s", PQerrormsg);
X+            return STATUS_ERROR;
X+        }
X+        if (strcmp(PG_KRB_KEYTAB, "default") == 0)
X+        {
X+            code = krb5_kt_default(mycontext, &keytab);
X+        }
X+        else
X+        {
X+            code = krb5_kt_resolve(mycontext, PG_KRB_KEYTAB,
X+                           &keytab);
X+        }
X+        if (code)
X+        {
X+            snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X+                 "pg_krb5_recvauth: keytab %s: %s\n",
X+                 PG_KRB_KEYTAB,
X+                 error_message(code));
X+            fputs(PQerrormsg, stderr);
X+            pqdebug("%s", PQerrormsg);
X+            return STATUS_ERROR;
X+        }
X+        mycontext_inited = 1;
X+    }
X
X-    /*
X-     * Set up server side -- since we have no ticket file to make this
X-     * easy, we construct our own name and parse it.  See note on
X-     * canonicalization above.
X-     */
X-    strcpy(servbuf, PG_KRB_SRVNAM);
X-    *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
X-    if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
X-        strcpy(hostp, "localhost");
X-    if (hostp = strchr(hostp, '.'))
X-        *hostp = '\0';
X-    if (code = krb5_parse_name(servbuf, &server))
X+    code = krb5_auth_con_init(mycontext, &authctx);
X+    if (code)
X+    {
X+        snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X+             "pg_krb5_recvauth: krb5_auth_con_init: %s\n",
X+             error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        pqdebug("%s", PQerrormsg);
X+        return STATUS_ERROR;
X+    }
X+
X+    if (gethostname(hostbuf, MAXHOSTNAMELEN) < 0)
X     {
X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X-        "pg_krb5_recvauth: Kerberos error %d in krb5_parse_name\n", code);
X-        com_err("pg_krb5_recvauth", code, "in krb5_parse_name");
X+             "pg_krb5_recvauth: gethostname: %s\n",
X+             strerror(errno));
X+        fputs(PQerrormsg, stderr);
X+        pqdebug("%s", PQerrormsg);
X+        krb5_auth_con_free(mycontext, authctx);
X         return STATUS_ERROR;
X     }
X
X+    code = krb5_sname_to_principal(mycontext, hostbuf, PG_KRB_SRVNAM,
X+                       KRB5_NT_SRV_HST, &server);
X+    if (code)
X+    {
X+        snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X+             "pg_krb5_recvauth: krb5_sname_to_principal: %s\n",
X+             error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        pqdebug("%s", PQerrormsg);
X+        krb5_auth_con_free(mycontext, authctx);
X+        return STATUS_ERROR;
X+    }
X+
X     /*
X      * krb5_sendauth needs this to verify the address in the client
X      * authenticator.
X      */
X-    sender_addr.addrtype = port->raddr.in.sin_family;
X-    sender_addr.length = sizeof(port->raddr.in.sin_addr);
X-    sender_addr.contents = (krb5_octet *) & (port->raddr.in.sin_addr);
X-
X-    if (strcmp(PG_KRB_SRVTAB, ""))
X+#define    ALL    (KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR | \
X+         KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)
X+    code = krb5_auth_con_genaddrs(mycontext, authctx, port->sock, ALL);
X+#undef ALL
X+
X+    code = krb5_recvauth(mycontext, &authctx, (krb5_pointer)&port->sock,
X+                 PG_KRB5_VERSION, server, 0, keytab,
X+                 (krb5_ticket **)0);
X+    if (code)
X     {
X-        keyproc = krb5_kt_read_service_key;
X-        keyprocarg = PG_KRB_SRVTAB;
X+        snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X+             "pg_krb5_recvauth: krb5_recvauth: %s\n",
X+             error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        pqdebug("%s", PQerrormsg);
X+        krb5_free_principal(mycontext, server);
X+        krb5_auth_con_free(mycontext, authctx);
X+        return STATUS_ERROR;
X     }
X+    krb5_free_principal(mycontext, server);
X
X-    if (code = krb5_recvauth((krb5_pointer) & port->sock,
X-                             PG_KRB5_VERSION,
X-                             server,
X-                             &sender_addr,
X-                             (krb5_pointer) NULL,
X-                             keyproc,
X-                             keyprocarg,
X-                             (char *) NULL,
X-                             (krb5_int32 *) NULL,
X-                             &client,
X-                             (krb5_ticket **) NULL,
X-                             (krb5_authenticator **) NULL))
X+    code = krb5_auth_con_getauthenticator(mycontext, authctx, &them);
X+    if (code)
X     {
X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X-         "pg_krb5_recvauth: Kerberos error %d in krb5_recvauth\n", code);
X-        com_err("pg_krb5_recvauth", code, "in krb5_recvauth");
X-        krb5_free_principal(server);
X+         "pg_krb5_recvauth: getauthenticator: %s\n",
X+             error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        pqdebug("%s", PQerrormsg);
X+        krb5_free_principal(mycontext, server);
X+        krb5_auth_con_free(mycontext, authctx);
X         return STATUS_ERROR;
X     }
X-    krb5_free_principal(server);
X
X     /*
X@@ -270,13 +325,18 @@
X      * postmaster startup packet.
X      */
X-    if ((code = krb5_unparse_name(client, &kusername)))
X+    if ((code = krb5_unparse_name(mycontext, them->client, &kusername)))
X     {
X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X-                 "pg_krb5_recvauth: Kerberos error %d in krb5_unparse_name\n", code);
X-        com_err("pg_krb5_recvauth", code, "in krb5_unparse_name");
X-        krb5_free_principal(client);
X+             "pg_krb5_recvauth: krb5_unparse_name: %s\n",
X+             error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        pqdebug("%s", PQerrormsg);
X+        krb5_free_authenticator(mycontext, them);
X+        krb5_auth_con_free(mycontext, authctx);
X         return STATUS_ERROR;
X     }
X-    krb5_free_principal(client);
X+    krb5_free_authenticator(mycontext, them);
X+    krb5_auth_con_free(mycontext, authctx);
X+
X     if (!kusername)
X     {
X@@ -288,5 +348,5 @@
X     }
X     kusername = pg_an_to_ln(kusername);
X-    if (strncmp(username, kusername, SM_USER))
X+    if (strncmp(port->user, kusername, SM_USER))
X     {
X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
X@@ -294,8 +354,8 @@
X         fputs(PQerrormsg, stderr);
X         pqdebug("%s", PQerrormsg);
X-        pfree(kusername);
X+        free(kusername);
X         return STATUS_ERROR;
X     }
X-    pfree(kusername);
X+    free(kusername);
X     return STATUS_OK;
X }
END-of-patch-be
echo x - patch-bf
sed 's/^X//' >patch-bf << 'END-of-patch-bf'
Xdiff -ru2 old/configure.in configure.in
X--- old/configure.in    Wed May 24 18:43:59 2000
X+++ configure.in    Tue Sep 26 22:39:46 2000
X@@ -369,4 +369,55 @@
X export USE_ODBC
X
X+AC_ARG_WITH(
X+    krb5,
X+    [  --with-krb5[=PREFIX]    build Kerberos 5 authentication support ],
X+    [
X+    case "$withval" in
X+    y | ye | yes)    USE_KRB5=true;;
X+    n | no)    USE_KRB5=false;;
X+    *)    USE_KRB5=true
X+        KRB5_INCS="-I$withval/include $KRB5_INCS"
X+        KRB5_LIBS="-L$withval/lib $KRB5_LIBS";;
X+    esac
X+    ],
X+    [ USE_KRB5=false ]
X+)
X+
X+AC_ARG_WITH(
X+    krb-service-name,
X+    [  --with-krb-service-name=NAME authenticate as Kerberos principal NAME ],
X+    [  KRB_SRVNAM="$withval" ],
X+    [  unset KRB_SRVNAM ]
X+)
X+
X+AC_ARG_WITH(
X+    krb-keytab,
X+    [  --with-krb-keytab=FILE:FILENAME use Kerberos v5 keytab FILENAME ],
X+    [  KRB_KEYTAB="$withval" ],
X+    [  unset KRB_KEYTAB ]
X+)
X+
X+export USE_KRB5
X+export KRB5_INCS
X+export KRB5_LIBS
X+
X+AC_ARG_WITH(
X+    openssl,
X+    [  --with-openssl[=PREFIX] build OpenSSL transport support ],
X+    [
X+    case "$withval" in
X+    y | ye | yes)    USE_OPENSSL=true;;
X+    n | no)    USE_OPENSSL=false;;
X+    *)    USE_OPENSSL=true
X+        OPENSSL_INCS="-I$withval $OPENSSL_INCS"
X+        OPENSSL_LIBS="-L$withval $OPENSSL_LIBS -lopenssl -lcrypto";;
X+    esac
X+    ],
X+    [ USE_OPENSSL=false ]
X+)
X+export USE_OPENSSL
X+export OPENSSL_INCS
X+export OPENSSL_LIBS
X+
X AC_MSG_CHECKING(setproctitle)
X AC_ARG_WITH(
X@@ -509,4 +560,6 @@
X AC_SUBST(USE_ODBC)
X AC_SUBST(MULTIBYTE)
X+AC_SUBST(USE_KRB5)
X+AC_SUBST(USE_OPENSSL)
X
X dnl Check for C++ support (allow override if needed)
X@@ -1312,4 +1365,50 @@
X     CPPFLAGS="$ice_save_CPPFLAGS"
X     LDFLAGS="$ice_save_LDFLAGS"
X+fi
X+
X+dnl
X+dnl User requested Kerberos support in libpq; see if the required
X+dnl header files and libraries are actually there.
X+dnl
X+if $USE_KRB5; then
X+    AC_CHECKING(if Kerberos 5 environment is complete)
X+    OCFLAGS="$CFLAGS"
X+    OCPPFLAGS="$CPPFLAGS"
X+    OLIBS="$LIBS"
X+    if test "$KRB5_INCS"; then
X+        CFLAGS="$CFLAGS $KRB5_INCS"
X+        CPPFLAGS="$CPPFLAGS $KRB5_INCS"
X+    fi
X+    if test "$KRB5_LIBS"; then
X+        LIBS="$LIBS $KRB5_LIBS"
X+    fi
X+    AC_CHECK_HEADERS(krb5.h)
X+    AC_CHECK_LIB(krb5, krb5_recvauth, , , [-lk5crypto -lcom_err])
X+    if test "$ac_cv_header_krb5_h" = "no"; then
X+        AC_MSG_WARN([krb5.h not found; disabling Kerberos v5 support])
X+        USE_KRB5=false
X+        CFLAGS="$OCFLAGS"
X+        LIBS="$OLIBS"
X+    else
X+        if test "$ac_cv_lib_krb5" = "no"; then
X+            AC_MSG_WARN([libkrb5 not found; disabling Kerberos v5 support])
X+            USE_KRB5=false
X+        else
X+        fi
X+    fi
X+    if $USE_KRB5; then
X+        CFLAGS="$CFLAGS -DKRB5 -DUSE_KRB5"
X+        LIBS="$LIBS -lkrb5 -lk5crypto -lcom_err"
X+        if test "$KRB_SRVNAM"; then
X+            CFLAGS="$CFLAGS -DPG_KRB_SRVNAM=\\\"$KRB_SRVNAM\\\""
X+        fi
X+        if test "$KRB_KEYTAB"; then
X+            CFLAGS="$CFLAGS -DPG_KRB_KEYTAB=\\\"$KRB_KEYTAB\\\""
X+        fi
X+    else
X+        CFLAGS="$OCFLAGS"
X+        CPPFLAGS="$OCPPFLAGS"
X+        LIBS="$OLIBS"
X+    fi
X fi
X
END-of-patch-bf
echo x - patch-bg
sed 's/^X//' >patch-bg << 'END-of-patch-bg'
Xdiff -ru2 old/interfaces/libpq/Makefile.in interfaces/libpq/Makefile.in
X--- old/interfaces/libpq/Makefile.in    Thu Apr 13 20:42:06 2000
X+++ interfaces/libpq/Makefile.in    Tue Sep 26 23:50:45 2000
X@@ -34,4 +34,8 @@
X # make sure it gets included in shared libpq.
X SHLIB_LINK+= $(findstring -lcrypt,$(LIBS))
X+SHLIB_LINK+= $(filter -L%,$(LIBS))
X+SHLIB_LINK+= $(findstring -lkrb5,$(LIBS))
X+SHLIB_LINK+= $(findstring -lk5crypto,$(LIBS))
X+SHLIB_LINK+= $(findstring -lcom_err,$(LIBS))
X
X # Shared library stuff, also default 'all' target
END-of-patch-bg
echo x - patch-bh
sed 's/^X//' >patch-bh << 'END-of-patch-bh'
X--- interfaces/libpq/fe-auth.c.orig    Wed Apr 12 13:17:13 2000
X+++ interfaces/libpq/fe-auth.c    Wed Sep 27 23:15:30 2000
X@@ -235,5 +235,9 @@
X  */
X
X-#include "krb5/krb5.h"
X+#include <fcntl.h>
X+#include "krb5.h"
X+#ifndef PG_KRB_SRVNAM
X+#define    PG_KRB_SRVNAM    "pgsql"
X+#endif
X
X /*
X@@ -241,12 +245,11 @@
X  *                  name
X  *
X- * XXX Assumes that the first aname component is the user name.  This is NOT
X- *       necessarily so, since an aname can actually be something out of your
X- *       worst X.400 nightmare, like
X- *          ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
X- *       Note that the MIT an_to_ln code does the same thing if you don't
X- *       provide an aname mapping database...it may be a better idea to use
X- *       krb5_an_to_ln, except that it punts if multiple components are found,
X- *       and we can't afford to punt.
X+ * XXX - this is totally broken (and potentially insecure on the server side).
X+ * The correct mechanism is to use the entire principal name, and make
X+ * the server do a table lookup to discover the mapping.
X+ * (In the protocol as it stands, if user jrl@A.EXAMPLE.COM authenticates to
X+ * a server in the B.EXAMPLE.COM realm, the server will accept him as
X+ * local-user `jrl' regardless of whether or not jrl@A.EXAMPLE.COM is
X+ * the same user as jrl@B.EXAMPLE.COM.)
X  */
X static char *
X@@ -260,63 +263,37 @@
X }
X
X+static krb5_context mycontext;
X+static int mycontext_inited;
X+static krb5_ccache ccache;
X
X-/*
X- * pg_krb5_init -- initialization performed before any Kerberos calls are made
X- *
X- * With v5, we can no longer set the ticket (credential cache) file name;
X- * we now have to provide a file handle for the open (well, "resolved")
X- * ticket file everywhere.
X- *
X- */
X-static int
X-            krb5_ccache
X+static krb5_error_code
X pg_krb5_init(void)
X {
X     krb5_error_code code;
X-    char       *realm,
X-               *defname;
X-    char        tktbuf[MAXPGPATH];
X-    static krb5_ccache ccache = (krb5_ccache) NULL;
X
X-    if (ccache)
X-        return ccache;
X-
X-    /*
X-     * If the user set PGREALM, then we use a ticket file with a special
X-     * name: <usual-ticket-file-name>@<PGREALM-value>
X-     */
X-    if (!(defname = krb5_cc_default_name()))
X-    {
X-        (void) sprintf(PQerrormsg,
X-                       "pg_krb5_init: krb5_cc_default_name failed\n");
X-        return (krb5_ccache) NULL;
X-    }
X-    strcpy(tktbuf, defname);
X-    if (realm = getenv("PGREALM"))
X-    {
X-        strcat(tktbuf, "@");
X-        strcat(tktbuf, realm);
X-    }
X+    if (mycontext_inited)
X+        return 0;
X
X-    if (code = krb5_cc_resolve(tktbuf, &ccache))
X-    {
X-        (void) sprintf(PQerrormsg,
X-           "pg_krb5_init: Kerberos error %d in krb5_cc_resolve\n", code);
X-        com_err("pg_krb5_init", code, "in krb5_cc_resolve");
X-        return (krb5_ccache) NULL;
X-    }
X-    return ccache;
X+    code = krb5_init_context(&mycontext);
X+    if (code)
X+        return code;
X+
X+    code = krb5_cc_default(mycontext, &ccache);
X+    if (code)
X+        return code;
X+    mycontext_inited = 1;
X+    return 0;
X }
X
X /*
X  * pg_krb5_authname -- returns a pointer to static space containing whatever
X- *                       name the user has authenticated to the system
X+ *                name the user has authenticated to the system
X  *
X  * We obtain this information by digging around in the ticket file.
X+ * XXX see comments above
X  */
X-static const char *
X-pg_krb5_authname(const char *PQerrormsg)
X+static char *
X+pg_krb5_authname(char *PQerrormsg)
X {
X-    krb5_ccache ccache;
X     krb5_principal principal;
X     krb5_error_code code;
X@@ -326,22 +303,33 @@
X         return authname;
X
X-    ccache = pg_krb5_init();    /* don't free this */
X+    code = pg_krb5_init();
X+    if (code)
X+    {
X+        sprintf(PQerrormsg, "pg_krb5_init: %s\n",
X+            error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        return (char *)NULL;
X+    }
X
X-    if (code = krb5_cc_get_principal(ccache, &principal))
X+    code = krb5_cc_get_principal(mycontext, ccache, &principal);
X+    if (code)
X     {
X         (void) sprintf(PQerrormsg,
X-                       "pg_krb5_authname: Kerberos error %d in krb5_cc_get_principal\n", code);
X-        com_err("pg_krb5_authname", code, "in krb5_cc_get_principal");
X+                   "pg_krb5_authname: krb5_cc_get_principal: %s\n",
X+                   error_message(code));
X+        fputs(PQerrormsg, stderr);
X         return (char *) NULL;
X     }
X-    if (code = krb5_unparse_name(principal, &authname))
X+    code = krb5_unparse_name(mycontext, principal, &authname);
X+    if (code)
X     {
X         (void) sprintf(PQerrormsg,
X-                       "pg_krb5_authname: Kerberos error %d in krb5_unparse_name\n", code);
X-        com_err("pg_krb5_authname", code, "in krb5_unparse_name");
X-        krb5_free_principal(principal);
X+                   "pg_krb5_authname: krb5_unparse_name: %s\n",
X+                   error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        krb5_free_principal(mycontext, principal);
X         return (char *) NULL;
X     }
X-    krb5_free_principal(principal);
X+    krb5_free_principal(mycontext, principal);
X     return pg_an_to_ln(authname);
X }
X@@ -349,107 +337,125 @@
X /*
X  * pg_krb5_sendauth -- client routine to send authentication information to
X- *                       the server
X- *
X- * This routine does not do mutual authentication, nor does it return enough
X- * information to do encrypted connections.  But then, if we want to do
X- * encrypted connections, we'll have to redesign the whole RPC mechanism
X- * anyway.
X+ *               the server
X  *
X- * Server hostnames are canonicalized v4-style, i.e., all domain suffixes
X- * are simply chopped off.    Hence, we are assuming that you've entered your
X- * server instances as
X- *        <value-of-PG_KRB_SRVNAM>/<canonicalized-hostname>
X- * in the PGREALM (or local) database.    This is probably a bad assumption.
X  */
X static int
X-pg_krb5_sendauth(const char *PQerrormsg, int sock,
X-                 struct sockaddr_in * laddr,
X-                 struct sockaddr_in * raddr,
X-                 const char *hostname)
X+pg_krb5_sendauth(char *PQerrormsg, int sock,
X+         struct sockaddr_in * laddr,
X+         struct sockaddr_in * raddr,
X+         const char *hostname)
X {
X-    char        servbuf[MAXHOSTNAMELEN + 1 +
X-                                    sizeof(PG_KRB_SRVNAM)];
X-    const char *hostp;
X-    const char *realm;
X+    int sflags;
X+    char servbuf[MAXHOSTNAMELEN + 1];
X     krb5_error_code code;
X-    krb5_principal client,
X-                server;
X-    krb5_ccache ccache;
X+    krb5_principal server;
X     krb5_error *error = (krb5_error *) NULL;
X+    krb5_auth_context authctx;
X
X-    ccache = pg_krb5_init();    /* don't free this */
X+    code = pg_krb5_init();
X+    if (code)
X+    {
X+        sprintf(PQerrormsg, "pg_krb5_init: %s\n",
X+            error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        return STATUS_ERROR;
X+    }
X
X-    /*
X-     * set up client -- this is easy, we can get it out of the ticket
X-     * file.
X-     */
X-    if (code = krb5_cc_get_principal(ccache, &client))
X+    code = krb5_auth_con_init(mycontext, &authctx);
X+    if (code)
X     {
X-        (void) sprintf(PQerrormsg,
X-                       "pg_krb5_sendauth: Kerberos error %d in krb5_cc_get_principal\n", code);
X-        com_err("pg_krb5_sendauth", code, "in krb5_cc_get_principal");
X+        sprintf(PQerrormsg,
X+            "pg_krb5_recvauth: krb5_auth_con_init: %s\n",
X+            error_message(code));
X+        fputs(PQerrormsg, stderr);
X         return STATUS_ERROR;
X     }
X
X-    /*
X-     * set up server -- canonicalize as described above
X-     */
X-    strcpy(servbuf, PG_KRB_SRVNAM);
X-    *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
X-    if (hostname || *hostname)
X-        strncpy(++hostp, hostname, MAXHOSTNAMELEN);
X-    else
X-    {
X-        if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
X-            strcpy(hostp, "localhost");
X-    }
X-    if (hostp = strchr(hostp, '.'))
X-        *hostp = '\0';
X-    if (realm = getenv("PGREALM"))
X+    if (hostname == 0 || *hostname == '\0')
X     {
X-        strcat(servbuf, "@");
X-        strcat(servbuf, realm);
X+        if (gethostname(servbuf, MAXHOSTNAMELEN) < 0)
X+        {
X+            sprintf(PQerrormsg,
X+                 "pg_krb5_sendauth: gethostname: %s\n",
X+                 strerror(errno));
X+            fputs(PQerrormsg, stderr);
X+            krb5_auth_con_free(mycontext, authctx);
X+            return STATUS_ERROR;
X+        }
X+        hostname = servbuf;
X     }
X-    if (code = krb5_parse_name(servbuf, &server))
X+
X+    code = krb5_sname_to_principal(mycontext, hostname, PG_KRB_SRVNAM,
X+                       KRB5_NT_SRV_HST, &server);
X+
X+    if (code)
X     {
X-        (void) sprintf(PQerrormsg,
X-        "pg_krb5_sendauth: Kerberos error %d in krb5_parse_name\n", code);
X-        com_err("pg_krb5_sendauth", code, "in krb5_parse_name");
X-        krb5_free_principal(client);
X+        sprintf(PQerrormsg,
X+            "pg_krb5_sendauth: krb5_sname_to_principal: %s\n",
X+            error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        krb5_auth_con_free(mycontext, authctx);
X+        return STATUS_ERROR;
X+    }
X+
X+#define    ALL    (KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR | \
X+         KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)
X+    code = krb5_auth_con_genaddrs(mycontext, authctx, sock, ALL);
X+#undef ALL
X+    if (code)
X+    {
X+        sprintf(PQerrormsg,
X+            "pg_krb5_sendauth: krb5_auth_con_genaddrs: %s\n",
X+            error_message(code));
X+        fputs(PQerrormsg, stderr);
X+        krb5_free_principal(mycontext, server);
X+        krb5_auth_con_free(mycontext, authctx);
X         return STATUS_ERROR;
X     }
X
X+
X+    /*
X+     * krb5_sendauth does not appreciate getting a non-blocking file
X+     * descriptor.  So, we set it to blocking mode and then reset
X+     * it afterwards.
X+     */
X+    sflags = fcntl(sock, F_GETFL, 0);
X+    fcntl(sock, F_SETFL, sflags & ~O_NONBLOCK);
X+
X     /*
X      * The only thing we want back from krb5_sendauth is an error status
X-     * and any error messages.
X+     * and any error messages.  If we cared, we could get the session
X+     * key(s) from the auth context and stick them somewhere to encrypt
X+     * the whole data stream.  However, this would mean a major change
X+     * in the protocol, and I'm not prepared to do that right now.
X+     * (It should work to encrypt the session using SSL, if a bit of a
X+     * muchness.)
X      */
X-    if (code = krb5_sendauth((krb5_pointer) & sock,
X-                             PG_KRB5_VERSION,
X-                             client,
X-                             server,
X-                             (krb5_flags) 0,
X-                             (krb5_checksum *) NULL,
X-                             (krb5_creds *) NULL,
X-                             ccache,
X-                             (krb5_int32 *) NULL,
X-                             (krb5_keyblock **) NULL,
X-                             &error,
X-                             (krb5_ap_rep_enc_part **) NULL))
X+    code = krb5_sendauth(mycontext, &authctx, (krb5_pointer) &sock,
X+                 PG_KRB5_VERSION, (krb5_principal) NULL,
X+                 server, AP_OPTS_MUTUAL_REQUIRED,
X+                 (krb5_data *) NULL, (krb5_creds *) NULL,
X+                 ccache, &error, (krb5_ap_rep_enc_part **) NULL,
X+                 (krb5_creds **) NULL);
X+    fcntl(sock, F_SETFL, sflags);
X+    if (code)
X     {
X         if ((code == KRB5_SENDAUTH_REJECTED) && error)
X         {
X-            (void) sprintf(PQerrormsg,
X-                  "pg_krb5_sendauth: authentication rejected: \"%*s\"\n",
X-                           error->text.length, error->text.data);
X+            sprintf(PQerrormsg,
X+                "pg_krb5_sendauth: authentication rejected: \"%.*s\"\n",
X+                error->text.length, error->text.data);
X         }
X         else
X         {
X-            (void) sprintf(PQerrormsg,
X-                           "pg_krb5_sendauth: Kerberos error %d in krb5_sendauth\n", code);
X-            com_err("pg_krb5_sendauth", code, "in krb5_sendauth");
X+            sprintf(PQerrormsg,
X+                "pg_krb5_sendauth: krb5_sendauth: %s\n",
X+                error_message(code));
X         }
X     }
X-    krb5_free_principal(client);
X-    krb5_free_principal(server);
X+    krb5_free_principal(mycontext, server);
X+    if (error != 0)
X+        krb5_free_error(mycontext, error);
X+    krb5_auth_con_free(mycontext, authctx);
X     return code ? STATUS_ERROR : STATUS_OK;
X }
END-of-patch-bh
exit



Re: Kerberos v5 support

From
Bruce Momjian
Date:
I have applied some kerberos changes to the current snapshot a few
months ago.  Can you grab that and let me know what you would like
changed?  Thanks.


> I thought I sent this back in September, but I can't find anything in
> the mailing-list archives, so I am assuming it fell into a black hole.
>
> Enclosed please find a set of patches, relative to 7.0.2, which will
> result in Kerberos v5 support which both compiles and works (as in,
> I've successfully authenticated as a remote client).
>
> Our pg_hba.conf file then looks like:
>
> local        all                                           trust
> host         all         0.0.0.0        0.0.0.0         krb5
>
> However, that `trust' is tempered by changes to the startup scripts
> (not included here) which force the local-domain socket to mode 600,
> thereby ensuring that all normal clients connect via an authenticated
> network connection.
>
> You can see from some of the comments that I'd like this to be made
> stronger in a number of ways.  This patch set simply gets pgsql up to
> the minimum acceptable level of security for our environment and
> application.
>
> My original message follows.
>
> ------------------------------------------------------------------------
>
> The enclosed patches fix the Kerberos v5 support in libpq and the
> backend.  It was totally broken before, now it's still broken but
> works.  (That is to say: before, it didn't work, and now it does work
> but doesn't provide the level of security it should.  Still better
> than plaintext passwords.)  So far as I can tell, the original code
> was written for an early beta version, and rotted severely in the
> intervening years.  We actually need authentication to work, which is
> why I'm doing this now.
>
> A few notes:
>
> - See the comment near pg_an_to_ln() about one part of the brokenness.
>
> - As implemented, this code will not work over PF_LOCAL sockets.
>
> - Some things don't work correctly in the absence of a `local all
> trust' line in pg_hba.conf, and PGUSER needs to be set in order for
> *that* to work.
>
> - E2E encryption would really be preferable.  It looks fairly easy to
> do in the fe->be side of the protocol, but it's not obviously possible
> for the other direction.  In any event, I wanted to confine my changes
> to the smallest number of source files, so I didn't make any effort to
> implement this.  Either way, a protocol change is required.
>
> -GAWollman
>
> # This is a shell archive.  Save it in a file, remove anything before
> # this line, and then unpack it by entering "sh file".  Note, it may
> # create directories; files and directories will be owned by you and
> # have default permissions.
> #
> # This archive contains:
> #
> #    patch-be
> #    patch-bf
> #    patch-bg
> #    patch-bh
> #
> echo x - patch-be
> sed 's/^X//' >patch-be << 'END-of-patch-be'
> X--- backend/libpq/auth.c.orig    Wed Apr 12 13:15:13 2000
> X+++ backend/libpq/auth.c    Wed Sep 27 23:33:17 2000
> X@@ -142,7 +142,4 @@
> X
> X #ifdef KRB5
> X-/* This needs to be ifdef'd out because krb5.h doesn't exist.  This needs
> X-   to be fixed.
> X-*/
> X /*----------------------------------------------------------------
> X  * MIT Kerberos authentication system - protocol version 5
> X@@ -150,5 +147,15 @@
> X  */
> X
> X-#include "krb5/krb5.h"
> X+#include "krb5.h"
> X+#ifndef PG_KRB_SRVNAM
> X+#define    PG_KRB_SRVNAM    "pgsql"
> X+#endif
> X+#ifndef PG_KRB_KEYTAB
> X+#define PG_KRB_KEYTAB    "FILE:/etc/keytab.pgsql"
> X+#endif
> X+
> X+static krb5_context    mycontext;
> X+static int        mycontext_inited;
> X+static krb5_keytab    keytab;
> X
> X /*
> X@@ -156,12 +163,11 @@
> X  *                  name
> X  *
> X- * XXX Assumes that the first aname component is the user name.  This is NOT
> X- *       necessarily so, since an aname can actually be something out of your
> X- *       worst X.400 nightmare, like
> X- *          ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
> X- *       Note that the MIT an_to_ln code does the same thing if you don't
> X- *       provide an aname mapping database...it may be a better idea to use
> X- *       krb5_an_to_ln, except that it punts if multiple components are found,
> X- *       and we can't afford to punt.
> X+ * XXX - this is totally broken (and potentially insecure on the server side).
> X+ * The correct mechanism is to use the entire principal name, and make
> X+ * the server do a table lookup to discover the mapping.
> X+ * (In the protocol as it stands, if user jrl@A.EXAMPLE.COM authenticates to
> X+ * a server in the B.EXAMPLE.COM realm, the server will accept him as
> X+ * local-user `jrl' regardless of whether or not jrl@A.EXAMPLE.COM is
> X+ * the same user as jrl@B.EXAMPLE.COM.)
> X  */
> X static char *
> X@@ -200,68 +206,117 @@
> X pg_krb5_recvauth(Port *port)
> X {
> X-    char        servbuf[MAXHOSTNAMELEN + 1 +
> X-                                    sizeof(PG_KRB_SRVNAM)];
> X-    char       *hostp,
> X-               *kusername = (char *) NULL;
> X+    char        hostbuf[MAXHOSTNAMELEN + 1];
> X+    char        *kusername = (char *) NULL;
> X     krb5_error_code code;
> X-    krb5_principal client,
> X-                server;
> X-    krb5_address sender_addr;
> X-    krb5_rdreq_key_proc keyproc = (krb5_rdreq_key_proc) NULL;
> X-    krb5_pointer keyprocarg = (krb5_pointer) NULL;
> X+    krb5_principal    server;
> X+    krb5_auth_context authctx;
> X+    krb5_authenticator *them;
> X+
> X+    if (!mycontext_inited)
> X+    {
> X+        code = krb5_init_context(&mycontext);
> X+        if (code)
> X+        {
> X+            snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X+                 "pg_krb5_recvauth: krb5_init_context: %s\n",
> X+                 error_message(code));
> X+            fputs(PQerrormsg, stderr);
> X+            pqdebug("%s", PQerrormsg);
> X+            return STATUS_ERROR;
> X+        }
> X+        if (strcmp(PG_KRB_KEYTAB, "default") == 0)
> X+        {
> X+            code = krb5_kt_default(mycontext, &keytab);
> X+        }
> X+        else
> X+        {
> X+            code = krb5_kt_resolve(mycontext, PG_KRB_KEYTAB,
> X+                           &keytab);
> X+        }
> X+        if (code)
> X+        {
> X+            snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X+                 "pg_krb5_recvauth: keytab %s: %s\n",
> X+                 PG_KRB_KEYTAB,
> X+                 error_message(code));
> X+            fputs(PQerrormsg, stderr);
> X+            pqdebug("%s", PQerrormsg);
> X+            return STATUS_ERROR;
> X+        }
> X+        mycontext_inited = 1;
> X+    }
> X
> X-    /*
> X-     * Set up server side -- since we have no ticket file to make this
> X-     * easy, we construct our own name and parse it.  See note on
> X-     * canonicalization above.
> X-     */
> X-    strcpy(servbuf, PG_KRB_SRVNAM);
> X-    *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
> X-    if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
> X-        strcpy(hostp, "localhost");
> X-    if (hostp = strchr(hostp, '.'))
> X-        *hostp = '\0';
> X-    if (code = krb5_parse_name(servbuf, &server))
> X+    code = krb5_auth_con_init(mycontext, &authctx);
> X+    if (code)
> X+    {
> X+        snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X+             "pg_krb5_recvauth: krb5_auth_con_init: %s\n",
> X+             error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        pqdebug("%s", PQerrormsg);
> X+        return STATUS_ERROR;
> X+    }
> X+
> X+    if (gethostname(hostbuf, MAXHOSTNAMELEN) < 0)
> X     {
> X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X-        "pg_krb5_recvauth: Kerberos error %d in krb5_parse_name\n", code);
> X-        com_err("pg_krb5_recvauth", code, "in krb5_parse_name");
> X+             "pg_krb5_recvauth: gethostname: %s\n",
> X+             strerror(errno));
> X+        fputs(PQerrormsg, stderr);
> X+        pqdebug("%s", PQerrormsg);
> X+        krb5_auth_con_free(mycontext, authctx);
> X         return STATUS_ERROR;
> X     }
> X
> X+    code = krb5_sname_to_principal(mycontext, hostbuf, PG_KRB_SRVNAM,
> X+                       KRB5_NT_SRV_HST, &server);
> X+    if (code)
> X+    {
> X+        snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X+             "pg_krb5_recvauth: krb5_sname_to_principal: %s\n",
> X+             error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        pqdebug("%s", PQerrormsg);
> X+        krb5_auth_con_free(mycontext, authctx);
> X+        return STATUS_ERROR;
> X+    }
> X+
> X     /*
> X      * krb5_sendauth needs this to verify the address in the client
> X      * authenticator.
> X      */
> X-    sender_addr.addrtype = port->raddr.in.sin_family;
> X-    sender_addr.length = sizeof(port->raddr.in.sin_addr);
> X-    sender_addr.contents = (krb5_octet *) & (port->raddr.in.sin_addr);
> X-
> X-    if (strcmp(PG_KRB_SRVTAB, ""))
> X+#define    ALL    (KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR | \
> X+         KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)
> X+    code = krb5_auth_con_genaddrs(mycontext, authctx, port->sock, ALL);
> X+#undef ALL
> X+
> X+    code = krb5_recvauth(mycontext, &authctx, (krb5_pointer)&port->sock,
> X+                 PG_KRB5_VERSION, server, 0, keytab,
> X+                 (krb5_ticket **)0);
> X+    if (code)
> X     {
> X-        keyproc = krb5_kt_read_service_key;
> X-        keyprocarg = PG_KRB_SRVTAB;
> X+        snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X+             "pg_krb5_recvauth: krb5_recvauth: %s\n",
> X+             error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        pqdebug("%s", PQerrormsg);
> X+        krb5_free_principal(mycontext, server);
> X+        krb5_auth_con_free(mycontext, authctx);
> X+        return STATUS_ERROR;
> X     }
> X+    krb5_free_principal(mycontext, server);
> X
> X-    if (code = krb5_recvauth((krb5_pointer) & port->sock,
> X-                             PG_KRB5_VERSION,
> X-                             server,
> X-                             &sender_addr,
> X-                             (krb5_pointer) NULL,
> X-                             keyproc,
> X-                             keyprocarg,
> X-                             (char *) NULL,
> X-                             (krb5_int32 *) NULL,
> X-                             &client,
> X-                             (krb5_ticket **) NULL,
> X-                             (krb5_authenticator **) NULL))
> X+    code = krb5_auth_con_getauthenticator(mycontext, authctx, &them);
> X+    if (code)
> X     {
> X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X-         "pg_krb5_recvauth: Kerberos error %d in krb5_recvauth\n", code);
> X-        com_err("pg_krb5_recvauth", code, "in krb5_recvauth");
> X-        krb5_free_principal(server);
> X+         "pg_krb5_recvauth: getauthenticator: %s\n",
> X+             error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        pqdebug("%s", PQerrormsg);
> X+        krb5_free_principal(mycontext, server);
> X+        krb5_auth_con_free(mycontext, authctx);
> X         return STATUS_ERROR;
> X     }
> X-    krb5_free_principal(server);
> X
> X     /*
> X@@ -270,13 +325,18 @@
> X      * postmaster startup packet.
> X      */
> X-    if ((code = krb5_unparse_name(client, &kusername)))
> X+    if ((code = krb5_unparse_name(mycontext, them->client, &kusername)))
> X     {
> X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X-                 "pg_krb5_recvauth: Kerberos error %d in krb5_unparse_name\n", code);
> X-        com_err("pg_krb5_recvauth", code, "in krb5_unparse_name");
> X-        krb5_free_principal(client);
> X+             "pg_krb5_recvauth: krb5_unparse_name: %s\n",
> X+             error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        pqdebug("%s", PQerrormsg);
> X+        krb5_free_authenticator(mycontext, them);
> X+        krb5_auth_con_free(mycontext, authctx);
> X         return STATUS_ERROR;
> X     }
> X-    krb5_free_principal(client);
> X+    krb5_free_authenticator(mycontext, them);
> X+    krb5_auth_con_free(mycontext, authctx);
> X+
> X     if (!kusername)
> X     {
> X@@ -288,5 +348,5 @@
> X     }
> X     kusername = pg_an_to_ln(kusername);
> X-    if (strncmp(username, kusername, SM_USER))
> X+    if (strncmp(port->user, kusername, SM_USER))
> X     {
> X         snprintf(PQerrormsg, PQERRORMSG_LENGTH,
> X@@ -294,8 +354,8 @@
> X         fputs(PQerrormsg, stderr);
> X         pqdebug("%s", PQerrormsg);
> X-        pfree(kusername);
> X+        free(kusername);
> X         return STATUS_ERROR;
> X     }
> X-    pfree(kusername);
> X+    free(kusername);
> X     return STATUS_OK;
> X }
> END-of-patch-be
> echo x - patch-bf
> sed 's/^X//' >patch-bf << 'END-of-patch-bf'
> Xdiff -ru2 old/configure.in configure.in
> X--- old/configure.in    Wed May 24 18:43:59 2000
> X+++ configure.in    Tue Sep 26 22:39:46 2000
> X@@ -369,4 +369,55 @@
> X export USE_ODBC
> X
> X+AC_ARG_WITH(
> X+    krb5,
> X+    [  --with-krb5[=PREFIX]    build Kerberos 5 authentication support ],
> X+    [
> X+    case "$withval" in
> X+    y | ye | yes)    USE_KRB5=true;;
> X+    n | no)    USE_KRB5=false;;
> X+    *)    USE_KRB5=true
> X+        KRB5_INCS="-I$withval/include $KRB5_INCS"
> X+        KRB5_LIBS="-L$withval/lib $KRB5_LIBS";;
> X+    esac
> X+    ],
> X+    [ USE_KRB5=false ]
> X+)
> X+
> X+AC_ARG_WITH(
> X+    krb-service-name,
> X+    [  --with-krb-service-name=NAME authenticate as Kerberos principal NAME ],
> X+    [  KRB_SRVNAM="$withval" ],
> X+    [  unset KRB_SRVNAM ]
> X+)
> X+
> X+AC_ARG_WITH(
> X+    krb-keytab,
> X+    [  --with-krb-keytab=FILE:FILENAME use Kerberos v5 keytab FILENAME ],
> X+    [  KRB_KEYTAB="$withval" ],
> X+    [  unset KRB_KEYTAB ]
> X+)
> X+
> X+export USE_KRB5
> X+export KRB5_INCS
> X+export KRB5_LIBS
> X+
> X+AC_ARG_WITH(
> X+    openssl,
> X+    [  --with-openssl[=PREFIX] build OpenSSL transport support ],
> X+    [
> X+    case "$withval" in
> X+    y | ye | yes)    USE_OPENSSL=true;;
> X+    n | no)    USE_OPENSSL=false;;
> X+    *)    USE_OPENSSL=true
> X+        OPENSSL_INCS="-I$withval $OPENSSL_INCS"
> X+        OPENSSL_LIBS="-L$withval $OPENSSL_LIBS -lopenssl -lcrypto";;
> X+    esac
> X+    ],
> X+    [ USE_OPENSSL=false ]
> X+)
> X+export USE_OPENSSL
> X+export OPENSSL_INCS
> X+export OPENSSL_LIBS
> X+
> X AC_MSG_CHECKING(setproctitle)
> X AC_ARG_WITH(
> X@@ -509,4 +560,6 @@
> X AC_SUBST(USE_ODBC)
> X AC_SUBST(MULTIBYTE)
> X+AC_SUBST(USE_KRB5)
> X+AC_SUBST(USE_OPENSSL)
> X
> X dnl Check for C++ support (allow override if needed)
> X@@ -1312,4 +1365,50 @@
> X     CPPFLAGS="$ice_save_CPPFLAGS"
> X     LDFLAGS="$ice_save_LDFLAGS"
> X+fi
> X+
> X+dnl
> X+dnl User requested Kerberos support in libpq; see if the required
> X+dnl header files and libraries are actually there.
> X+dnl
> X+if $USE_KRB5; then
> X+    AC_CHECKING(if Kerberos 5 environment is complete)
> X+    OCFLAGS="$CFLAGS"
> X+    OCPPFLAGS="$CPPFLAGS"
> X+    OLIBS="$LIBS"
> X+    if test "$KRB5_INCS"; then
> X+        CFLAGS="$CFLAGS $KRB5_INCS"
> X+        CPPFLAGS="$CPPFLAGS $KRB5_INCS"
> X+    fi
> X+    if test "$KRB5_LIBS"; then
> X+        LIBS="$LIBS $KRB5_LIBS"
> X+    fi
> X+    AC_CHECK_HEADERS(krb5.h)
> X+    AC_CHECK_LIB(krb5, krb5_recvauth, , , [-lk5crypto -lcom_err])
> X+    if test "$ac_cv_header_krb5_h" = "no"; then
> X+        AC_MSG_WARN([krb5.h not found; disabling Kerberos v5 support])
> X+        USE_KRB5=false
> X+        CFLAGS="$OCFLAGS"
> X+        LIBS="$OLIBS"
> X+    else
> X+        if test "$ac_cv_lib_krb5" = "no"; then
> X+            AC_MSG_WARN([libkrb5 not found; disabling Kerberos v5 support])
> X+            USE_KRB5=false
> X+        else
> X+        fi
> X+    fi
> X+    if $USE_KRB5; then
> X+        CFLAGS="$CFLAGS -DKRB5 -DUSE_KRB5"
> X+        LIBS="$LIBS -lkrb5 -lk5crypto -lcom_err"
> X+        if test "$KRB_SRVNAM"; then
> X+            CFLAGS="$CFLAGS -DPG_KRB_SRVNAM=\\\"$KRB_SRVNAM\\\""
> X+        fi
> X+        if test "$KRB_KEYTAB"; then
> X+            CFLAGS="$CFLAGS -DPG_KRB_KEYTAB=\\\"$KRB_KEYTAB\\\""
> X+        fi
> X+    else
> X+        CFLAGS="$OCFLAGS"
> X+        CPPFLAGS="$OCPPFLAGS"
> X+        LIBS="$OLIBS"
> X+    fi
> X fi
> X
> END-of-patch-bf
> echo x - patch-bg
> sed 's/^X//' >patch-bg << 'END-of-patch-bg'
> Xdiff -ru2 old/interfaces/libpq/Makefile.in interfaces/libpq/Makefile.in
> X--- old/interfaces/libpq/Makefile.in    Thu Apr 13 20:42:06 2000
> X+++ interfaces/libpq/Makefile.in    Tue Sep 26 23:50:45 2000
> X@@ -34,4 +34,8 @@
> X # make sure it gets included in shared libpq.
> X SHLIB_LINK+= $(findstring -lcrypt,$(LIBS))
> X+SHLIB_LINK+= $(filter -L%,$(LIBS))
> X+SHLIB_LINK+= $(findstring -lkrb5,$(LIBS))
> X+SHLIB_LINK+= $(findstring -lk5crypto,$(LIBS))
> X+SHLIB_LINK+= $(findstring -lcom_err,$(LIBS))
> X
> X # Shared library stuff, also default 'all' target
> END-of-patch-bg
> echo x - patch-bh
> sed 's/^X//' >patch-bh << 'END-of-patch-bh'
> X--- interfaces/libpq/fe-auth.c.orig    Wed Apr 12 13:17:13 2000
> X+++ interfaces/libpq/fe-auth.c    Wed Sep 27 23:15:30 2000
> X@@ -235,5 +235,9 @@
> X  */
> X
> X-#include "krb5/krb5.h"
> X+#include <fcntl.h>
> X+#include "krb5.h"
> X+#ifndef PG_KRB_SRVNAM
> X+#define    PG_KRB_SRVNAM    "pgsql"
> X+#endif
> X
> X /*
> X@@ -241,12 +245,11 @@
> X  *                  name
> X  *
> X- * XXX Assumes that the first aname component is the user name.  This is NOT
> X- *       necessarily so, since an aname can actually be something out of your
> X- *       worst X.400 nightmare, like
> X- *          ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
> X- *       Note that the MIT an_to_ln code does the same thing if you don't
> X- *       provide an aname mapping database...it may be a better idea to use
> X- *       krb5_an_to_ln, except that it punts if multiple components are found,
> X- *       and we can't afford to punt.
> X+ * XXX - this is totally broken (and potentially insecure on the server side).
> X+ * The correct mechanism is to use the entire principal name, and make
> X+ * the server do a table lookup to discover the mapping.
> X+ * (In the protocol as it stands, if user jrl@A.EXAMPLE.COM authenticates to
> X+ * a server in the B.EXAMPLE.COM realm, the server will accept him as
> X+ * local-user `jrl' regardless of whether or not jrl@A.EXAMPLE.COM is
> X+ * the same user as jrl@B.EXAMPLE.COM.)
> X  */
> X static char *
> X@@ -260,63 +263,37 @@
> X }
> X
> X+static krb5_context mycontext;
> X+static int mycontext_inited;
> X+static krb5_ccache ccache;
> X
> X-/*
> X- * pg_krb5_init -- initialization performed before any Kerberos calls are made
> X- *
> X- * With v5, we can no longer set the ticket (credential cache) file name;
> X- * we now have to provide a file handle for the open (well, "resolved")
> X- * ticket file everywhere.
> X- *
> X- */
> X-static int
> X-            krb5_ccache
> X+static krb5_error_code
> X pg_krb5_init(void)
> X {
> X     krb5_error_code code;
> X-    char       *realm,
> X-               *defname;
> X-    char        tktbuf[MAXPGPATH];
> X-    static krb5_ccache ccache = (krb5_ccache) NULL;
> X
> X-    if (ccache)
> X-        return ccache;
> X-
> X-    /*
> X-     * If the user set PGREALM, then we use a ticket file with a special
> X-     * name: <usual-ticket-file-name>@<PGREALM-value>
> X-     */
> X-    if (!(defname = krb5_cc_default_name()))
> X-    {
> X-        (void) sprintf(PQerrormsg,
> X-                       "pg_krb5_init: krb5_cc_default_name failed\n");
> X-        return (krb5_ccache) NULL;
> X-    }
> X-    strcpy(tktbuf, defname);
> X-    if (realm = getenv("PGREALM"))
> X-    {
> X-        strcat(tktbuf, "@");
> X-        strcat(tktbuf, realm);
> X-    }
> X+    if (mycontext_inited)
> X+        return 0;
> X
> X-    if (code = krb5_cc_resolve(tktbuf, &ccache))
> X-    {
> X-        (void) sprintf(PQerrormsg,
> X-           "pg_krb5_init: Kerberos error %d in krb5_cc_resolve\n", code);
> X-        com_err("pg_krb5_init", code, "in krb5_cc_resolve");
> X-        return (krb5_ccache) NULL;
> X-    }
> X-    return ccache;
> X+    code = krb5_init_context(&mycontext);
> X+    if (code)
> X+        return code;
> X+
> X+    code = krb5_cc_default(mycontext, &ccache);
> X+    if (code)
> X+        return code;
> X+    mycontext_inited = 1;
> X+    return 0;
> X }
> X
> X /*
> X  * pg_krb5_authname -- returns a pointer to static space containing whatever
> X- *                       name the user has authenticated to the system
> X+ *                name the user has authenticated to the system
> X  *
> X  * We obtain this information by digging around in the ticket file.
> X+ * XXX see comments above
> X  */
> X-static const char *
> X-pg_krb5_authname(const char *PQerrormsg)
> X+static char *
> X+pg_krb5_authname(char *PQerrormsg)
> X {
> X-    krb5_ccache ccache;
> X     krb5_principal principal;
> X     krb5_error_code code;
> X@@ -326,22 +303,33 @@
> X         return authname;
> X
> X-    ccache = pg_krb5_init();    /* don't free this */
> X+    code = pg_krb5_init();
> X+    if (code)
> X+    {
> X+        sprintf(PQerrormsg, "pg_krb5_init: %s\n",
> X+            error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        return (char *)NULL;
> X+    }
> X
> X-    if (code = krb5_cc_get_principal(ccache, &principal))
> X+    code = krb5_cc_get_principal(mycontext, ccache, &principal);
> X+    if (code)
> X     {
> X         (void) sprintf(PQerrormsg,
> X-                       "pg_krb5_authname: Kerberos error %d in krb5_cc_get_principal\n", code);
> X-        com_err("pg_krb5_authname", code, "in krb5_cc_get_principal");
> X+                   "pg_krb5_authname: krb5_cc_get_principal: %s\n",
> X+                   error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X         return (char *) NULL;
> X     }
> X-    if (code = krb5_unparse_name(principal, &authname))
> X+    code = krb5_unparse_name(mycontext, principal, &authname);
> X+    if (code)
> X     {
> X         (void) sprintf(PQerrormsg,
> X-                       "pg_krb5_authname: Kerberos error %d in krb5_unparse_name\n", code);
> X-        com_err("pg_krb5_authname", code, "in krb5_unparse_name");
> X-        krb5_free_principal(principal);
> X+                   "pg_krb5_authname: krb5_unparse_name: %s\n",
> X+                   error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        krb5_free_principal(mycontext, principal);
> X         return (char *) NULL;
> X     }
> X-    krb5_free_principal(principal);
> X+    krb5_free_principal(mycontext, principal);
> X     return pg_an_to_ln(authname);
> X }
> X@@ -349,107 +337,125 @@
> X /*
> X  * pg_krb5_sendauth -- client routine to send authentication information to
> X- *                       the server
> X- *
> X- * This routine does not do mutual authentication, nor does it return enough
> X- * information to do encrypted connections.  But then, if we want to do
> X- * encrypted connections, we'll have to redesign the whole RPC mechanism
> X- * anyway.
> X+ *               the server
> X  *
> X- * Server hostnames are canonicalized v4-style, i.e., all domain suffixes
> X- * are simply chopped off.    Hence, we are assuming that you've entered your
> X- * server instances as
> X- *        <value-of-PG_KRB_SRVNAM>/<canonicalized-hostname>
> X- * in the PGREALM (or local) database.    This is probably a bad assumption.
> X  */
> X static int
> X-pg_krb5_sendauth(const char *PQerrormsg, int sock,
> X-                 struct sockaddr_in * laddr,
> X-                 struct sockaddr_in * raddr,
> X-                 const char *hostname)
> X+pg_krb5_sendauth(char *PQerrormsg, int sock,
> X+         struct sockaddr_in * laddr,
> X+         struct sockaddr_in * raddr,
> X+         const char *hostname)
> X {
> X-    char        servbuf[MAXHOSTNAMELEN + 1 +
> X-                                    sizeof(PG_KRB_SRVNAM)];
> X-    const char *hostp;
> X-    const char *realm;
> X+    int sflags;
> X+    char servbuf[MAXHOSTNAMELEN + 1];
> X     krb5_error_code code;
> X-    krb5_principal client,
> X-                server;
> X-    krb5_ccache ccache;
> X+    krb5_principal server;
> X     krb5_error *error = (krb5_error *) NULL;
> X+    krb5_auth_context authctx;
> X
> X-    ccache = pg_krb5_init();    /* don't free this */
> X+    code = pg_krb5_init();
> X+    if (code)
> X+    {
> X+        sprintf(PQerrormsg, "pg_krb5_init: %s\n",
> X+            error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        return STATUS_ERROR;
> X+    }
> X
> X-    /*
> X-     * set up client -- this is easy, we can get it out of the ticket
> X-     * file.
> X-     */
> X-    if (code = krb5_cc_get_principal(ccache, &client))
> X+    code = krb5_auth_con_init(mycontext, &authctx);
> X+    if (code)
> X     {
> X-        (void) sprintf(PQerrormsg,
> X-                       "pg_krb5_sendauth: Kerberos error %d in krb5_cc_get_principal\n", code);
> X-        com_err("pg_krb5_sendauth", code, "in krb5_cc_get_principal");
> X+        sprintf(PQerrormsg,
> X+            "pg_krb5_recvauth: krb5_auth_con_init: %s\n",
> X+            error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X         return STATUS_ERROR;
> X     }
> X
> X-    /*
> X-     * set up server -- canonicalize as described above
> X-     */
> X-    strcpy(servbuf, PG_KRB_SRVNAM);
> X-    *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
> X-    if (hostname || *hostname)
> X-        strncpy(++hostp, hostname, MAXHOSTNAMELEN);
> X-    else
> X-    {
> X-        if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
> X-            strcpy(hostp, "localhost");
> X-    }
> X-    if (hostp = strchr(hostp, '.'))
> X-        *hostp = '\0';
> X-    if (realm = getenv("PGREALM"))
> X+    if (hostname == 0 || *hostname == '\0')
> X     {
> X-        strcat(servbuf, "@");
> X-        strcat(servbuf, realm);
> X+        if (gethostname(servbuf, MAXHOSTNAMELEN) < 0)
> X+        {
> X+            sprintf(PQerrormsg,
> X+                 "pg_krb5_sendauth: gethostname: %s\n",
> X+                 strerror(errno));
> X+            fputs(PQerrormsg, stderr);
> X+            krb5_auth_con_free(mycontext, authctx);
> X+            return STATUS_ERROR;
> X+        }
> X+        hostname = servbuf;
> X     }
> X-    if (code = krb5_parse_name(servbuf, &server))
> X+
> X+    code = krb5_sname_to_principal(mycontext, hostname, PG_KRB_SRVNAM,
> X+                       KRB5_NT_SRV_HST, &server);
> X+
> X+    if (code)
> X     {
> X-        (void) sprintf(PQerrormsg,
> X-        "pg_krb5_sendauth: Kerberos error %d in krb5_parse_name\n", code);
> X-        com_err("pg_krb5_sendauth", code, "in krb5_parse_name");
> X-        krb5_free_principal(client);
> X+        sprintf(PQerrormsg,
> X+            "pg_krb5_sendauth: krb5_sname_to_principal: %s\n",
> X+            error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        krb5_auth_con_free(mycontext, authctx);
> X+        return STATUS_ERROR;
> X+    }
> X+
> X+#define    ALL    (KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR | \
> X+         KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR)
> X+    code = krb5_auth_con_genaddrs(mycontext, authctx, sock, ALL);
> X+#undef ALL
> X+    if (code)
> X+    {
> X+        sprintf(PQerrormsg,
> X+            "pg_krb5_sendauth: krb5_auth_con_genaddrs: %s\n",
> X+            error_message(code));
> X+        fputs(PQerrormsg, stderr);
> X+        krb5_free_principal(mycontext, server);
> X+        krb5_auth_con_free(mycontext, authctx);
> X         return STATUS_ERROR;
> X     }
> X
> X+
> X+    /*
> X+     * krb5_sendauth does not appreciate getting a non-blocking file
> X+     * descriptor.  So, we set it to blocking mode and then reset
> X+     * it afterwards.
> X+     */
> X+    sflags = fcntl(sock, F_GETFL, 0);
> X+    fcntl(sock, F_SETFL, sflags & ~O_NONBLOCK);
> X+
> X     /*
> X      * The only thing we want back from krb5_sendauth is an error status
> X-     * and any error messages.
> X+     * and any error messages.  If we cared, we could get the session
> X+     * key(s) from the auth context and stick them somewhere to encrypt
> X+     * the whole data stream.  However, this would mean a major change
> X+     * in the protocol, and I'm not prepared to do that right now.
> X+     * (It should work to encrypt the session using SSL, if a bit of a
> X+     * muchness.)
> X      */
> X-    if (code = krb5_sendauth((krb5_pointer) & sock,
> X-                             PG_KRB5_VERSION,
> X-                             client,
> X-                             server,
> X-                             (krb5_flags) 0,
> X-                             (krb5_checksum *) NULL,
> X-                             (krb5_creds *) NULL,
> X-                             ccache,
> X-                             (krb5_int32 *) NULL,
> X-                             (krb5_keyblock **) NULL,
> X-                             &error,
> X-                             (krb5_ap_rep_enc_part **) NULL))
> X+    code = krb5_sendauth(mycontext, &authctx, (krb5_pointer) &sock,
> X+                 PG_KRB5_VERSION, (krb5_principal) NULL,
> X+                 server, AP_OPTS_MUTUAL_REQUIRED,
> X+                 (krb5_data *) NULL, (krb5_creds *) NULL,
> X+                 ccache, &error, (krb5_ap_rep_enc_part **) NULL,
> X+                 (krb5_creds **) NULL);
> X+    fcntl(sock, F_SETFL, sflags);
> X+    if (code)
> X     {
> X         if ((code == KRB5_SENDAUTH_REJECTED) && error)
> X         {
> X-            (void) sprintf(PQerrormsg,
> X-                  "pg_krb5_sendauth: authentication rejected: \"%*s\"\n",
> X-                           error->text.length, error->text.data);
> X+            sprintf(PQerrormsg,
> X+                "pg_krb5_sendauth: authentication rejected: \"%.*s\"\n",
> X+                error->text.length, error->text.data);
> X         }
> X         else
> X         {
> X-            (void) sprintf(PQerrormsg,
> X-                           "pg_krb5_sendauth: Kerberos error %d in krb5_sendauth\n", code);
> X-            com_err("pg_krb5_sendauth", code, "in krb5_sendauth");
> X+            sprintf(PQerrormsg,
> X+                "pg_krb5_sendauth: krb5_sendauth: %s\n",
> X+                error_message(code));
> X         }
> X     }
> X-    krb5_free_principal(client);
> X-    krb5_free_principal(server);
> X+    krb5_free_principal(mycontext, server);
> X+    if (error != 0)
> X+        krb5_free_error(mycontext, error);
> X+    krb5_auth_con_free(mycontext, authctx);
> X     return code ? STATUS_ERROR : STATUS_OK;
> X }
> END-of-patch-bh
> exit
>
>
>


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Kerberos v5 support

From
Garrett Wollman
Date:
<<On Mon, 6 Nov 2000 12:05:01 -0500 (EST), Bruce Momjian <pgman@candle.pha.pa.us> said:

> I have applied some kerberos changes to the current snapshot a few
> months ago.  Can you grab that and let me know what you would like
> changed?  Thanks.

My code has much better error handing (``Kerberos error %d'' is vile!)
and uses the correct API to determine the client's authenticated
name.  My version also checks the IP addresses in the client's ticket
to protect against certain kinds of attacks.  On the other hand, the
-current code is configurable with respect to the name of the keytab.
(I don't personally see much value in allowing the keytab name to be
changed at run time, but whatever floats your boat....)

Both versions still sweep the an_to_ln problem under the carpet.  This
is a SERIOUS flaw for anyone who needs to operate in an environment
with cross-realm authentication.  I don't know the innards of pgsql
well-enough to be able to code the internal table-lookup that would be
necessary to perform proper an_to_ln mapping -- hopefully someone else
out there does.

Since I'm working in a near-production environment, I'm not presently
able to combine my functionality with that provided in pgsql-current.
When it becomes a release, you may well hear back from me.

-GAWollman


Re: Kerberos v5 support

From
Bruce Momjian
Date:
OK.

> <<On Mon, 6 Nov 2000 12:05:01 -0500 (EST), Bruce Momjian <pgman@candle.pha.pa.us> said:
>
> > I have applied some kerberos changes to the current snapshot a few
> > months ago.  Can you grab that and let me know what you would like
> > changed?  Thanks.
>
> My code has much better error handing (``Kerberos error %d'' is vile!)
> and uses the correct API to determine the client's authenticated
> name.  My version also checks the IP addresses in the client's ticket
> to protect against certain kinds of attacks.  On the other hand, the
> -current code is configurable with respect to the name of the keytab.
> (I don't personally see much value in allowing the keytab name to be
> changed at run time, but whatever floats your boat....)
>
> Both versions still sweep the an_to_ln problem under the carpet.  This
> is a SERIOUS flaw for anyone who needs to operate in an environment
> with cross-realm authentication.  I don't know the innards of pgsql
> well-enough to be able to code the internal table-lookup that would be
> necessary to perform proper an_to_ln mapping -- hopefully someone else
> out there does.
>
> Since I'm working in a near-production environment, I'm not presently
> able to combine my functionality with that provided in pgsql-current.
> When it becomes a release, you may well hear back from me.
>
> -GAWollman
>
>


--
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 853-3000
  +  If your life is a hard drive,     |  830 Blythe Avenue
  +  Christ can be your backup.        |  Drexel Hill, Pennsylvania 19026

Re: Kerberos v5 support

From
Peter Eisentraut
Date:
Garrett Wollman writes:

> Enclosed please find a set of patches, relative to 7.0.2, which will
> result in Kerberos v5 support which both compiles and works (as in,
> I've successfully authenticated as a remote client).

The 7.0 series is not so interesting at this point but you might have a
few days yet to get stuff into 7.1. :)  (Especially stuff that's #ifdef
KRB5 ought to be safe.)

'configure' support for Kerberos (and OpenSSL) has been implemented
meanwhile.

> local        all                                           trust
> host         all         0.0.0.0        0.0.0.0         krb5
>
> However, that `trust' is tempered by changes to the startup scripts
> (not included here) which force the local-domain socket to mode 600,

We also got that in 7.1-to-be, even without race conditions. :)

> You can see from some of the comments that I'd like this to be made
> stronger in a number of ways.  This patch set simply gets pgsql up to
> the minimum acceptable level of security for our environment and
> application.

Well, not a lot of people really know and use the Kerberos support, so
anything that can be done to improve it should be okay.  Some better
documentation would also be appreciated. :)

--
Peter Eisentraut      peter_e@gmx.net       http://yi.org/peter-e/