From 04c1f729202b00cf053535587820cd913dbddfc1 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Thu, 6 Sep 2001 03:23:38 +0000
Subject: [PATCH] PAM authentication:

> 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                         |  27 +++-
 doc/src/sgml/client-auth.sgml        |  23 ++-
 src/backend/libpq/auth.c             | 233 +++++++++++++++++++++++++--
 src/backend/libpq/hba.c              |   7 +-
 src/backend/libpq/pg_hba.conf.sample |   7 +-
 src/include/libpq/hba.h              |   5 +-
 src/include/pg_config.h.in           |   5 +-
 7 files changed, 290 insertions(+), 17 deletions(-)

diff --git a/configure.in b/configure.in
index cb3d9bd9e8..b1fd48218a 100644
--- a/configure.in
+++ b/configure.in
@@ -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
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 76cba40751..86aab400f7 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -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>
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index adcb881c1c..56c1d01c5c 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -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;
 
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 9dddb61754..69f88c257f 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -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)
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index c348b7668c..0aff0f43fc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -148,7 +148,12 @@
 #		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
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 02eee16d9a..6525d5ecb3 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -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,
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 8a165be62d..30a3834261 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -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;