]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
PAM authentication:
authorBruce Momjian <bruce@momjian.us>
Thu, 6 Sep 2001 03:23:38 +0000 (03:23 +0000)
committerBruce Momjian <bruce@momjian.us>
Thu, 6 Sep 2001 03:23:38 +0000 (03:23 +0000)
> pam_strerror() should be used a few more times, rather than just saying
> "Error!".  Also, the configure.in snippet seems wrong.  You add
> -I$pam_prefix/include/security to $INCLUDES and then you #include
> <security/pam_appl.h>.  This whole thing is probably unnecessary, since
> PAM is a system library on the systems where it exists, so the headers
> and libraries are found automatically, unlike OpenSSL and
> Kerberos.

See attached revised patch. (I'm sure the configure.in stuff can be done
right/better, I'm just not enough of a autoconf guru to know what to
change it to.)

Dominic J. Eidson

configure.in
doc/src/sgml/client-auth.sgml
src/backend/libpq/auth.c
src/backend/libpq/hba.c
src/backend/libpq/pg_hba.conf.sample
src/include/libpq/hba.h
src/include/pg_config.h.in

index cb3d9bd9e8c229c32b23946d9f44d4d93d958467..b1fd48218aaae1171d927f4a5dddab3f5f16c6b3 100644 (file)
@@ -432,7 +432,6 @@ PGAC_ARG_BOOL(with, perl, no, [  --with-perl             build Perl interface an
 AC_MSG_RESULT([$with_perl])
 AC_SUBST(with_perl)
 
-
 #
 # Optionally build Python interface module
 #
@@ -529,6 +528,23 @@ AC_DEFINE_UNQUOTED([PG_KRB_SRVNAM], ["$with_krb_srvnam"],
                    [The name of the PostgreSQL service principal in Kerberos])
 
 
+#
+# PAM
+#
+AC_MSG_CHECKING([whether to build with PAM support])
+PGAC_ARG_OPTARG(with, pam,
+                [  --with-pam[=DIR]        build with PAM support [/usr]],
+                [pam_prefix=/usr],
+                [pam_prefix=$withval],
+[
+  AC_MSG_RESULT([yes])
+  AC_DEFINE([USE_PAM], 1, [Define to build with PAM support])
+
+],
+[AC_MSG_RESULT(no)])
+
+AC_SUBST(with_pam)
+
 
 #
 # OpenSSL
@@ -752,11 +768,14 @@ if test "$with_openssl" = yes ; then
   AC_CHECK_LIB(ssl,    [SSL_library_init], [], [AC_MSG_ERROR([library 'ssl' is required for OpenSSL])])
 fi
 
+if test "$with_pam" = yes ; then
+  AC_CHECK_LIB(pam,    [pam_start], [], [AC_MSG_ERROR([library 'pam' is required for PAM])])
+fi
+
 if test "$enable_nls" = yes ; then
   PGAC_CHECK_GETTEXT
 fi
 
-
 ##
 ## Header files
 ##
@@ -794,6 +813,10 @@ if test "$with_openssl" = yes ; then
   AC_CHECK_HEADER([openssl/err.h], [], [AC_MSG_ERROR([header file <openssl/err.h> is required for OpenSSL])])
 fi
 
+if test "$with_pam" = yes ; then
+  AC_CHECK_HEADER([security/pam_appl.h], [], [AC_MSG_ERROR([header file <security/pam_appl.h> is required for PAM])])
+fi
+
 
 ##
 ## Types, structures, compiler characteristics
index 76cba4075151575c1dc535bda3534f0ac637f3f2..86aab400f7b71b5d2fe43763a6fbe4a1c5c2746c 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $Header: /cvsroot/pgsql/doc/src/sgml/client-auth.sgml,v 1.17 2001/08/16 16:24:15 momjian Exp $ -->
+<!-- $Header: /cvsroot/pgsql/doc/src/sgml/client-auth.sgml,v 1.18 2001/09/06 03:23:38 momjian Exp $ -->
 
 <chapter id="client-authentication">
  <title>Client Authentication</title>
@@ -278,6 +278,27 @@ hostssl <replaceable>database</replaceable> <replaceable>IP-address</replaceable
          </para>
         </listitem>
        </varlistentry>
+
+       <varlistentry>
+        <term>pam</term>
+        <listitem>
+         <para>
+          This authentication type operates similar to
+          <firstterm>password</firstterm>, with the main difference that
+          it will use PAM (Pluggable Authentication Modules) as the
+          authentication mechanism. The <replaceable>authentication
+          option</replaceable> following the <literal>pam</> keyword
+          specifies the service name that will be passed to PAM. The
+          default service name is <firstterm>postgresql</firstterm>.
+          For more information about PAM, please read <ulink
+          url="http://www.kernel.org/pub/linux/libs/pam/">Linux-PAM
+          Page</ulink> and <ulink 
+          url="http://www.sun.com/software/solaris/pam/">Solaris-PAM
+          Page</ulink>.
+         </para>
+        </listitem>
+       </varlistentry>
+
       </variablelist>
 
       </para>
index adcb881c1ce103b6f25356ae4a9a1f2b0cc4989e..56c1d01c5c0c9902c7ce930b9ad98343a534f913 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.64 2001/08/21 15:21:25 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.65 2001/09/06 03:23:38 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -43,6 +43,24 @@ static int   recv_and_check_passwordv0(Port *port);
 
 char      *pg_krb_server_keyfile;
 
+#ifdef USE_PAM
+#include <security/pam_appl.h>
+#define PGSQL_PAM_SERVICE "postgresql"  /* Service name passed to PAM */
+
+static int      CheckPAMAuth(Port *port, char *user, char *password);
+static int      pam_passwd_conv_proc(int num_msg, const struct pam_message **msg,
+                struct pam_response **resp, void *appdata_ptr);
+
+static struct pam_conv pam_passw_conv = {
+    &pam_passwd_conv_proc,
+    NULL
+};
+
+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 */
+#endif /* USE_PAM */
 
 #ifdef KRB4
 /*----------------------------------------------------------------
@@ -428,6 +446,11 @@ auth_failed(Port *port)
                case uaPassword:
                        authmethod = "Password";
                        break;
+#ifdef USE_PAM
+               case uaPAM:
+                       authmethod = "PAM";
+                       break;
+#endif /* USE_PAM */
        }
 
        elog(FATAL, "%s authentication failed for user \"%s\"",
@@ -525,15 +548,21 @@ ClientAuthentication(Port *port)
                        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);
+                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:
+                       pam_port_cludge = port;
+                       status = CheckPAMAuth(port, port->user, "");
                        break;
+#endif /* USE_PAM */
 
                case uaTrust:
                        status = STATUS_OK;
@@ -577,7 +606,190 @@ sendAuthRequest(Port *port, AuthRequest areq)
        pq_flush();
 }
 
