]> granicus.if.org Git - postgresql/blobdiff - src/backend/libpq/auth.c
Make krb_realm and krb_server_hostname be pg_hba options only, and remove
[postgresql] / src / backend / libpq / auth.c
index 35fe2801aeb64861c15fa21db226065e4f0d8c80..f0561a5b969a4d0d0c01d4ee3d480870de2a2133 100644 (file)
@@ -3,12 +3,12 @@
  * auth.c
  *       Routines to handle network authentication
  *
- * Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.120 2004/12/20 17:13:40 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.178 2009/01/09 10:13:18 mha Exp $
  *
  *-------------------------------------------------------------------------
  */
 #if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
 #include <sys/uio.h>
 #include <sys/ucred.h>
-#include <errno.h>
+#endif
+#ifdef HAVE_UCRED_H
+# include <ucred.h>
 #endif
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <unistd.h>
 
 #include "libpq/auth.h"
 #include "libpq/crypt.h"
-#include "libpq/hba.h"
+#include "libpq/ip.h"
 #include "libpq/libpq.h"
-#include "libpq/pqcomm.h"
 #include "libpq/pqformat.h"
-#include "miscadmin.h"
 #include "storage/ipc.h"
 
-
+/*----------------------------------------------------------------
+ * Global authentication functions 
+ *----------------------------------------------------------------
+ */
 static void sendAuthRequest(Port *port, AuthRequest areq);
 static void auth_failed(Port *port, int status);
 static char *recv_password_packet(Port *port);
 static int     recv_and_check_password_packet(Port *port);
 
-char      *pg_krb_server_keyfile;
 
+/*----------------------------------------------------------------
+ * Ident authentication
+ *----------------------------------------------------------------
+ */
+/* Max size of username ident server can return */
+#define IDENT_USERNAME_MAX 512
+
+/* Standard TCP port number for Ident service. Assigned by IANA */
+#define IDENT_PORT 113
+
+static int  authident(hbaPort *port);
+
+
+/*----------------------------------------------------------------
+ * PAM authentication
+ *----------------------------------------------------------------
+ */
 #ifdef USE_PAM
 #ifdef HAVE_PAM_PAM_APPL_H
 #include <pam/pam_appl.h>
@@ -62,119 +82,67 @@ static struct pam_conv pam_passw_conv = {
 };
 
 static char *pam_passwd = NULL; /* Workaround for Solaris 2.6 brokenness */
-static Port *pam_port_cludge;  /* Workaround for passing "Port *port"
-                                                                * into pam_passwd_conv_proc */
+static Port *pam_port_cludge;  /* Workaround for passing "Port *port" into
+                                                                * pam_passwd_conv_proc */
 #endif   /* USE_PAM */
 
-#ifdef KRB4
+
 /*----------------------------------------------------------------
- * MIT Kerberos authentication system - protocol version 4
+ * LDAP authentication
  *----------------------------------------------------------------
  */
+#ifdef USE_LDAP
+#ifndef WIN32
+/* We use a deprecated function to keep the codepath the same as win32. */
+#define LDAP_DEPRECATED 1
+#include <ldap.h>
+#else
+#include <winldap.h>
+
+/* Correct header from the Platform SDK */
+typedef
+ULONG(*__ldap_start_tls_sA) (
+                                                        IN PLDAP ExternalHandle,
+                                                        OUT PULONG ServerReturnValue,
+                                                        OUT LDAPMessage ** result,
+                                                        IN PLDAPControlA * ServerControls,
+                                                        IN PLDAPControlA * ClientControls
+);
+#endif
 
-#include "krb.h"
+static int     CheckLDAPAuth(Port *port);
+#endif /* USE_LDAP */
 
-/*
- * pg_krb4_recvauth -- server routine to receive authentication information
- *                                        from the client
- *
- * Nothing unusual here, except that we compare the username obtained from
- * the client's setup packet to the authenticated name.  (We have to retain
- * the name in the setup packet since we have to retain the ability to handle
- * unauthenticated connections.)
+/*----------------------------------------------------------------
+ * Cert authentication
+ *----------------------------------------------------------------
  */
-static int
-pg_krb4_recvauth(Port *port)
-{
-       long            krbopts = 0;    /* one-way authentication */
-       KTEXT_ST        clttkt;
-       char            instance[INST_SZ + 1],
-                               version[KRB_SENDAUTH_VLEN + 1];
-       AUTH_DAT        auth_data;
-       Key_schedule key_sched;
-       int                     status;
-
-       strcpy(instance, "*");          /* don't care, but arg gets expanded
-                                                                * anyway */
-       status = krb_recvauth(krbopts,
-                                                 port->sock,
-                                                 &clttkt,
-                                                 PG_KRB_SRVNAM,
-                                                 instance,
-                                                 &port->raddr.in,
-                                                 &port->laddr.in,
-                                                 &auth_data,
-                                                 pg_krb_server_keyfile,
-                                                 key_sched,
-                                                 version);
-       if (status != KSUCCESS)
-       {
-               ereport(LOG,
-                               (errmsg("Kerberos error: %s", krb_err_txt[status])));
-               return STATUS_ERROR;
-       }
-       if (strncmp(version, PG_KRB4_VERSION, KRB_SENDAUTH_VLEN) != 0)
-       {
-               ereport(LOG,
-                               (errmsg("unexpected Kerberos protocol version received from client (received \"%s\", expected \"%s\")",
-                                               version, PG_KRB4_VERSION)));
-               return STATUS_ERROR;
-       }
-       if (strncmp(port->user_name, auth_data.pname, SM_DATABASE_USER) != 0)
-       {
-               ereport(LOG,
-                               (errmsg("unexpected Kerberos user name received from client (received \"%s\", expected \"%s\")",
-                                               port->user_name, auth_data.pname)));
-               return STATUS_ERROR;
-       }
-       return STATUS_OK;
-}
+#ifdef USE_SSL
+static int     CheckCertAuth(Port *port);
+#endif
 
-#else
 
-static int
-pg_krb4_recvauth(Port *port)
-{
-       ereport(LOG,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("Kerberos 4 not implemented on this server")));
-       return STATUS_ERROR;
-}
-#endif   /* KRB4 */
+/*----------------------------------------------------------------
+ * Kerberos and GSSAPI GUCs
+ *----------------------------------------------------------------
+ */
+char      *pg_krb_server_keyfile;
+char      *pg_krb_srvnam;
+bool           pg_krb_caseins_users;
 
 
-#ifdef KRB5
 /*----------------------------------------------------------------
  * MIT Kerberos authentication system - protocol version 5
  *----------------------------------------------------------------
  */
+#ifdef KRB5
+static int pg_krb5_recvauth(Port *port);
 
 #include <krb5.h>
-
-/*
- * pg_an_to_ln -- return the local name corresponding to an authentication
- *                               name
- *
- * XXX Assumes that the first aname component is the user name.  This is NOT
- *        necessarily so, since an aname can actually be something out of your
- *        worst X.400 nightmare, like
- *               ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
- *        Note that the MIT an_to_ln code does the same thing if you don't
- *        provide an aname mapping database...it may be a better idea to use
- *        krb5_an_to_ln, except that it punts if multiple components are found,
- *        and we can't afford to punt.
- */
-static char *
-pg_an_to_ln(char *aname)
-{
-       char       *p;
-
-       if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
-               *p = '\0';
-       return aname;
-}
-
-
+/* Some old versions of Kerberos do not include <com_err.h> in <krb5.h> */
+#if !defined(__COM_ERR_H) && !defined(__COM_ERR_H__)
+#include <com_err.h>
+#endif
 /*
  * Various krb5 state which is not connection specfic, and a flag to
  * indicate whether we have initialised it yet.
@@ -183,150 +151,41 @@ static int       pg_krb5_initialised;
 static krb5_context pg_krb5_context;
 static krb5_keytab pg_krb5_keytab;
 static krb5_principal pg_krb5_server;
+#endif /* KRB5 */
 
 
-static int
-pg_krb5_init(void)
-{
-       krb5_error_code retval;
-
-       if (pg_krb5_initialised)
-               return STATUS_OK;
-
-       retval = krb5_init_context(&pg_krb5_context);
-       if (retval)
-       {
-               ereport(LOG,
-                               (errmsg("Kerberos initialization returned error %d",
-                                               retval)));
-               com_err("postgres", retval, "while initializing krb5");
-               return STATUS_ERROR;
-       }
-
-       retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab);
-       if (retval)
-       {
-               ereport(LOG,
-                               (errmsg("Kerberos keytab resolving returned error %d",
-                                               retval)));
-               com_err("postgres", retval, "while resolving keytab file \"%s\"",
-                               pg_krb_server_keyfile);
-               krb5_free_context(pg_krb5_context);
-               return STATUS_ERROR;
-       }
-
-       retval = krb5_sname_to_principal(pg_krb5_context, NULL, PG_KRB_SRVNAM,
-                                                                        KRB5_NT_SRV_HST, &pg_krb5_server);
-       if (retval)
-       {
-               ereport(LOG,
-                (errmsg("Kerberos sname_to_principal(\"%s\") returned error %d",
-                                PG_KRB_SRVNAM, retval)));
-               com_err("postgres", retval,
-                               "while getting server principal for service \"%s\"",
-                               PG_KRB_SRVNAM);
-               krb5_kt_close(pg_krb5_context, pg_krb5_keytab);
-               krb5_free_context(pg_krb5_context);
-               return STATUS_ERROR;
-       }
-
-       pg_krb5_initialised = 1;
-       return STATUS_OK;
-}
-
-
-/*
- * pg_krb5_recvauth -- server routine to receive authentication information
- *                                        from the client
- *
- * We still need to compare the username obtained from the client's setup
- * packet to the authenticated name, as described in pg_krb4_recvauth. This
- * is a bit more problematic in v5, as described above in pg_an_to_ln.
- *
- * We have our own keytab file because postgres is unlikely to run as root,
- * and so cannot read the default keytab.
+/*----------------------------------------------------------------
+ * GSSAPI Authentication
+ *----------------------------------------------------------------
  */
