From 4f9bf7fc5a1fcc3b1beac9b2e1d2e693ae7bd796 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 9 Dec 2007 19:01:40 +0000
Subject: [PATCH] Fix up the PQconnectionUsedPassword mess: create a separate
 PQconnectionNeedsPassword function that tells the right thing for whether to
 prompt for a password, and improve PQconnectionUsedPassword so that it checks
 whether the password used by the connection was actually supplied as a
 connection argument, instead of coming from environment or a password file.
 Per bug report from Mark Cave-Ayland and subsequent discussion.

---
 doc/src/sgml/libpq.sgml           | 32 +++++++++++++++++----
 doc/src/sgml/release.sgml         | 20 +++++++++++--
 src/bin/pg_ctl/pg_ctl.c           |  4 +--
 src/bin/pg_dump/pg_backup_db.c    |  6 ++--
 src/bin/pg_dump/pg_dumpall.c      |  4 +--
 src/bin/psql/command.c            |  4 +--
 src/bin/psql/startup.c            |  4 +--
 src/bin/scripts/common.c          |  4 +--
 src/interfaces/libpq/exports.txt  |  3 +-
 src/interfaces/libpq/fe-auth.c    |  5 ++--
 src/interfaces/libpq/fe-connect.c | 47 ++++++++++++++++++++++---------
 src/interfaces/libpq/libpq-fe.h   |  5 ++--
 src/interfaces/libpq/libpq-int.h  |  5 ++--
 13 files changed, 102 insertions(+), 41 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 3acbe2c3d30..fec498c19a8 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.247 2007/11/28 15:42:31 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/libpq.sgml,v 1.248 2007/12/09 19:01:40 tgl Exp $ -->
 
 <chapter id="libpq">
  <title><application>libpq</application> - C Library</title>
@@ -1145,12 +1145,33 @@ typedef struct
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><function>PQconnectionNeedsPassword</function><indexterm><primary>PQconnectionNeedsPassword</></></term>
+     <listitem>
+      <para>
+       Returns true (1) if the connection authentication method
+       required a password, but none was available.
+       Returns false (0) if not.
+
+       <synopsis>
+        int PQconnectionNeedsPassword(const PGconn *conn);
+       </synopsis>
+
+      </para>
+
+      <para>
+       This function can be applied after a failed connection attempt
+       to decide whether to prompt the user for a password.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><function>PQconnectionUsedPassword</function><indexterm><primary>PQconnectionUsedPassword</></></term>
      <listitem>
       <para>
        Returns true (1) if the connection authentication method
-       required a password to be supplied. Returns false (0) if not.
+       used a caller-supplied password. Returns false (0) if not.
 
        <synopsis>
         int PQconnectionUsedPassword(const PGconn *conn);
@@ -1159,9 +1180,10 @@ typedef struct
       </para>
 
       <para>
-       This function can be applied after either successful or failed
-       connection attempts.  In the case of failure, it can for example
-       be used to decide whether to prompt the user for a password.
+       This function detects whether a password supplied to the connection
+       function was actually used.  Passwords obtained from other
+       sources (such as the <filename>.pgpass</> file) are not considered
+       caller-supplied.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml
index ef4373fe6d6..4bb10cbf611 100644
--- a/doc/src/sgml/release.sgml
+++ b/doc/src/sgml/release.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.563 2007/12/08 17:24:03 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/release.sgml,v 1.564 2007/12/09 19:01:40 tgl Exp $ -->
 <!--
 
 Typical markup:
@@ -2184,8 +2184,9 @@ current_date &lt; 2017-11-17
 
      <listitem>
       <para>
-       Add <function>PQconnectionUsedPassword()</function> that returns
-       true if the server required a password (Joe Conway)
+       Add <function>PQconnectionNeedsPassword()</function> that returns
+       true if the server required a password but none was supplied
+       (Joe Conway, Tom)
       </para>
 
       <para>
@@ -2197,6 +2198,19 @@ current_date &lt; 2017-11-17
       </para>
      </listitem>
 
+     <listitem>
+      <para>
+       Add <function>PQconnectionUsedPassword()</function> that returns
+       true if the supplied password was actually used
+       (Joe Conway, Tom)
+      </para>
+
+      <para>
+       This is useful in some security contexts where it is important
+       to know whether a user-supplied password is actually valid.
+      </para>
+     </listitem>
+
     </itemizedlist>
 
    </sect3>
diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c
index 849287fca76..b83e52a87ba 100644
--- a/src/bin/pg_ctl/pg_ctl.c
+++ b/src/bin/pg_ctl/pg_ctl.c
@@ -4,7 +4,7 @@
  *
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.90 2007/11/20 19:24:26 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_ctl/pg_ctl.c,v 1.91 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -492,7 +492,7 @@ test_postmaster_connection(bool do_checkpoint)
 	{
 		if ((conn = PQconnectdb(connstr)) != NULL &&
 			(PQstatus(conn) == CONNECTION_OK ||
-			 PQconnectionUsedPassword(conn)))
+			 PQconnectionNeedsPassword(conn)))
 		{
 			PQfinish(conn);
 			success = true;
diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c
index 5f8039e9827..d3e7959d817 100644
--- a/src/bin/pg_dump/pg_backup_db.c
+++ b/src/bin/pg_dump/pg_backup_db.c
@@ -5,7 +5,7 @@
  *	Implements the basic DB functions used by the archiver.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.76 2007/07/08 19:07:38 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.77 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -159,7 +159,7 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
 
 		if (PQstatus(newConn) == CONNECTION_BAD)
 		{
-			if (!PQconnectionUsedPassword(newConn))
+			if (!PQconnectionNeedsPassword(newConn))
 				die_horribly(AH, modulename, "could not reconnect to database: %s",
 							 PQerrorMessage(newConn));
 			PQfinish(newConn);
@@ -234,7 +234,7 @@ ConnectDatabase(Archive *AHX,
 			die_horribly(AH, modulename, "failed to connect to database\n");
 
 		if (PQstatus(AH->connection) == CONNECTION_BAD &&
-			PQconnectionUsedPassword(AH->connection) &&
+			PQconnectionNeedsPassword(AH->connection) &&
 			password == NULL &&
 			!feof(stdin))
 		{
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index bb01994ebf8..7ed48d110e5 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.98 2007/11/15 21:14:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dumpall.c,v 1.99 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1336,7 +1336,7 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
 		}
 
 		if (PQstatus(conn) == CONNECTION_BAD &&
-			PQconnectionUsedPassword(conn) &&
+			PQconnectionNeedsPassword(conn) &&
 			password == NULL &&
 			!feof(stdin))
 		{
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 2fa68972ff3..19d9d5141e7 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.183 2007/11/15 21:14:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.184 2007/12/09 19:01:40 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "command.h"
@@ -1164,7 +1164,7 @@ do_connect(char *dbname, char *user, char *host, char *port)
 		 * Connection attempt failed; either retry the connection attempt with
 		 * a new password, or give up.
 		 */
-		if (!password && PQconnectionUsedPassword(n_conn))
+		if (!password && PQconnectionNeedsPassword(n_conn))
 		{
 			PQfinish(n_conn);
 			password = prompt_for_password(user);
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 65c2e1d906d..2305d4bdb11 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2007, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.141 2007/07/08 19:07:38 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.142 2007/12/09 19:01:40 tgl Exp $
  */
 #include "postgres_fe.h"
 
@@ -211,7 +211,7 @@ main(int argc, char *argv[])
 							   username, password);
 
 		if (PQstatus(pset.db) == CONNECTION_BAD &&
-			PQconnectionUsedPassword(pset.db) &&
+			PQconnectionNeedsPassword(pset.db) &&
 			password == NULL &&
 			!feof(stdin))
 		{
diff --git a/src/bin/scripts/common.c b/src/bin/scripts/common.c
index 5e815e57f4b..6e20f659a39 100644
--- a/src/bin/scripts/common.c
+++ b/src/bin/scripts/common.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/scripts/common.c,v 1.29 2007/11/15 21:14:42 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/scripts/common.c,v 1.30 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -123,7 +123,7 @@ connectDatabase(const char *dbname, const char *pghost, const char *pgport,
 		}
 
 		if (PQstatus(conn) == CONNECTION_BAD &&
-			PQconnectionUsedPassword(conn) &&
+			PQconnectionNeedsPassword(conn) &&
 			password == NULL &&
 			!feof(stdin))
 		{
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 222a6595b43..2b7b8fe94b7 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.17 2007/10/13 20:18:41 tgl Exp $
+# $PostgreSQL: pgsql/src/interfaces/libpq/exports.txt,v 1.18 2007/12/09 19:01:40 tgl Exp $
 # Functions to be exported by libpq DLLs
 PQconnectdb               1
 PQsetdbLogin              2
@@ -139,3 +139,4 @@ PQsendDescribePortal      136
 lo_truncate               137
 PQconnectionUsedPassword  138
 pg_valid_server_encoding_id 139
+PQconnectionNeedsPassword 140
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index f3a177c985e..0c0fc1c4908 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-auth.c,v 1.134 2007/12/04 13:02:53 mha Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-auth.c,v 1.135 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -954,7 +954,8 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
 		case AUTH_REQ_MD5:
 		case AUTH_REQ_CRYPT:
 		case AUTH_REQ_PASSWORD:
-			if (conn->pgpass == NULL || *conn->pgpass == '\0')
+			conn->password_needed = true;
+			if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
 			{
 				printfPQExpBuffer(&conn->errorMessage,
 								  PQnoPasswordSupplied);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 65033b5af55..dda32c5158c 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.353 2007/11/15 21:14:46 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/interfaces/libpq/fe-connect.c,v 1.354 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -232,7 +232,7 @@ static PGconn *makeEmptyPGconn(void);
 static void freePGconn(PGconn *conn);
 static void closePGconn(PGconn *conn);
 static PQconninfoOption *conninfo_parse(const char *conninfo,
-			   PQExpBuffer errorMessage);
+			   PQExpBuffer errorMessage, bool *password_from_string);
 static char *conninfo_getval(PQconninfoOption *connOptions,
 				const char *keyword);
 static void defaultNoticeReceiver(void *arg, const PGresult *res);
@@ -376,7 +376,8 @@ connectOptions1(PGconn *conn, const char *conninfo)
 	/*
 	 * Parse the conninfo string
 	 */
-	connOptions = conninfo_parse(conninfo, &conn->errorMessage);
+	connOptions = conninfo_parse(conninfo, &conn->errorMessage,
+								 &conn->pgpass_from_client);
 	if (connOptions == NULL)
 	{
 		conn->status = CONNECTION_BAD;
@@ -472,6 +473,7 @@ connectOptions2(PGconn *conn)
 										conn->dbName, conn->pguser);
 		if (conn->pgpass == NULL)
 			conn->pgpass = strdup(DefaultPassword);
+		conn->pgpass_from_client = false;
 	}
 
 	/*
@@ -555,10 +557,11 @@ PQconninfoOption *
 PQconndefaults(void)
 {
 	PQExpBufferData errorBuf;
+	bool		password_from_string;
 	PQconninfoOption *connOptions;
 
 	initPQExpBuffer(&errorBuf);
-	connOptions = conninfo_parse("", &errorBuf);
+	connOptions = conninfo_parse("", &errorBuf, &password_from_string);
 	termPQExpBuffer(&errorBuf);
 	return connOptions;
 }
@@ -659,6 +662,7 @@ PQsetdbLogin(const char *pghost, const char *pgport, const char *pgoptions,
 		if (conn->pgpass)
 			free(conn->pgpass);
 		conn->pgpass = strdup(pwd);
+		conn->pgpass_from_client = true;
 	}
 
 	/*
@@ -1718,10 +1722,6 @@ keep_going:						/* We will come back to here until there is
 				 */
 				conn->inStart = conn->inCursor;
 
-				/* Save the authentication request type, if first one. */
-				if (conn->areq == AUTH_REQ_OK)
-					conn->areq = areq;
-
 				/* Respond to the request if necessary. */
 
 				/*
@@ -1924,7 +1924,7 @@ makeEmptyPGconn(void)
 	conn->std_strings = false;	/* unless server says differently */
 	conn->verbosity = PQERRORS_DEFAULT;
 	conn->sock = -1;
-	conn->areq = AUTH_REQ_OK;	/* until we receive something else */
+	conn->password_needed = false;
 #ifdef USE_SSL
 	conn->allow_ssl_try = true;
 	conn->wait_ssl_try = false;
@@ -3064,9 +3064,12 @@ parseServiceInfo(PQconninfoOption *options, PQExpBuffer errorMessage)
  * If successful, a malloc'd PQconninfoOption array is returned.
  * If not successful, NULL is returned and an error message is
  * left in errorMessage.
+ * *password_from_string is set TRUE if we got a password from the
+ * conninfo string, otherwise FALSE.
  */
 static PQconninfoOption *
-conninfo_parse(const char *conninfo, PQExpBuffer errorMessage)
+conninfo_parse(const char *conninfo, PQExpBuffer errorMessage,
+			   bool *password_from_string)
 {
 	char	   *pname;
 	char	   *pval;
@@ -3077,6 +3080,8 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage)
 	PQconninfoOption *options;
 	PQconninfoOption *option;
 
+	*password_from_string = false;			/* default result */
+
 	/* Make a working copy of PQconninfoOptions */
 	options = malloc(sizeof(PQconninfoOptions));
 	if (options == NULL)
@@ -3233,6 +3238,12 @@ conninfo_parse(const char *conninfo, PQExpBuffer errorMessage)
 			free(buf);
 			return NULL;
 		}
+
+		/*
+		 * Special handling for password
+		 */
+		if (strcmp(option->keyword, "password") == 0)
+			*password_from_string = (option->val[0] != '\0');
 	}
 
 	/* Done with the modifiable input string */
@@ -3475,14 +3486,24 @@ PQbackendPID(const PGconn *conn)
 	return conn->be_pid;
 }
 
+int
+PQconnectionNeedsPassword(const PGconn *conn)
+{
+	if (!conn)
+		return false;
+	if (conn->password_needed &&
+		(conn->pgpass == NULL || conn->pgpass[0] == '\0'))
+		return true;
+	else
+		return false;
+}
+
 int
 PQconnectionUsedPassword(const PGconn *conn)
 {
 	if (!conn)
 		return false;
-	if (conn->areq == AUTH_REQ_MD5 ||
-		conn->areq == AUTH_REQ_CRYPT ||
-		conn->areq == AUTH_REQ_PASSWORD)
+	if (conn->password_needed && conn->pgpass_from_client)
 		return true;
 	else
 		return false;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 9c51c532dc6..143cf17adab 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.139 2007/10/13 20:18:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-fe.h,v 1.140 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -263,6 +263,7 @@ extern int	PQserverVersion(const PGconn *conn);
 extern char *PQerrorMessage(const PGconn *conn);
 extern int	PQsocket(const PGconn *conn);
 extern int	PQbackendPID(const PGconn *conn);
+extern int	PQconnectionNeedsPassword(const PGconn *conn);
 extern int	PQconnectionUsedPassword(const PGconn *conn);
 extern int	PQclientEncoding(const PGconn *conn);
 extern int	PQsetClientEncoding(PGconn *conn, const char *encoding);
@@ -426,7 +427,7 @@ extern void PQfreemem(void *ptr);
 #define PQfreeNotify(ptr) PQfreemem(ptr)
 
 /* Error when no password was given. */
-/* Note: depending on this is deprecated; use PQconnectionUsedPassword(). */
+/* Note: depending on this is deprecated; use PQconnectionNeedsPassword(). */
 #define PQnoPasswordSupplied	"fe_sendauth: no password supplied\n"
 
 /*
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 5a9709b073e..26671d17cfe 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -12,7 +12,7 @@
  * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.127 2007/11/15 21:14:46 momjian Exp $
+ * $PostgreSQL: pgsql/src/interfaces/libpq/libpq-int.h,v 1.128 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -291,6 +291,7 @@ struct pg_conn
 	char	   *dbName;			/* database name */
 	char	   *pguser;			/* Postgres username and password, if any */
 	char	   *pgpass;
+	bool		pgpass_from_client;	/* did password come from connect args? */
 	char	   *sslmode;		/* SSL mode (require,prefer,allow,disable) */
 #if defined(KRB5) || defined(ENABLE_GSS) || defined(ENABLE_SSPI)
 	char	   *krbsrvname;		/* Kerberos service name */
@@ -323,7 +324,7 @@ struct pg_conn
 	SockAddr	raddr;			/* Remote address */
 	ProtocolVersion pversion;	/* FE/BE protocol version in use */
 	int			sversion;		/* server version, e.g. 70401 for 7.4.1 */
-	AuthRequest areq;			/* auth type demanded by server */
+	bool		password_needed;	/* true if server demanded a password */
 
 	/* Transient state needed while establishing connection */
 	struct addrinfo *addrlist;	/* list of possible backend addresses */
-- 
GitLab