+#ifdef USE_PAM
+
+/*
+ * PAM conversation function
+ */
+
+static int
+pam_passwd_conv_proc (int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
+{
+       StringInfoData buf;
+       int32 len;
+
+       if (num_msg != 1 || msg[0]->msg_style != PAM_PROMPT_ECHO_OFF) {
+               switch(msg[0]->msg_style) {
+                       case PAM_ERROR_MSG:
+                               snprintf(PQerrormsg, PQERRORMSG_LENGTH, 
+                                       "pam_passwd_conv_proc: Error from underlying PAM layer: '%s'\n", msg[0]->msg);
+                               fputs(PQerrormsg, stderr);
+                               pqdebug("%s", PQerrormsg);
+                               return PAM_CONV_ERR;
+                       default:
+                               snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                                       "pam_passwd_conv_proc: Unexpected PAM conversation %d/'%s'\n", 
+                                       msg[0]->msg_style, msg[0]->msg);
+                               fputs(PQerrormsg, stderr);
+                               pqdebug("%s", PQerrormsg);
+                               return PAM_CONV_ERR;
+               }
+       }
+
+       if (!appdata_ptr) {
+               /* 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.
+        */
+       if(strlen(appdata_ptr) == 0) {
+               sendAuthRequest(pam_port_cludge, AUTH_REQ_PASSWORD);
+               if (pq_eof() == EOF || pq_getint(&len, 4) == EOF) {
+                       return PAM_CONV_ERR;    /* client didn't want to send password */
+               }
+
+               initStringInfo(&buf);
+               pq_getstr(&buf);
+               if (DebugLvl)
+                       fprintf(stderr, "received PAM packet with len=%d, pw=%s\n",
+                               len, buf.data);
+
+               if(strlen(buf.data) == 0) {
+                       snprintf(PQerrormsg, PQERRORMSG_LENGTH, "pam_passwd_conv_proc: no password\n");
+                       fputs(PQerrormsg, stderr);
+                       return PAM_CONV_ERR;
+               }
+               appdata_ptr = buf.data;
+       }
+
+       /* Explicitly not using palloc here - PAM will free this memory in
+        * pam_end()
+        */
+       *resp = calloc(num_msg, sizeof(struct pam_response));
+       if (!*resp) {
+               snprintf(PQerrormsg, PQERRORMSG_LENGTH, "pam_passwd_conv_proc: Out of memory!\n");
+               fputs(PQerrormsg, stderr);
+               pqdebug("%s", PQerrormsg);
+               if(buf.data)
+                       pfree(buf.data);
+               return PAM_CONV_ERR;
+       }
+
+       (*resp)[0].resp = strdup((char *) appdata_ptr);
+       (*resp)[0].resp_retcode = 0;
+
+       return ((*resp)[0].resp ? PAM_SUCCESS : PAM_CONV_ERR);
+}
+
+
+/*
+ * Check authentication against PAM.
+ */
+static int
+CheckPAMAuth(Port *port, char *user, char *password)
+{
+       int retval;
+       pam_handle_t *pamh = NULL;
+
+       /*
+        * Apparently, Solaris 2.6 is broken, and needs ugly static 
+        * variable workaround
+        */
+       pam_passwd = password;
+
+       /* Set the application data portion of the conversation struct
+        * This is later used inside the PAM conversation to pass the
+        * password to the authentication module.
+        */
+       pam_passw_conv.appdata_ptr = (char*) password;  /* from password above, not allocated */
+
+       /* Optionally, one can set the service name in pg_hba.conf */
+       if(port->auth_arg[0] == '\0') {
+               retval = pam_start(PGSQL_PAM_SERVICE, "pgsql@", &pam_passw_conv, &pamh);
+       } else {
+               retval = pam_start(port->auth_arg, "pgsql@", &pam_passw_conv, &pamh);
+       }
+
+       if (retval != PAM_SUCCESS) {
+               snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                       "CheckPAMAuth: Failed to create PAM authenticator: '%s'\n",
+                       pam_strerror(pamh, retval));
+               fputs(PQerrormsg, stderr);
+               pqdebug("%s", PQerrormsg);  
+               pam_passwd = NULL;      /* Unset pam_passwd */
+               return STATUS_ERROR;
+       }
+
+       if (retval == PAM_SUCCESS) {
+               retval = pam_set_item(pamh, PAM_USER, user);
+       } else {
+               snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                       "CheckPAMAuth: pam_set_item(PAM_USER) failed: '%s'\n",
+                       pam_strerror(pamh, retval));
+               fputs(PQerrormsg, stderr);
+               pqdebug("%s", PQerrormsg);  
+               pam_passwd = NULL;      /* Unset pam_passwd */
+               return STATUS_ERROR;
+       }
+       if (retval == PAM_SUCCESS) {
+               retval = pam_set_item(pamh, PAM_CONV, &pam_passw_conv);
+       } else {
+               snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                       "CheckPAMAuth: pam_set_item(PAM_CONV) failed: '%s'\n",
+                       pam_strerror(pamh, retval));
+               fputs(PQerrormsg, stderr);
+               pqdebug("%s", PQerrormsg);  
+               pam_passwd = NULL;      /* Unset pam_passwd */
+               return STATUS_ERROR;
+       }
+       if (retval == PAM_SUCCESS) {
+               retval = pam_authenticate(pamh, 0);
+       } else {
+               snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                       "CheckPAMAuth: pam_authenticate failed: '%s'\n",
+                       pam_strerror(pamh, retval));
+               fputs(PQerrormsg, stderr);
+               pqdebug("%s", PQerrormsg);  
+               pam_passwd = NULL;      /* Unset pam_passwd */
+               return STATUS_ERROR;
+       }
+       if (retval == PAM_SUCCESS) {
+               retval = pam_acct_mgmt(pamh, 0);
+       } else {
+               snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                       "CheckPAMAuth: pam_acct_mgmt failed: '%s'\n",
+                       pam_strerror(pamh, retval));
+               fputs(PQerrormsg, stderr);
+               pqdebug("%s", PQerrormsg);  
+               pam_passwd = NULL;      /* Unset pam_passwd */
+               return STATUS_ERROR;
+       }
+       if (retval == PAM_SUCCESS) {
+               retval = pam_end(pamh, retval);
+               if(retval != PAM_SUCCESS) {
+                       snprintf(PQerrormsg, PQERRORMSG_LENGTH,
+                               "CheckPAMAuth: Failed to release PAM authenticator: '%s'\n",
+                               pam_strerror(pamh, retval));
+                       fputs(PQerrormsg, stderr);
+                       pqdebug("%s", PQerrormsg);  
+               }
+
+               pam_passwd = NULL;      /* Unset pam_passwd */
+
+               return (retval == PAM_SUCCESS ? STATUS_OK : STATUS_ERROR);
+       } else {
+               return STATUS_ERROR;
+       }
+}
+
+
 