-static int
-pg_krb5_recvauth(Port *port)
-{
-       krb5_error_code retval;
-       int                     ret;
-       krb5_auth_context auth_context = NULL;
-       krb5_ticket *ticket;
-       char       *kusername;
-
-       ret = pg_krb5_init();
-       if (ret != STATUS_OK)
-               return ret;
-
-       retval = krb5_recvauth(pg_krb5_context, &auth_context,
-                                                  (krb5_pointer) & port->sock, PG_KRB_SRVNAM,
-                                                  pg_krb5_server, 0, pg_krb5_keytab, &ticket);
-       if (retval)
-       {
-               ereport(LOG,
-                               (errmsg("Kerberos recvauth returned error %d",
-                                               retval)));
-               com_err("postgres", retval, "from krb5_recvauth");
-               return STATUS_ERROR;
-       }
-
-       /*
-        * The "client" structure comes out of the ticket and is therefore
-        * authenticated.  Use it to check the username obtained from the
-        * postmaster startup packet.
-        *
-        * I have no idea why this is considered necessary.
-        */
-#if defined(HAVE_KRB5_TICKET_ENC_PART2)
-       retval = krb5_unparse_name(pg_krb5_context,
-                                                          ticket->enc_part2->client, &kusername);
-#elif defined(HAVE_KRB5_TICKET_CLIENT)
-       retval = krb5_unparse_name(pg_krb5_context,
-                                                          ticket->client, &kusername);
+#ifdef ENABLE_GSS
+#if defined(HAVE_GSSAPI_H)
+#include <gssapi.h>
 #else
-#error "bogus configuration"
+#include <gssapi/gssapi.h>
 #endif
-       if (retval)
-       {
-               ereport(LOG,
-                               (errmsg("Kerberos unparse_name returned error %d",
-                                               retval)));
-               com_err("postgres", retval, "while unparsing client name");
-               krb5_free_ticket(pg_krb5_context, ticket);
-               krb5_auth_con_free(pg_krb5_context, auth_context);
-               return STATUS_ERROR;
-       }
 
-       kusername = pg_an_to_ln(kusername);
-       if (strncmp(port->user_name, kusername, SM_DATABASE_USER))
-       {
-               ereport(LOG,
-                               (errmsg("unexpected Kerberos user name received from client (received \"%s\", expected \"%s\")",
-                                               port->user_name, kusername)));
-               ret = STATUS_ERROR;
-       }
-       else
-               ret = STATUS_OK;
+static int pg_GSS_recvauth(Port *port);
+#endif /* ENABLE_GSS */
 
-       krb5_free_ticket(pg_krb5_context, ticket);
-       krb5_auth_con_free(pg_krb5_context, auth_context);
-       free(kusername);
 
-       return ret;
-}
+/*----------------------------------------------------------------
+ * SSPI Authentication
+ *----------------------------------------------------------------
+ */
+#ifdef ENABLE_SSPI
+typedef                SECURITY_STATUS
+                       (WINAPI * QUERY_SECURITY_CONTEXT_TOKEN_FN) (
+                                                                                                          PCtxtHandle, void **);
+static int pg_SSPI_recvauth(Port *port);
+#endif
 
-#else
 
-static int
-pg_krb5_recvauth(Port *port)
-{
-       ereport(LOG,
-                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                        errmsg("Kerberos 5 not implemented on this server")));
-       return STATUS_ERROR;
-}
-#endif   /* KRB5 */
+
+/*----------------------------------------------------------------
+ * Global authentication functions
+ *----------------------------------------------------------------
+ */
 
 
 /*
@@ -348,28 +207,31 @@ auth_failed(Port *port, int status)
 
        /*
         * If we failed due to EOF from client, just quit; there's no point in
-        * trying to send a message to the client, and not much point in
-        * logging the failure in the postmaster log.  (Logging the failure
-        * might be desirable, were it not for the fact that libpq closes the
-        * connection unceremoniously if challenged for a password when it
-        * hasn't got one to send.  We'll get a useless log entry for every
-        * psql connection under password auth, even if it's perfectly
-        * successful, if we log STATUS_EOF events.)
+        * trying to send a message to the client, and not much point in logging
+        * the failure in the postmaster log.  (Logging the failure might be
+        * desirable, were it not for the fact that libpq closes the connection
+        * unceremoniously if challenged for a password when it hasn't got one to
+        * send.  We'll get a useless log entry for every psql connection under
+        * password auth, even if it's perfectly successful, if we log STATUS_EOF
+        * events.)
         */
        if (status == STATUS_EOF)
                proc_exit(0);
 
-       switch (port->auth_method)
+       switch (port->hba->auth_method)
        {
                case uaReject:
                        errstr = gettext_noop("authentication failed for user \"%s\": host rejected");
                        break;
-               case uaKrb4:
-                       errstr = gettext_noop("Kerberos 4 authentication failed for user \"%s\"");
-                       break;
                case uaKrb5:
                        errstr = gettext_noop("Kerberos 5 authentication failed for user \"%s\"");
                        break;
+               case uaGSS:
+                       errstr = gettext_noop("GSSAPI authentication failed for user \"%s\"");
+                       break;
+               case uaSSPI:
+                       errstr = gettext_noop("SSPI authentication failed for user \"%s\"");
+                       break;
                case uaTrust:
                        errstr = gettext_noop("\"trust\" authentication failed for user \"%s\"");
                        break;
@@ -377,15 +239,15 @@ auth_failed(Port *port, int status)
                        errstr = gettext_noop("Ident authentication failed for user \"%s\"");
                        break;
                case uaMD5:
-               case uaCrypt:
                case uaPassword:
                        errstr = gettext_noop("password authentication failed for user \"%s\"");
                        break;
-#ifdef USE_PAM
                case uaPAM:
                        errstr = gettext_noop("PAM authentication failed for user \"%s\"");
                        break;
-#endif   /* USE_PAM */
+               case uaLDAP:
+                       errstr = gettext_noop("LDAP authentication failed for user \"%s\"");
+                       break;
                default:
                        errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method");
                        break;
@@ -409,9 +271,9 @@ ClientAuthentication(Port *port)
 
        /*
         * Get the authentication method to use for this frontend/database
-        * combination.  Note: a failure return indicates a problem with the
-        * hba config file, not with the request.  hba.c should have dropped
-        * an error message into the postmaster logfile if it failed.
+        * combination.  Note: a failure return indicates a problem with the hba
+        * config file, not with the request.  hba.c should have dropped an error
+        * message into the postmaster logfile if it failed.
         */
        if (hba_getauthmethod(port) != STATUS_OK)
                ereport(FATAL,
@@ -419,56 +281,101 @@ ClientAuthentication(Port *port)
                                 errmsg("missing or erroneous pg_hba.conf file"),
                                 errhint("See server log for details.")));
 
-       switch (port->auth_method)
+       /*
+        * This is the first point where we have access to the hba record for
+        * the current connection, so perform any verifications based on the
+        * hba options field that should be done *before* the authentication
+        * here.
+        */
+       if (port->hba->clientcert)
+       {
+               /*
+                * When we parse pg_hba.conf, we have already made sure that we have
+                * been able to load a certificate store. Thus, if a certificate is
+                * present on the client, it has been verified against our root
+                * certificate store, and the connection would have been aborted
+                * already if it didn't verify ok.
+                */
+#ifdef USE_SSL
+               if (!port->peer)
+               {
+                       ereport(FATAL,
+                                       (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+                                        errmsg("connection requires a valid client certificate")));
+               }
+#else
+               /*
+                * hba.c makes sure hba->clientcert can't be set unless OpenSSL
+                * is present.
+                */
+               Assert(false);
+#endif
+       }
+
+       /*
+        * Now proceed to do the actual authentication check
+        */
+       switch (port->hba->auth_method)
        {
                case uaReject:
 
                        /*
                         * This could have come from an explicit "reject" entry in
                         * pg_hba.conf, but more likely it means there was no matching
-                        * entry.  Take pity on the poor user and issue a helpful
-                        * error message.  NOTE: this is not a security breach,
-                        * because all the info reported here is known at the frontend
-                        * and must be assumed known to bad guys. We're merely helping
-                        * out the less clueful good guys.
+                        * entry.  Take pity on the poor user and issue a helpful error
+                        * message.  NOTE: this is not a security breach, because all the
+                        * info reported here is known at the frontend and must be assumed
+                        * known to bad guys. We're merely helping out the less clueful
+                        * good guys.
                         */
                        {
                                char            hostinfo[NI_MAXHOST];
 
-                               getnameinfo_all(&port->raddr.addr, port->raddr.salen,
-                                                               hostinfo, sizeof(hostinfo),
-                                                               NULL, 0,
-                                                               NI_NUMERICHOST);
+                               pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
+                                                                  hostinfo, sizeof(hostinfo),
+                                                                  NULL, 0,
+                                                                  NI_NUMERICHOST);
 
 #ifdef USE_SSL
                                ereport(FATAL,
-                                  (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
-                                       errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
-                                                  hostinfo, port->user_name, port->database_name,
-                                  port->ssl ? gettext("SSL on") : gettext("SSL off"))));
+                                               (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+                                                errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\", %s",
+                                                         hostinfo, port->user_name, port->database_name,
+                                                               port->ssl ? _("SSL on") : _("SSL off"))));
 #else
                                ereport(FATAL,
-                                  (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
-                                       errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
-                                          hostinfo, port->user_name, port->database_name)));
+                                               (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+                                                errmsg("no pg_hba.conf entry for host \"%s\", user \"%s\", database \"%s\"",
+                                                  hostinfo, port->user_name, port->database_name)));
 #endif
                                break;
                        }
 
