PAM authentication:
authorBruce Momjian
Thu, 6 Sep 2001 03:23:38 +0000 (03:23 +0000)
committerBruce Momjian
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
.  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  is required for OpenSSL])])
 fi
 
+if test "$with_pam" = yes ; then
+  AC_CHECK_HEADER([security/pam_appl.h], [], [AC_MSG_ERROR([header file  is required for PAM])])
+fi
+
 
 ##
 ## Types, structures, compiler characteristics
index 76cba4075151575c1dc535bda3534f0ac637f3f2..86aab400f7b71b5d2fe43763a6fbe4a1c5c2746c 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
 
  Client Authentication
@@ -278,6 +278,27 @@ hostssl database IP-address
          
         
        
+
+       
+        pam
+        
+         
+          This authentication type operates similar to
+          password, with the main difference that
+          it will use PAM (Pluggable Authentication Modules) as the
+          authentication mechanism. The authentication
+          option following the pam keyword
+          specifies the service name that will be passed to PAM. The
+          default service name is postgresql.
+          For more information about PAM, please read 
+          url="http://www.kernel.org/pub/linux/libs/pam/">Linux-PAM
+          Page and 
+          url="http://www.sun.com/software/solaris/pam/">Solaris-PAM
+          Page.
+         
+        
+       
+
       
 
       
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 
+#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;