+#endif /* USE_PAM */
 
 /*
  * Called when we have received the password packet.
@@ -670,6 +882,9 @@ map_old_to_new(Port *port, UserAuth old, int status)
                case uaMD5:
                case uaCrypt:
                case uaReject:
+#ifdef USE_PAM
+               case uaPAM:
+#endif /* USE_PAM */
                        status = STATUS_ERROR;
                        break;
 
index 9dddb6175405a860cea1e1041e3e8b76c7446377..69f88c257fdbf427a67bc98976666ec8d3c3a199 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.68 2001/08/21 15:49:17 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.69 2001/09/06 03:23:38 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -235,6 +235,10 @@ parse_hba_auth(List *line, ProtocolVersion proto, UserAuth *userauth_p,
                        *userauth_p = uaMD5;
                else if (strcmp(token, "crypt") == 0)
                        *userauth_p = uaCrypt;
+#ifdef USE_PAM
+               else if (strcmp(token, "pam") == 0)
+                       *userauth_p = uaPAM;
+#endif
                else
                        *error_p = true;
                line = lnext(line);
@@ -277,7 +281,6 @@ parse_hba(List *line, hbaPort *port, bool *found_p, bool *error_p)
        line_number = lfirsti(line);
        line = lnext(line);
        Assert(line != NIL);
-
        /* Check the record type. */
        token = lfirst(line);
        if (strcmp(token, "local") == 0)
index c348b7668c0d44f1aba08f2bbfda1a38da04243f..0aff0f43fcc5294e4abdd6204a09a101f0366f30 100644 (file)
 #              that are part of a network specified later in the file.
 #              To be effective, "reject" must appear before the later
 #              entries.
-# 
+#
+#   pam:        Authentication is passed off to PAM (PostgreSQL must be
+#               configured --with-pam), using the default service name
+#               "postgresql" - you can specify your own service name, by
+#               setting AUTH_ARGUMENT to the desired service name.
+#
 # 
 # 
 # Examples
index 02eee16d9a01f11a03f827eb85ea549346d8ed87..6525d5ecb3f267ba390c10e6ee043781ac2391e8 100644 (file)
@@ -4,7 +4,7 @@
  *       Interface to hba.c
  *
  *
- * $Id: hba.h,v 1.25 2001/08/24 16:59:10 momjian Exp $
+ * $Id: hba.h,v 1.26 2001/09/06 03:23:38 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,6 +31,9 @@
 
 typedef enum UserAuth
 {
+#ifdef USE_PAM
+       uaPAM,
+#endif /* USE_PAM */
        uaReject,
        uaKrb4,
        uaKrb5,
index 8a165be62dbebede2fcc6d69885a2825f2da3e08..30a3834261d3563cac612a573eece4773d8c3615 100644 (file)
@@ -8,7 +8,7 @@
  * or in pg_config.h afterwards.  Of course, if you edit pg_config.h, then your
  * changes will be overwritten the next time you run configure.
  *
- * $Id: pg_config.h.in,v 1.2 2001/09/06 02:56:32 momjian Exp $
+ * $Id: pg_config.h.in,v 1.3 2001/09/06 03:23:38 momjian Exp $
  */
 
 #ifndef PG_CONFIG_H
@@ -63,6 +63,9 @@
 /* Define to build with (Open)SSL support (--with-openssl[=DIR]) */
 #undef USE_SSL
 
+/* Define to build with PAM Support */
+#undef USE_PAM
+
 /* 
  * DEF_PGPORT is the TCP port number on which the Postmaster listens and
  * which clients will try to connect to.  This is just a default value;