-               case uaKrb4:
-                       /* Kerberos 4 only seems to work with AF_INET. */
-                       if (port->raddr.addr.ss_family != AF_INET
-                               || port->laddr.addr.ss_family != AF_INET)
-                               ereport(FATAL,
-                                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-                                  errmsg("Kerberos 4 only supports IPv4 connections")));
-                       sendAuthRequest(port, AUTH_REQ_KRB4);
-                       status = pg_krb4_recvauth(port);
-                       break;
-
                case uaKrb5:
+#ifdef KRB5
                        sendAuthRequest(port, AUTH_REQ_KRB5);
                        status = pg_krb5_recvauth(port);
+#else
+                       Assert(false);
+#endif
+                       break;
+
+               case uaGSS:
+#ifdef ENABLE_GSS
+                       sendAuthRequest(port, AUTH_REQ_GSS);
+                       status = pg_GSS_recvauth(port);
+#else
+                       Assert(false);
+#endif
+                       break;
+
+               case uaSSPI:
+#ifdef ENABLE_SSPI
+                       sendAuthRequest(port, AUTH_REQ_SSPI);
+                       status = pg_SSPI_recvauth(port);
+#else
+                       Assert(false);
+#endif
                        break;
 
                case uaIdent:
@@ -494,7 +401,7 @@ ClientAuthentication(Port *port)
                                if (setsockopt(port->sock, 0, LOCAL_CREDS, &on, sizeof(on)) < 0)
                                        ereport(FATAL,
                                                        (errcode_for_socket_access(),
-                                       errmsg("could not enable credential reception: %m")));
+                                          errmsg("could not enable credential reception: %m")));
 #endif
 
                                sendAuthRequest(port, AUTH_REQ_SCM_CREDS);
@@ -504,26 +411,43 @@ ClientAuthentication(Port *port)
                        break;
 
                case uaMD5:
+                       if (Db_user_namespace)
+                               ereport(FATAL,
+                                               (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
+                                                errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
                        sendAuthRequest(port, AUTH_REQ_MD5);
                        status = recv_and_check_password_packet(port);
                        break;
 
-               case uaCrypt:
-                       sendAuthRequest(port, AUTH_REQ_CRYPT);
-                       status = recv_and_check_password_packet(port);
-                       break;
-
                case uaPassword:
                        sendAuthRequest(port, AUTH_REQ_PASSWORD);
                        status = recv_and_check_password_packet(port);
                        break;
 
-#ifdef USE_PAM
                case uaPAM:
+#ifdef USE_PAM
                        pam_port_cludge = port;
                        status = CheckPAMAuth(port, port->user_name, "");
-                       break;
+#else
+                       Assert(false);
 #endif   /* USE_PAM */
+                       break;
+
+               case uaLDAP:
+#ifdef USE_LDAP
+                       status = CheckLDAPAuth(port);
+#else
+                       Assert(false);
+#endif
+                       break;
+
+               case uaCert:
+#ifdef USE_SSL
+                       status = CheckCertAuth(port);
+#else
+                       Assert(false);
+#endif
+                       break;
 
                case uaTrust:
                        status = STATUS_OK;
@@ -551,25 +475,1391 @@ sendAuthRequest(Port *port, AuthRequest areq)
        /* Add the salt for encrypted passwords. */
        if (areq == AUTH_REQ_MD5)
                pq_sendbytes(&buf, port->md5Salt, 4);
-       else if (areq == AUTH_REQ_CRYPT)
-               pq_sendbytes(&buf, port->cryptSalt, 2);
+
+#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
+
+       /*
+        * Add the authentication data for the next step of the GSSAPI or SSPI
+        * negotiation.
+        */
+       else if (areq == AUTH_REQ_GSS_CONT)
+       {
+               if (port->gss->outbuf.length > 0)
+               {
+                       elog(DEBUG4, "sending GSS token of length %u",
+                                (unsigned int) port->gss->outbuf.length);
+
+                       pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length);
+               }
+       }
+#endif
 
        pq_endmessage(&buf);
 
        /*
-        * Flush message so client will see it, except for AUTH_REQ_OK, which
-        * need not be sent until we are ready for queries.
+        * Flush message so client will see it, except for AUTH_REQ_OK, which need
+        * not be sent until we are ready for queries.
         */
        if (areq != AUTH_REQ_OK)
                pq_flush();
 }
 
-
-#ifdef USE_PAM
-
 /*
- * PAM conversation function
- */
+ * Collect password response packet from frontend.
+ *
+ * Returns NULL if couldn't get password, else palloc'd string.
+ */
+static char *
+recv_password_packet(Port *port)
+{
+       StringInfoData buf;
+
+       if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
+       {
+               /* Expect 'p' message type */
+               int                     mtype;
+
+               mtype = pq_getbyte();
+               if (mtype != 'p')
+               {
+                       /*
+                        * If the client just disconnects without offering a password,
+                        * don't make a log entry.  This is legal per protocol spec and in
+                        * fact commonly done by psql, so complaining just clutters the
+                        * log.
+                        */
+                       if (mtype != EOF)
+                               ereport(COMMERROR,
+                                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                       errmsg("expected password response, got message type %d",
+                                                  mtype)));
+                       return NULL;            /* EOF or bad message type */
+               }
+       }
+       else
+       {
+               /* For pre-3.0 clients, avoid log entry if they just disconnect */
+               if (pq_peekbyte() == EOF)
+                       return NULL;            /* EOF */
+       }
+
+       initStringInfo(&buf);
+       if (pq_getmessage(&buf, 1000))          /* receive password */
+       {
+               /* EOF - pq_getmessage already logged a suitable message */
+               pfree(buf.data);
+               return NULL;
+       }
+
+       /*
+        * Apply sanity check: password packet length should agree with length of
+        * contained string.  Note it is safe to use strlen here because
+        * StringInfo is guaranteed to have an appended '\0'.
+        */
+       if (strlen(buf.data) + 1 != buf.len)
+               ereport(COMMERROR,
+                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                errmsg("invalid password packet size")));
+
+       /* Do not echo password to logs, for security. */
+       ereport(DEBUG5,
+                       (errmsg("received password packet")));
+
+       /*
+        * Return the received string.  Note we do not attempt to do any
+        * character-set conversion on it; since we don't yet know the client's
+        * encoding, there wouldn't be much point.
+        */
+       return buf.data;
+}
+
+
+/*----------------------------------------------------------------
+ * MD5 and crypt authentication
+ *----------------------------------------------------------------
+ */
+
+/*
+ * Called when we have sent an authorization request for a password.
+ * Get the response and check it.
+ */
+static int
+recv_and_check_password_packet(Port *port)
+{
+       char       *passwd;
+       int                     result;
+
+       passwd = recv_password_packet(port);
+
+       if (passwd == NULL)
+               return STATUS_EOF;              /* client wouldn't send password */
+
+       result = md5_crypt_verify(port, port->user_name, passwd);
+
+       pfree(passwd);
+
+       return result;
+}
+
+
+/*----------------------------------------------------------------
+ * MIT Kerberos authentication system - protocol version 5
+ *----------------------------------------------------------------
+ */
+#ifdef KRB5
+
+static int
+pg_krb5_init(Port *port)
+{
+       krb5_error_code retval;
+       char       *khostname;
+
+       if (pg_krb5_initialised)
+               return STATUS_OK;
+
+       retval = krb5_init_context(&pg_krb5_context);
+       if (retval)
+       {
+               ereport(LOG,
+                               (errmsg("Kerberos initialization returned error %d",
+                                               retval)));
+               com_err("postgres", retval, "while initializing krb5");
+               return STATUS_ERROR;
+       }
+
+       retval = krb5_kt_resolve(pg_krb5_context, pg_krb_server_keyfile, &pg_krb5_keytab);
+       if (retval)
+       {
+               ereport(LOG,
+                               (errmsg("Kerberos keytab resolving returned error %d",
+                                               retval)));
+               com_err("postgres", retval, "while resolving keytab file \"%s\"",
+                               pg_krb_server_keyfile);
+               krb5_free_context(pg_krb5_context);
+               return STATUS_ERROR;
+       }
+
+       /*
+        * If no hostname was specified, pg_krb_server_hostname is already NULL.
+        * If it's set to blank, force it to NULL.
+        */
+       khostname = port->hba->krb_server_hostname;
+       if (khostname && khostname[0] == '\0')
+               khostname = NULL;
+
+       retval = krb5_sname_to_principal(pg_krb5_context,
+                                                                        khostname,
+                                                                        pg_krb_srvnam,
+                                                                        KRB5_NT_SRV_HST,
+                                                                        &pg_krb5_server);
+       if (retval)
+       {
+               ereport(LOG,
+                               (errmsg("Kerberos sname_to_principal(\"%s\", \"%s\") returned error %d",
+                khostname ? khostname : "server hostname", pg_krb_srvnam, retval)));
+               com_err("postgres", retval,
+               "while getting server principal for server \"%s\" for service \"%s\"",
+                               khostname ? khostname : "server hostname", pg_krb_srvnam);
+               krb5_kt_close(pg_krb5_context, pg_krb5_keytab);
+               krb5_free_context(pg_krb5_context);
+               return STATUS_ERROR;
+       }
+
+       pg_krb5_initialised = 1;
+       return STATUS_OK;
+}
+
+
+/*
+ * pg_krb5_recvauth -- server routine to receive authentication information
+ *                                        from the client
+ *
+ * We still need to compare the username obtained from the client's setup
+ * packet to the authenticated name.
+ *
+ * We have our own keytab file because postgres is unlikely to run as root,
+ * and so cannot read the default keytab.
+ */
+static int
+pg_krb5_recvauth(Port *port)
+{
+       krb5_error_code retval;
+       int                     ret;
+       krb5_auth_context auth_context = NULL;
+       krb5_ticket *ticket;
+       char       *kusername;
+       char       *cp;
+
+       if (get_role_line(port->user_name) == NULL)
+               return STATUS_ERROR;
+
+       ret = pg_krb5_init(port);
+       if (ret != STATUS_OK)
+               return ret;
+
+       retval = krb5_recvauth(pg_krb5_context, &auth_context,
+                                                  (krb5_pointer) & port->sock, pg_krb_srvnam,
+                                                  pg_krb5_server, 0, pg_krb5_keytab, &ticket);
+       if (retval)
+       {
+               ereport(LOG,
+                               (errmsg("Kerberos recvauth returned error %d",
+                                               retval)));
+               com_err("postgres", retval, "from krb5_recvauth");
+               return STATUS_ERROR;
+       }
+
+       /*
+        * The "client" structure comes out of the ticket and is therefore
+        * authenticated.  Use it to check the username obtained from the
+        * postmaster startup packet.
+        */
+#if defined(HAVE_KRB5_TICKET_ENC_PART2)
+       retval = krb5_unparse_name(pg_krb5_context,
+                                                          ticket->enc_part2->client, &kusername);
+#elif defined(HAVE_KRB5_TICKET_CLIENT)
+       retval = krb5_unparse_name(pg_krb5_context,
+                                                          ticket->client, &kusername);
+#else
+#error "bogus configuration"
+#endif
+       if (retval)
+       {
+               ereport(LOG,
+                               (errmsg("Kerberos unparse_name returned error %d",
+                                               retval)));
+               com_err("postgres", retval, "while unparsing client name");
+               krb5_free_ticket(pg_krb5_context, ticket);
+               krb5_auth_con_free(pg_krb5_context, auth_context);
+               return STATUS_ERROR;
+       }
+
+       cp = strchr(kusername, '@');
+       if (cp)
+       {
+               /*
+                * If we are not going to include the realm in the username that is passed
+                * to the ident map, destructively modify it here to remove the realm. Then
+                * advance past the separator to check the realm.
+                */
+               if (!port->hba->include_realm)
+                       *cp = '\0';
+               cp++;
+
+               if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
+               {
+                       /* Match realm against configured */
+                       if (pg_krb_caseins_users)
+                               ret = pg_strcasecmp(port->hba->krb_realm, cp);
+                       else
+                               ret = strcmp(port->hba->krb_realm, cp);
+
+                       if (ret)
+                       {
+                               elog(DEBUG2,
+                                        "krb5 realm (%s) and configured realm (%s) don't match",
+                                        cp, port->hba->krb_realm);
+
+                               krb5_free_ticket(pg_krb5_context, ticket);
+                               krb5_auth_con_free(pg_krb5_context, auth_context);
+                               return STATUS_ERROR;
+                       }
+               }
+       }
+       else if (port->hba->krb_realm&& strlen(port->hba->krb_realm))
+       {
+               elog(DEBUG2,
+                        "krb5 did not return realm but realm matching was requested");
+
+               krb5_free_ticket(pg_krb5_context, ticket);
+               krb5_auth_con_free(pg_krb5_context, auth_context);
+               return STATUS_ERROR;
+       }
+
+       ret = check_usermap(port->hba->usermap, port->user_name, kusername,
+                                               pg_krb_caseins_users);
+
+       krb5_free_ticket(pg_krb5_context, ticket);
+       krb5_auth_con_free(pg_krb5_context, auth_context);
+       free(kusername);
+
+       return ret;
+}
+#endif   /* KRB5 */
+
+
+/*----------------------------------------------------------------
+ * GSSAPI authentication system
+ *----------------------------------------------------------------
+ */
+#ifdef ENABLE_GSS
+
+#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
+/*
+ * MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
+ * that contain the OIDs required. Redefine here, values copied
+ * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
+ */
+static const gss_OID_desc GSS_C_NT_USER_NAME_desc =
+{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"};
+static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc;
+#endif
+
+
+static void
+pg_GSS_error(int severity, char *errmsg, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+       gss_buffer_desc gmsg;
+       OM_uint32       lmaj_s,
+                               lmin_s,
+                               msg_ctx;
+       char            msg_major[128],
+                               msg_minor[128];
+
+       /* Fetch major status message */
+       msg_ctx = 0;
+       lmaj_s = 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;
+       lmaj_s = 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("%s: %s", msg_major, msg_minor)));
+}
+
+static int
+pg_GSS_recvauth(Port *port)
+{
+       OM_uint32       maj_stat,
+                               min_stat,
+                               lmin_s,
+                               gflags;
+       int                     mtype;
+       int                     ret;
+       StringInfoData buf;
+       gss_buffer_desc gbuf;
+
+       /*
+        * GSS auth is not supported for protocol versions before 3, because it
+        * relies on the overall message length word to determine the GSS payload
+        * size in AuthenticationGSSContinue and PasswordMessage messages.
+        * (This is, in fact, a design error in our GSS support, because protocol
+        * messages are supposed to be parsable without relying on the length
+        * word; but it's not worth changing it now.)
+        */
+       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+               ereport(FATAL,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("GSSAPI is not supported in protocol version 2")));
+
+       if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0)
+       {
+               /*
+                * Set default Kerberos keytab file for the Krb5 mechanism.
+                *
+                * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); except setenv()
+                * not always available.
+                */
+               if (getenv("KRB5_KTNAME") == NULL)
+               {
+                       size_t  kt_len = strlen(pg_krb_server_keyfile) + 14;
+                       char   *kt_path = malloc(kt_len);
+
+                       if (!kt_path)
+                       {
+                               ereport(LOG,
+                                               (errcode(ERRCODE_OUT_OF_MEMORY),
+                                                errmsg("out of memory")));
+                               return STATUS_ERROR;
+                       }
+                       snprintf(kt_path, kt_len, "KRB5_KTNAME=%s", pg_krb_server_keyfile);
+                       putenv(kt_path);
+               }
+       }
+
+       /*
+        * We accept any service principal that's present in our keytab. This
+        * increases interoperability between kerberos implementations that see
+        * for example case sensitivity differently, while not really opening up
+        * any vector of attack.
+        */
+       port->gss->cred = GSS_C_NO_CREDENTIAL;
+
+       /*
+        * Initialize sequence with an empty context
+        */
+       port->gss->ctx = GSS_C_NO_CONTEXT;
+
+       /*
+        * Loop through GSSAPI message exchange. This exchange can consist of
+        * multiple messags sent in both directions. First message is always from
+        * the client. All messages from client to server are password packets
+        * (type 'p').
+        */
+       do
+       {
+               mtype = pq_getbyte();
+               if (mtype != 'p')
+               {
+                       /* Only log error if client didn't disconnect. */
+                       if (mtype != EOF)
+                               ereport(COMMERROR,
+                                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                                errmsg("expected GSS response, got message type %d",
+                                                               mtype)));
+                       return STATUS_ERROR;
+               }
+
+               /* Get the actual GSS token */
+               initStringInfo(&buf);
+               if (pq_getmessage(&buf, 2000))
+               {
+                       /* EOF - pq_getmessage already logged error */
+                       pfree(buf.data);
+                       return STATUS_ERROR;
+               }
+
+               /* Map to GSSAPI style buffer */
+               gbuf.length = buf.len;
+               gbuf.value = buf.data;
+
+               elog(DEBUG4, "Processing received GSS token of length %u",
+                        (unsigned int) gbuf.length);
+
+               maj_stat = gss_accept_sec_context(
+                                                                                 &min_stat,
+                                                                                 &port->gss->ctx,
+                                                                                 port->gss->cred,
+                                                                                 &gbuf,
+                                                                                 GSS_C_NO_CHANNEL_BINDINGS,
+                                                                                 &port->gss->name,
+                                                                                 NULL,
+                                                                                 &port->gss->outbuf,
+                                                                                 &gflags,
+                                                                                 NULL,
+                                                                                 NULL);
+
+               /* gbuf no longer used */
+               pfree(buf.data);
+
+               elog(DEBUG5, "gss_accept_sec_context major: %d, "
+                        "minor: %d, outlen: %u, outflags: %x",
+                        maj_stat, min_stat,
+                        (unsigned int) port->gss->outbuf.length, gflags);
+
+               if (port->gss->outbuf.length != 0)
+               {
+                       /*
+                        * Negotiation generated data to be sent to the client.
+                        */
+                       OM_uint32       lmin_s;
+
+                       elog(DEBUG4, "sending GSS response token of length %u",
+                                (unsigned int) port->gss->outbuf.length);
+
+                       sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+
+                       gss_release_buffer(&lmin_s, &port->gss->outbuf);
+               }
+
+               if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
+               {
+                       OM_uint32       lmin_s;
+
+                       gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER);
+                       pg_GSS_error(ERROR,
+                                          gettext_noop("accepting GSS security context failed"),
+                                                maj_stat, min_stat);
+               }
+
+               if (maj_stat == GSS_S_CONTINUE_NEEDED)
+                       elog(DEBUG4, "GSS continue needed");
+
+       } while (maj_stat == GSS_S_CONTINUE_NEEDED);
+
+       if (port->gss->cred != GSS_C_NO_CREDENTIAL)
+       {
+               /*
+                * Release service principal credentials
+                */
+               gss_release_cred(&min_stat, &port->gss->cred);
+       }
+
+       /*
+        * 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.
+        */
+       maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL);
+       if (maj_stat != GSS_S_COMPLETE)
+               pg_GSS_error(ERROR,
+                                        gettext_noop("retrieving GSS user name failed"),
+                                        maj_stat, min_stat);
+
+       /*
+        * Split the username at the realm separator
+        */
+       if (strchr(gbuf.value, '@'))
+       {
+               char       *cp = strchr(gbuf.value, '@');
+
+               /*
+                * If we are not going to include the realm in the username that is passed
+                * to the ident map, destructively modify it here to remove the realm. Then
+                * advance past the separator to check the realm.
+                */
+               if (!port->hba->include_realm)
+                       *cp = '\0';
+               cp++;
+
+               if (port->hba->krb_realm != NULL && strlen(port->hba->krb_realm))
+               {
+                       /*
+                        * Match the realm part of the name first
+                        */
+                       if (pg_krb_caseins_users)
+                               ret = pg_strcasecmp(port->hba->krb_realm, cp);
+                       else
+                               ret = strcmp(port->hba->krb_realm, cp);
+
+                       if (ret)
+                       {
+                               /* GSS realm does not match */
+                               elog(DEBUG2,
+                                  "GSSAPI realm (%s) and configured realm (%s) don't match",
+                                        cp, port->hba->krb_realm);
+                               gss_release_buffer(&lmin_s, &gbuf);
+                               return STATUS_ERROR;
+                       }
+               }
+       }
+       else if (port->hba->krb_realm && strlen(port->hba->krb_realm))
+       {
+               elog(DEBUG2,
+                        "GSSAPI did not return realm but realm matching was requested");
+
+               gss_release_buffer(&lmin_s, &gbuf);
+               return STATUS_ERROR;
+       }
+
+       ret = check_usermap(port->hba->usermap, port->user_name, gbuf.value,
+                                               pg_krb_caseins_users);
+
+       gss_release_buffer(&lmin_s, &gbuf);
+
+       return STATUS_OK;
+}
+#endif   /* ENABLE_GSS */
+
+
+/*----------------------------------------------------------------
+ * SSPI authentication system
+ *----------------------------------------------------------------
+ */
+#ifdef ENABLE_SSPI
+static void
+pg_SSPI_error(int severity, char *errmsg, SECURITY_STATUS r)
+{
+       char            sysmsg[256];
+
+       if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, r, 0, sysmsg, sizeof(sysmsg), NULL) == 0)
+               ereport(severity,
+                               (errmsg_internal("%s", errmsg),
+                                errdetail("SSPI error %x", (unsigned int) r)));
+       else
+               ereport(severity,
+                               (errmsg_internal("%s", errmsg),
+                                errdetail("%s (%x)", sysmsg, (unsigned int) r)));
+}
+
+static int
+pg_SSPI_recvauth(Port *port)
+{
+       int                     mtype;
+       StringInfoData buf;
+       SECURITY_STATUS r;
+       CredHandle      sspicred;
+       CtxtHandle *sspictx = NULL,
+                               newctx;
+       TimeStamp       expiry;
+       ULONG           contextattr;
+       SecBufferDesc inbuf;
+       SecBufferDesc outbuf;
+       SecBuffer       OutBuffers[1];
+       SecBuffer       InBuffers[1];
+       HANDLE          token;
+       TOKEN_USER *tokenuser;
+       DWORD           retlen;
+       char            accountname[MAXPGPATH];
+       char            domainname[MAXPGPATH];
+       DWORD           accountnamesize = sizeof(accountname);
+       DWORD           domainnamesize = sizeof(domainname);
+       SID_NAME_USE accountnameuse;
+       HMODULE         secur32;
+       QUERY_SECURITY_CONTEXT_TOKEN_FN _QuerySecurityContextToken;
+
+       /*
+        * SSPI auth is not supported for protocol versions before 3, because it
+        * relies on the overall message length word to determine the SSPI payload
+        * size in AuthenticationGSSContinue and PasswordMessage messages.
+        * (This is, in fact, a design error in our SSPI support, because protocol
+        * messages are supposed to be parsable without relying on the length
+        * word; but it's not worth changing it now.)
+        */
+       if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+               ereport(FATAL,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("SSPI is not supported in protocol version 2")));
+
+       /*
+        * Acquire a handle to the server credentials.
+        */
+       r = AcquireCredentialsHandle(NULL,
+                                                                "negotiate",
+                                                                SECPKG_CRED_INBOUND,
+                                                                NULL,
+                                                                NULL,
+                                                                NULL,
+                                                                NULL,
+                                                                &sspicred,
+                                                                &expiry);
+       if (r != SEC_E_OK)
+               pg_SSPI_error(ERROR,
+                          gettext_noop("could not acquire SSPI credentials handle"), r);
+
+       /*
+        * Loop through SSPI message exchange. This exchange can consist of
+        * multiple messags sent in both directions. First message is always from
+        * the client. All messages from client to server are password packets
+        * (type 'p').
+        */
+       do
+       {
+               mtype = pq_getbyte();
+               if (mtype != 'p')
+               {
+                       /* Only log error if client didn't disconnect. */
+                       if (mtype != EOF)
+                               ereport(COMMERROR,
+                                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
+                                                errmsg("expected SSPI response, got message type %d",
+                                                               mtype)));
+                       return STATUS_ERROR;
+               }
+
+               /* Get the actual SSPI token */
+               initStringInfo(&buf);
+               if (pq_getmessage(&buf, 2000))
+               {
+                       /* EOF - pq_getmessage already logged error */
+                       pfree(buf.data);
+                       return STATUS_ERROR;
+               }
+
+               /* Map to SSPI style buffer */
+               inbuf.ulVersion = SECBUFFER_VERSION;
+               inbuf.cBuffers = 1;
+               inbuf.pBuffers = InBuffers;
+               InBuffers[0].pvBuffer = buf.data;
+               InBuffers[0].cbBuffer = buf.len;
+               InBuffers[0].BufferType = SECBUFFER_TOKEN;
+
+               /* Prepare output buffer */
+               OutBuffers[0].pvBuffer = NULL;
+               OutBuffers[0].BufferType = SECBUFFER_TOKEN;
+               OutBuffers[0].cbBuffer = 0;
+               outbuf.cBuffers = 1;
+               outbuf.pBuffers = OutBuffers;
+               outbuf.ulVersion = SECBUFFER_VERSION;
+
+
+               elog(DEBUG4, "Processing received SSPI token of length %u",
+                        (unsigned int) buf.len);
+
+               r = AcceptSecurityContext(&sspicred,
+                                                                 sspictx,
+                                                                 &inbuf,
+                                                                 ASC_REQ_ALLOCATE_MEMORY,
+                                                                 SECURITY_NETWORK_DREP,
+                                                                 &newctx,
+                                                                 &outbuf,
+                                                                 &contextattr,
+                                                                 NULL);
+
+               /* input buffer no longer used */
+               pfree(buf.data);
+
+               if (outbuf.cBuffers > 0 && outbuf.pBuffers[0].cbBuffer > 0)
+               {
+                       /*
+                        * Negotiation generated data to be sent to the client.
+                        */
+                       elog(DEBUG4, "sending SSPI response token of length %u",
+                                (unsigned int) outbuf.pBuffers[0].cbBuffer);
+
+                       port->gss->outbuf.length = outbuf.pBuffers[0].cbBuffer;
+                       port->gss->outbuf.value = outbuf.pBuffers[0].pvBuffer;
+
+                       sendAuthRequest(port, AUTH_REQ_GSS_CONT);
+
+                       FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
+               }
+
+               if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
+               {
+                       if (sspictx != NULL)
+                       {
+                               DeleteSecurityContext(sspictx);
+                               free(sspictx);
+                       }
+                       FreeCredentialsHandle(&sspicred);
+                       pg_SSPI_error(ERROR,
+                                 gettext_noop("could not accept SSPI security context"), r);
+               }
+
+               if (sspictx == NULL)
+               {
+                       sspictx = malloc(sizeof(CtxtHandle));
+                       if (sspictx == NULL)
+                               ereport(ERROR,
+                                               (errmsg("out of memory")));
+
+                       memcpy(sspictx, &newctx, sizeof(CtxtHandle));
+               }
+
+               if (r == SEC_I_CONTINUE_NEEDED)
+                       elog(DEBUG4, "SSPI continue needed");
+
+       } while (r == SEC_I_CONTINUE_NEEDED);
+
+
+       /*
+        * Release service principal credentials
+        */
+       FreeCredentialsHandle(&sspicred);
+
+
+       /*
+        * SEC_E_OK 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.
+        *
+        * MingW is missing the export for QuerySecurityContextToken in the
+        * secur32 library, so we have to load it dynamically.
+        */
+
+       secur32 = LoadLibrary("SECUR32.DLL");
+       if (secur32 == NULL)
+               ereport(ERROR,
+                               (errmsg_internal("could not load secur32.dll: %d",
+                                                                (int) GetLastError())));
+
+       _QuerySecurityContextToken = (QUERY_SECURITY_CONTEXT_TOKEN_FN)
+               GetProcAddress(secur32, "QuerySecurityContextToken");
+       if (_QuerySecurityContextToken == NULL)
+       {
+               FreeLibrary(secur32);
+               ereport(ERROR,
+                               (errmsg_internal("could not locate QuerySecurityContextToken in secur32.dll: %d",
+                                                                (int) GetLastError())));
+       }
+
+       r = (_QuerySecurityContextToken) (sspictx, &token);
+       if (r != SEC_E_OK)
+       {
+               FreeLibrary(secur32);
+               pg_SSPI_error(ERROR,
+                          gettext_noop("could not get security token from context"), r);
+       }
+
+       FreeLibrary(secur32);
+
+       /*
+        * No longer need the security context, everything from here on uses the
+        * token instead.
+        */
+       DeleteSecurityContext(sspictx);
+       free(sspictx);
+
+       if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
+               ereport(ERROR,
+                        (errmsg_internal("could not get token user size: error code %d",
+                                                         (int) GetLastError())));
+
+       tokenuser = malloc(retlen);
+       if (tokenuser == NULL)
+               ereport(ERROR,
+                               (errmsg("out of memory")));
+
+       if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
+               ereport(ERROR,
+                               (errmsg_internal("could not get user token: error code %d",
+                                                                (int) GetLastError())));
+
+       if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
+                                                 domainname, &domainnamesize, &accountnameuse))
+               ereport(ERROR,
+                         (errmsg_internal("could not lookup acconut sid: error code %d",
+                                                          (int) GetLastError())));
+
+       free(tokenuser);
+
+       /*
+        * Compare realm/domain if requested. In SSPI, always compare case
+        * insensitive.
+        */
+       if (port->hba->krb_realm && strlen(port->hba->krb_realm))
+       {
+               if (pg_strcasecmp(port->hba->krb_realm, domainname))
+               {
+                       elog(DEBUG2,
+                                "SSPI domain (%s) and configured domain (%s) don't match",
+                                domainname, port->hba->krb_realm);
+
+                       return STATUS_ERROR;
+               }
+       }
+
+       /*
+        * We have the username (without domain/realm) in accountname, compare to
+        * the supplied value. In SSPI, always compare case insensitive.
+        *
+        * If set to include realm, append it in <username>@<realm> format.
+        */
+       if (port->hba->include_realm)
+       {
+               char   *namebuf;
+               int             retval;
+
+               namebuf = palloc(strlen(accountname) + strlen(domainname) + 2);
+               sprintf(namebuf, "%s@%s", accountname, domainname);
+               retval = check_usermap(port->hba->usermap, port->user_name, namebuf, true);
+               pfree(namebuf);
+               return retval;
+       }
+       else
+               return check_usermap(port->hba->usermap, port->user_name, accountname, true);
+}
+#endif   /* ENABLE_SSPI */
+
+
+
+/*----------------------------------------------------------------
+ * Ident authentication system
+ *----------------------------------------------------------------
+ */
+
+/*
+ *     Parse the string "*ident_response" as a response from a query to an Ident
+ *     server.  If it's a normal response indicating a user name, return true
+ *     and store the user name at *ident_user. If it's anything else,
+ *     return false.
+ */
+static bool
+interpret_ident_response(const char *ident_response,
+                                                char *ident_user)
+{
+       const char *cursor = ident_response;            /* Cursor into *ident_response */
+
+       /*
+        * Ident's response, in the telnet tradition, should end in crlf (\r\n).
+        */
+       if (strlen(ident_response) < 2)
+               return false;
+       else if (ident_response[strlen(ident_response) - 2] != '\r')
+               return false;
+       else
+       {
+               while (*cursor != ':' && *cursor != '\r')
+                       cursor++;                       /* skip port field */
+
+               if (*cursor != ':')
+                       return false;
+               else
+               {
+                       /* We're positioned to colon before response type field */
+                       char            response_type[80];
+                       int                     i;              /* Index into *response_type */
+
+                       cursor++;                       /* Go over colon */
+                       while (pg_isblank(*cursor))
+                               cursor++;               /* skip blanks */
+                       i = 0;
+                       while (*cursor != ':' && *cursor != '\r' && !pg_isblank(*cursor) &&
+                                  i < (int) (sizeof(response_type) - 1))
+                               response_type[i++] = *cursor++;
+                       response_type[i] = '\0';
+                       while (pg_isblank(*cursor))
+                               cursor++;               /* skip blanks */
+                       if (strcmp(response_type, "USERID") != 0)
+                               return false;
+                       else
+                       {
+                               /*
+                                * It's a USERID response.  Good.  "cursor" should be pointing
+                                * to the colon that precedes the operating system type.
+                                */
+                               if (*cursor != ':')
+                                       return false;
+                               else
+                               {
+                                       cursor++;       /* Go over colon */
+                                       /* Skip over operating system field. */
+                                       while (*cursor != ':' && *cursor != '\r')
+                                               cursor++;
+                                       if (*cursor != ':')
+                                               return false;
+                                       else
+                                       {
+                                               int                     i;      /* Index into *ident_user */
+
+                                               cursor++;               /* Go over colon */
+                                               while (pg_isblank(*cursor))
+                                                       cursor++;       /* skip blanks */
+                                               /* Rest of line is user name.  Copy it over. */
+                                               i = 0;
+                                               while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
+                                                       ident_user[i++] = *cursor++;
+                                               ident_user[i] = '\0';
+                                               return true;
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+
+/*
+ *     Talk to the ident server on host "remote_ip_addr" and find out who
+ *     owns the tcp connection from his port "remote_port" to port
+ *     "local_port_addr" on host "local_ip_addr".      Return the user name the
+ *     ident server gives as "*ident_user".
+ *
+ *     IP addresses and port numbers are in network byte order.
+ *
+ *     But iff we're unable to get the information from ident, return false.
+ */
+static bool
+ident_inet(const SockAddr remote_addr,
+                  const SockAddr local_addr,
+                  char *ident_user)
+{
+       int                     sock_fd,                /* File descriptor for socket on which we talk
+                                                                * to Ident */
+                               rc;                             /* Return code from a locally called function */
+       bool            ident_return;
+       char            remote_addr_s[NI_MAXHOST];
+       char            remote_port[NI_MAXSERV];
+       char            local_addr_s[NI_MAXHOST];
+       char            local_port[NI_MAXSERV];
+       char            ident_port[NI_MAXSERV];
+       char            ident_query[80];
+       char            ident_response[80 + IDENT_USERNAME_MAX];
+       struct addrinfo *ident_serv = NULL,
+                          *la = NULL,
+                               hints;
+
+       /*
+        * Might look a little weird to first convert it to text and then back to
+        * sockaddr, but it's protocol independent.
+        */
+       pg_getnameinfo_all(&remote_addr.addr, remote_addr.salen,
+                                          remote_addr_s, sizeof(remote_addr_s),
+                                          remote_port, sizeof(remote_port),
+                                          NI_NUMERICHOST | NI_NUMERICSERV);
+       pg_getnameinfo_all(&local_addr.addr, local_addr.salen,
+                                          local_addr_s, sizeof(local_addr_s),
+                                          local_port, sizeof(local_port),
+                                          NI_NUMERICHOST | NI_NUMERICSERV);
+
+       snprintf(ident_port, sizeof(ident_port), "%d", IDENT_PORT);
+       hints.ai_flags = AI_NUMERICHOST;
+       hints.ai_family = remote_addr.addr.ss_family;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = 0;
+       hints.ai_addrlen = 0;
+       hints.ai_canonname = NULL;
+       hints.ai_addr = NULL;
+       hints.ai_next = NULL;
+       rc = pg_getaddrinfo_all(remote_addr_s, ident_port, &hints, &ident_serv);
+       if (rc || !ident_serv)
+       {
+               if (ident_serv)
+                       pg_freeaddrinfo_all(hints.ai_family, ident_serv);
+               return false;                   /* we don't expect this to happen */
+       }
+
+       hints.ai_flags = AI_NUMERICHOST;
+       hints.ai_family = local_addr.addr.ss_family;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = 0;
+       hints.ai_addrlen = 0;
+       hints.ai_canonname = NULL;
+       hints.ai_addr = NULL;
+       hints.ai_next = NULL;
+       rc = pg_getaddrinfo_all(local_addr_s, NULL, &hints, &la);
+       if (rc || !la)
+       {
+               if (la)
+                       pg_freeaddrinfo_all(hints.ai_family, la);
+               return false;                   /* we don't expect this to happen */
+       }
+
+       sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype,
+                                        ident_serv->ai_protocol);
+       if (sock_fd < 0)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not create socket for Ident connection: %m")));
+               ident_return = false;
+               goto ident_inet_done;
+       }
+
+       /*
+        * Bind to the address which the client originally contacted, otherwise
+        * the ident server won't be able to match up the right connection. This
+        * is necessary if the PostgreSQL server is running on an IP alias.
+        */
+       rc = bind(sock_fd, la->ai_addr, la->ai_addrlen);
+       if (rc != 0)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not bind to local address \"%s\": %m",
+                                               local_addr_s)));
+               ident_return = false;
+               goto ident_inet_done;
+       }
+
+       rc = connect(sock_fd, ident_serv->ai_addr,
+                                ident_serv->ai_addrlen);
+       if (rc != 0)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not connect to Ident server at address \"%s\", port %s: %m",
+                                               remote_addr_s, ident_port)));
+               ident_return = false;
+               goto ident_inet_done;
+       }
+
+       /* The query we send to the Ident server */
+       snprintf(ident_query, sizeof(ident_query), "%s,%s\r\n",
+                        remote_port, local_port);
+
+       /* loop in case send is interrupted */
+       do
+       {
+               rc = send(sock_fd, ident_query, strlen(ident_query), 0);
+       } while (rc < 0 && errno == EINTR);
+
+       if (rc < 0)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not send query to Ident server at address \"%s\", port %s: %m",
+                                               remote_addr_s, ident_port)));
+               ident_return = false;
+               goto ident_inet_done;
+       }
+
+       do
+       {
+               rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
+       } while (rc < 0 && errno == EINTR);
+
+       if (rc < 0)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not receive response from Ident server at address \"%s\", port %s: %m",
+                                               remote_addr_s, ident_port)));
+               ident_return = false;
+               goto ident_inet_done;
+       }
+
+       ident_response[rc] = '\0';
+       ident_return = interpret_ident_response(ident_response, ident_user);
+       if (!ident_return)
+               ereport(LOG,
+                       (errmsg("invalidly formatted response from Ident server: \"%s\"",
+                                       ident_response)));
+
+ident_inet_done:
+       if (sock_fd >= 0)
+               closesocket(sock_fd);
+       pg_freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv);
+       pg_freeaddrinfo_all(local_addr.addr.ss_family, la);
+       return ident_return;
+}
+
+/*
+ *     Ask kernel about the credentials of the connecting process and
+ *     determine the symbolic name of the corresponding user.
+ *
+ *     Returns either true and the username put into "ident_user",
+ *     or false if we were unable to determine the username.
+ */
+#ifdef HAVE_UNIX_SOCKETS
+
+static bool
+ident_unix(int sock, char *ident_user)
+{
+#if defined(HAVE_GETPEEREID)
+       /* OpenBSD style:  */
+       uid_t           uid;
+       gid_t           gid;
+       struct passwd *pass;
+
+       errno = 0;
+       if (getpeereid(sock, &uid, &gid) != 0)
+       {
+               /* We didn't get a valid credentials struct. */
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not get peer credentials: %m")));
+               return false;
+       }
+
+       pass = getpwuid(uid);
+
+       if (pass == NULL)
+       {
+               ereport(LOG,
+                               (errmsg("local user with ID %d does not exist",
+                                               (int) uid)));
+               return false;
+       }
+
+       strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
+
+       return true;
+#elif defined(SO_PEERCRED)
+       /* Linux style: use getsockopt(SO_PEERCRED) */
+       struct ucred peercred;
+       ACCEPT_TYPE_ARG3 so_len = sizeof(peercred);
+       struct passwd *pass;
+
+       errno = 0;
+       if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0 ||
+               so_len != sizeof(peercred))
+       {
+               /* We didn't get a valid credentials struct. */
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not get peer credentials: %m")));
+               return false;
+       }
+
+       pass = getpwuid(peercred.uid);
+
+       if (pass == NULL)
+       {
+               ereport(LOG,
+                               (errmsg("local user with ID %d does not exist",
+                                               (int) peercred.uid)));
+               return false;
+       }
+
+       strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
+
+       return true;
+#elif defined(HAVE_GETPEERUCRED)
+       /* Solaris > 10 */
+       uid_t           uid;
+       struct passwd *pass;
+       ucred_t    *ucred;
+
+       ucred = NULL; /* must be initialized to NULL */
+       if (getpeerucred(sock, &ucred) == -1)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not get peer credentials: %m")));
+               return false;
+       }
+
+       if ((uid = ucred_geteuid(ucred)) == -1)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not get effective UID from peer credentials: %m")));
+               return false;
+       }
+
+       ucred_free(ucred);
+
+       pass = getpwuid(uid);
+       if (pass == NULL)
+       {
+               ereport(LOG,
+                       (errmsg("local user with ID %d does not exist",
+                                       (int) uid)));
+               return false;
+       }
+
+       strlcpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
+
+       return true;
+#elif defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
+       struct msghdr msg;
+
+/* Credentials structure */
+#if defined(HAVE_STRUCT_CMSGCRED)
+       typedef struct cmsgcred Cred;
+
+#define cruid cmcred_uid
+#elif defined(HAVE_STRUCT_FCRED)
+       typedef struct fcred Cred;
+
+#define cruid fc_uid
+#elif defined(HAVE_STRUCT_SOCKCRED)
+       typedef struct sockcred Cred;
+
+#define cruid sc_uid
+#endif
+       Cred       *cred;
+
+       /* Compute size without padding */
+       char            cmsgmem[ALIGN(sizeof(struct cmsghdr)) + ALIGN(sizeof(Cred))];   /* for NetBSD */
+
+       /* Point to start of first structure */
+       struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem;
+
+       struct iovec iov;
+       char            buf;
+       struct passwd *pw;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = (char *) cmsg;
+       msg.msg_controllen = sizeof(cmsgmem);
+       memset(cmsg, 0, sizeof(cmsgmem));
+
+       /*
+        * The one character which is received here is not meaningful; its
+        * purposes is only to make sure that recvmsg() blocks long enough for the
+        * other side to send its credentials.
+        */
+       iov.iov_base = &buf;
+       iov.iov_len = 1;
+
+       if (recvmsg(sock, &msg, 0) < 0 ||
+               cmsg->cmsg_len < sizeof(cmsgmem) ||
+               cmsg->cmsg_type != SCM_CREDS)
+       {
+               ereport(LOG,
+                               (errcode_for_socket_access(),
+                                errmsg("could not get peer credentials: %m")));
+               return false;
+       }
+
+       cred = (Cred *) CMSG_DATA(cmsg);
+
+       pw = getpwuid(cred->cruid);
+
+       if (pw == NULL)
+       {
+               ereport(LOG,
+                               (errmsg("local user with ID %d does not exist",
+                                               (int) cred->cruid)));
+               return false;
+       }
+
+       strlcpy(ident_user, pw->pw_name, IDENT_USERNAME_MAX + 1);
+
+       return true;
+#else
+       ereport(LOG,
+                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                        errmsg("Ident authentication is not supported on local connections on this platform")));
+
+       return false;
+#endif
+}
+#endif   /* HAVE_UNIX_SOCKETS */
+
+
+/*
+ *     Determine the username of the initiator of the connection described
+ *     by "port".      Then look in the usermap file under the usermap
+ *     port->hba->usermap and see if that user is equivalent to Postgres user
+ *     port->user.
+ *
+ *     Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info).
+ */
+static int
+authident(hbaPort *port)
+{
+       char            ident_user[IDENT_USERNAME_MAX + 1];
+
+       if (get_role_line(port->user_name) == NULL)
+               return STATUS_ERROR;
+
+       switch (port->raddr.addr.ss_family)
+       {
+               case AF_INET:
+#ifdef HAVE_IPV6
+               case AF_INET6:
+#endif
+                       if (!ident_inet(port->raddr, port->laddr, ident_user))
+                               return STATUS_ERROR;
+                       break;
+
+#ifdef HAVE_UNIX_SOCKETS
+               case AF_UNIX:
+                       if (!ident_unix(port->sock, ident_user))
+                               return STATUS_ERROR;
+                       break;
+#endif
+
+               default:
+                       return STATUS_ERROR;
+       }
+
+       return check_usermap(port->hba->usermap, port->user_name, ident_user, false);
+}
+
+
+/*----------------------------------------------------------------
+ * PAM authentication system
+ *----------------------------------------------------------------
+ */
+#ifdef USE_PAM
+
+/*
+ * PAM conversation function
+ */
 
 static int
 pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
@@ -595,15 +1885,15 @@ pam_passwd_conv_proc(int num_msg, const struct pam_message ** msg,
        if (!appdata_ptr)
        {
                /*
-                * Workaround for Solaris 2.6 where the PAM library is broken and
-                * does not pass appdata_ptr to the conversation routine
+                * Workaround for Solaris 2.6 where the PAM library is broken and does
+                * not pass appdata_ptr to the conversation routine
                 */
                appdata_ptr = pam_passwd;
        }
 
        /*
-        * Password wasn't passed to PAM the first time around - let's go ask
-        * the client to send a password, which we then stuff into PAM.
+        * Password wasn't passed to PAM the first time around - let's go ask the
+        * client to send a password, which we then stuff into PAM.
         */
        if (strlen(appdata_ptr) == 0)
        {
@@ -668,8 +1958,8 @@ CheckPAMAuth(Port *port, char *user, char *password)
                                                                                                                 * not allocated */
 
        /* Optionally, one can set the service name in pg_hba.conf */
-       if (port->auth_arg && port->auth_arg[0] != '\0')
-               retval = pam_start(port->auth_arg, "pgsql@",
+       if (port->hba->pamservice && port->hba->pamservice[0] != '\0')
+               retval = pam_start(port->hba->pamservice, "pgsql@",
                                                   &pam_passw_conv, &pamh);
        else
                retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@",
@@ -744,94 +2034,158 @@ CheckPAMAuth(Port *port, char *user, char *password)
 #endif   /* USE_PAM */
 
 
-/*
- * Collect password response packet from frontend.
- *
- * Returns NULL if couldn't get password, else palloc'd string.
+
+/*----------------------------------------------------------------
+ * LDAP authentication system
+ *----------------------------------------------------------------
  */
-static char *
-recv_password_packet(Port *port)
+#ifdef USE_LDAP
+
+static int
+CheckLDAPAuth(Port *port)
 {
-       StringInfoData buf;
+       char       *passwd;
+       LDAP       *ldap;
+       int                     r;
+       int                     ldapversion = LDAP_VERSION3;
+       char            fulluser[NAMEDATALEN + 256 + 1];
 
-       if (PG_PROTOCOL_MAJOR(port->proto) >= 3)
+       if (!port->hba->ldapserver|| port->hba->ldapserver[0] == '\0')
        {
-               /* Expect 'p' message type */
-               int                     mtype;
+               ereport(LOG,
+                               (errmsg("LDAP server not specified")));
+               return STATUS_ERROR;
+       }
 
-               mtype = pq_getbyte();
-               if (mtype != 'p')
-               {
-                       /*
-                        * If the client just disconnects without offering a password,
-                        * don't make a log entry.  This is legal per protocol spec
-                        * and in fact commonly done by psql, so complaining just
-                        * clutters the log.
-                        */
-                       if (mtype != EOF)
-                               ereport(COMMERROR,
-                                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
-                               errmsg("expected password response, got message type %d",
-                                          mtype)));
-                       return NULL;            /* EOF or bad message type */
-               }
+       if (port->hba->ldapport == 0)
+               port->hba->ldapport = LDAP_PORT;
+
+       sendAuthRequest(port, AUTH_REQ_PASSWORD);
+
+       passwd = recv_password_packet(port);
+       if (passwd == NULL)
+               return STATUS_EOF;              /* client wouldn't send password */
+
+       ldap = ldap_init(port->hba->ldapserver, port->hba->ldapport);
+       if (!ldap)
+       {
+#ifndef WIN32
+               ereport(LOG,
+                               (errmsg("could not initialize LDAP: error code %d",
+                                               errno)));
+#else
+               ereport(LOG,
+                               (errmsg("could not initialize LDAP: error code %d",
+                                               (int) LdapGetLastError())));
+#endif
+               return STATUS_ERROR;
        }
-       else
+
+       if ((r = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ldapversion)) != LDAP_SUCCESS)
        {
-               /* For pre-3.0 clients, avoid log entry if they just disconnect */
-               if (pq_peekbyte() == EOF)
-                       return NULL;            /* EOF */
+               ldap_unbind(ldap);
+               ereport(LOG,
+                 (errmsg("could not set LDAP protocol version: error code %d", r)));
+               return STATUS_ERROR;
        }
 
-       initStringInfo(&buf);
-       if (pq_getmessage(&buf, 1000))          /* receive password */
+       if (port->hba->ldaptls)
        {
-               /* EOF - pq_getmessage already logged a suitable message */
-               pfree(buf.data);
-               return NULL;
+#ifndef WIN32
+               if ((r = ldap_start_tls_s(ldap, NULL, NULL)) != LDAP_SUCCESS)
+#else
+               static __ldap_start_tls_sA _ldap_start_tls_sA = NULL;
+
+               if (_ldap_start_tls_sA == NULL)
+               {
+                       /*
+                        * Need to load this function dynamically because it does not
+                        * exist on Windows 2000, and causes a load error for the whole
+                        * exe if referenced.
+                        */
+                       HANDLE          ldaphandle;
+
+                       ldaphandle = LoadLibrary("WLDAP32.DLL");
+                       if (ldaphandle == NULL)
+                       {
+                               /*
+                                * should never happen since we import other files from
+                                * wldap32, but check anyway
+                                */
+                               ldap_unbind(ldap);
+                               ereport(LOG,
+                                               (errmsg("could not load wldap32.dll")));
+                               return STATUS_ERROR;
+                       }
+                       _ldap_start_tls_sA = (__ldap_start_tls_sA) GetProcAddress(ldaphandle, "ldap_start_tls_sA");
+                       if (_ldap_start_tls_sA == NULL)
+                       {
+                               ldap_unbind(ldap);
+                               ereport(LOG,
+                                               (errmsg("could not load function _ldap_start_tls_sA in wldap32.dll"),
+                                                errdetail("LDAP over SSL is not supported on this platform.")));
+                               return STATUS_ERROR;
+                       }
+
+                       /*
+                        * Leak LDAP handle on purpose, because we need the library to
+                        * stay open. This is ok because it will only ever be leaked once
+                        * per process and is automatically cleaned up on process exit.
+                        */
+               }
+               if ((r = _ldap_start_tls_sA(ldap, NULL, NULL, NULL, NULL)) != LDAP_SUCCESS)
+#endif
+               {
+                       ldap_unbind(ldap);
+                       ereport(LOG,
+                        (errmsg("could not start LDAP TLS session: error code %d", r)));
+                       return STATUS_ERROR;
+               }
        }
 
-       /*
-        * Apply sanity check: password packet length should agree with length
-        * of contained string.  Note it is safe to use strlen here because
-        * StringInfo is guaranteed to have an appended '\0'.
-        */
-       if (strlen(buf.data) + 1 != buf.len)
-               ereport(COMMERROR,
-                               (errcode(ERRCODE_PROTOCOL_VIOLATION),
-                                errmsg("invalid password packet size")));
+       snprintf(fulluser, sizeof(fulluser), "%s%s%s",
+                        port->hba->ldapprefix ? port->hba->ldapprefix : "",
+                        port->user_name,
+                        port->hba->ldapsuffix ? port->hba->ldapsuffix : "");
+       fulluser[sizeof(fulluser) - 1] = '\0';
 
-       /* Do not echo password to logs, for security. */
-       ereport(DEBUG5,
-                       (errmsg("received password packet")));
+       r = ldap_simple_bind_s(ldap, fulluser, passwd);
+       ldap_unbind(ldap);
 
-       /*
-        * Return the received string.  Note we do not attempt to do any
-        * character-set conversion on it; since we don't yet know the
-        * client's encoding, there wouldn't be much point.
-        */
-       return buf.data;
+       if (r != LDAP_SUCCESS)
+       {
+               ereport(LOG,
+                               (errmsg("LDAP login failed for user \"%s\" on server \"%s\": error code %d",
+                                               fulluser, port->hba->ldapserver, r)));
+               return STATUS_ERROR;
+       }
+
+       return STATUS_OK;
 }
+#endif   /* USE_LDAP */
 
 
-/*
- * Called when we have sent an authorization request for a password.
- * Get the response and check it.
+/*----------------------------------------------------------------
+ * SSL client certificate authentication
+ *----------------------------------------------------------------
  */
+#ifdef USE_SSL
 static int
-recv_and_check_password_packet(Port *port)
+CheckCertAuth(Port *port)
 {
-       char       *passwd;
-       int                     result;
-
-       passwd = recv_password_packet(port);
-
-       if (passwd == NULL)
-               return STATUS_EOF;              /* client wouldn't send password */
-
-       result = md5_crypt_verify(port, port->user_name, passwd);
+       Assert(port->ssl);
 
-       pfree(passwd);
+       /* Make sure we have received a username in the certificate */
+       if (port->peer_cn == NULL ||
+               strlen(port->peer_cn) <= 0)
+       {
+               ereport(LOG,
+                               (errmsg("Certificate login failed for user \"%s\": client certificate contains no username",
+                                               port->user_name)));
+               return STATUS_ERROR;
+       }
 
-       return result;
+       /* Just pass the certificate CN to the usermap check */
+       return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false);
 }
+#endif