Fabien Coelho wrote:
A few detailed comments:

Beware of spacing:
 . "if(" -> "if (" (2 instances)
 . " =='\0')" -> " == '\0')" (at least one instance)

Elsewhere:

 + if (conn->pgpassfile_used && conn->password_needed && conn->result &&
 +     conn->pgpassfile && conn->pgpassfile[0]!='\0' &&

ISTM that if pgpassfile_used is true then pgpassfile is necessarily defined, so the second line tests are redundant? Or am I missing something?
I've adressed those spacing errors.
You are right, if pgpassfile_used is true, it SHOULD be defined, I just like to be careful whenever I'm working with strings. But I guess in this scenario I can trust the caller and omit those checks.


On 11/22/2016 07:04 AM, Mithun Cy wrote:
> Independently of your patch, while testing I concluded that the multi-host feature and documentation should be improved:
 > - If a connection fails, it does not say for which host/port.

Thanks I will re-test same.

> In fact they are tried in turn if the network connection fails, but not
> if the connection fails for some other reason such as the auth.
> The documentation is not precise enough.

If we fail due to authentication, then I think we should notify the client instead of trying next host. I think it is expected genuine user have right credentials for making the connection. So it's better if we notify same to client immediately rather than silently ignoring them. Otherwise the host node which the client failed to connect will be permanently unavailable until client corrects itself. But I agree documentation and error message as you said need improvements. I will try to correct same
I agree with those criticisms of the multi-host feature and notifying the client in case of an authentification error rather than trying other hosts seems sensible to me. But I think fixes for those should be part of different patches, as this patch's aim was only to expand the existing pgpassfile functionality to be used with a parameter.

regards,
Julian
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 0f375bf..c806aeb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -943,7 +943,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
         Note that authentication is likely to fail if <literal>host</>
         is not the name of the server at network address <literal>hostaddr</>.
         Also, note that <literal>host</> rather than <literal>hostaddr</>
-        is used to identify the connection in <filename>~/.pgpass</> (see
+        is used to identify the connection in a password file (see
         <xref linkend="libpq-pgpass">).
        </para>
 
@@ -1002,6 +1002,16 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-pgpassfile" xreflabel="pgpassfile">
+      <term><literal>pgpassfile</literal></term>
+      <listitem>
+      <para>
+        Specifies the name of the file used to lookup passwords.
+        Defaults to the password file (see <xref linkend="libpq-pgpass">).
+      </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout">
       <term><literal>connect_timeout</literal></term>
       <listitem>
@@ -6886,9 +6896,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
       <indexterm>
        <primary><envar>PGPASSFILE</envar></primary>
       </indexterm>
-      <envar>PGPASSFILE</envar> specifies the name of the password file to
-      use for lookups.  If not set, it defaults to <filename>~/.pgpass</>
-      (see <xref linkend="libpq-pgpass">).
+      <envar>PGPASSFILE</envar> behaves the same as the <xref
+      linkend="libpq-connect-pgpassfile"> connection parameter.
      </para>
     </listitem>
 
@@ -7160,13 +7169,15 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
   </indexterm>
 
   <para>
-   The file <filename>.pgpass</filename> in a user's home directory or the
-   file referenced by <envar>PGPASSFILE</envar> can contain passwords to
+   The file <filename>.pgpass</filename> in a user's home directory can contain passwords to
    be used if the connection requires a password (and no password has been
    specified  otherwise). On Microsoft Windows the file is named
    <filename>%APPDATA%\postgresql\pgpass.conf</> (where
    <filename>%APPDATA%</> refers to the Application Data subdirectory in
    the user's profile).
+   Alternatively, a password file can be specified
+   using the connection parameter <xref linkend="libpq-connect-pgpassfile">
+   or the environment variable <envar>PGPASSFILE</envar>.
   </para>
 
   <para>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 3e9c45b..ffc341d 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -184,6 +184,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Database-Password", "*", 20,
 	offsetof(struct pg_conn, pgpass)},
 
+	{"pgpassfile", "PGPASSFILE", NULL, NULL,
+		"Database-Password-File", "", 64,
+	offsetof(struct pg_conn, pgpassfile)},
+
 	{"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL,
 		"Connect-timeout", "", 10,		/* strlen(INT32_MAX) == 10 */
 	offsetof(struct pg_conn, connect_timeout)},
@@ -374,10 +378,10 @@ static int parseServiceFile(const char *serviceFile,
 				 PQExpBuffer errorMessage,
 				 bool *group_found);
 static char *pwdfMatchesString(char *buf, char *token);
-static char *PasswordFromFile(char *hostname, char *port, char *dbname,
-				 char *username);
-static bool getPgPassFilename(char *pgpassfile);
-static void dot_pg_pass_warning(PGconn *conn);
+static char *passwordFromFile(char *hostname, char *port, char *dbname,
+				 char *username, char *pgpassfile);
+static bool fillDefaultPGPassFile(PGconn *conn);
+static void pgpassfileWarning(PGconn *conn);
 static void default_threadlock(int acquire);
 
 
@@ -954,14 +958,25 @@ connectOptions2(PGconn *conn)
 		conn->pgpass = strdup(DefaultPassword);
 		if (!conn->pgpass)
 			goto oom_error;
+
+		if (!conn->pgpassfile || conn->pgpassfile[0] == '\0')
+		{
+			/* Fail if we couldn't fill in the default pgpassfile */
+			if (fillDefaultPGPassFile(conn) == false)
+				return false;
+		}
+
 		for (i = 0; i < conn->nconnhost; ++i)
 		{
+			/* We'll pass conn->pgpassfile regardless of it's contents - checks happen in passwordFromFile() */
 			conn->connhost[i].password =
-				PasswordFromFile(conn->connhost[i].host,
+				passwordFromFile(conn->connhost[i].host,
 								 conn->connhost[i].port,
-								 conn->dbName, conn->pguser);
-			if (conn->connhost[i].password != NULL)
-				conn->dot_pgpass_used = true;
+								 conn->dbName, conn->pguser,
+								 conn->pgpassfile);
+			/* Check if conn->pgpassfile_used is already true so we can skip this each following iteration */
+			if (!conn->pgpassfile_used && conn->connhost[i].password != NULL)
+				conn->pgpassfile_used = true;
 		}
 	}
 
@@ -2830,7 +2845,7 @@ keep_going:						/* We will come back to here until there is
 
 error_return:
 
-	dot_pg_pass_warning(conn);
+	pgpassfileWarning(conn);
 
 	/*
 	 * We used to close the socket at this point, but that makes it awkward
@@ -2961,7 +2976,7 @@ makeEmptyPGconn(void)
 	conn->sock = PGINVALID_SOCKET;
 	conn->auth_req_received = false;
 	conn->password_needed = false;
-	conn->dot_pgpass_used = false;
+	conn->pgpassfile_used = false;
 #ifdef USE_SSL
 	conn->allow_ssl_try = true;
 	conn->wait_ssl_try = false;
@@ -3070,6 +3085,8 @@ freePGconn(PGconn *conn)
 		free(conn->pguser);
 	if (conn->pgpass)
 		free(conn->pgpass);
+	if (conn->pgpassfile)
+		free(conn->pgpassfile);
 	if (conn->keepalives)
 		free(conn->keepalives);
 	if (conn->keepalives_idle)
@@ -5947,12 +5964,12 @@ pwdfMatchesString(char *buf, char *token)
 	return NULL;
 }
 
-/* Get a password from the password file. Return value is malloc'd. */
+/* Get a password from the password file or the user-specified pgpassfile.
+	Return value is malloc'd. */
 static char *
-PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
+passwordFromFile(char *hostname, char *port, char *dbname, char *username, char *pgpassfile)
 {
 	FILE	   *fp;
-	char		pgpassfile[MAXPGPATH];
 	struct stat stat_buf;
 
 #define LINELEN NAMEDATALEN*5
@@ -5979,9 +5996,6 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
 	if (port == NULL)
 		port = DEF_PGPORT_STR;
 
-	if (!getPgPassFilename(pgpassfile))
-		return NULL;
-
 	/* If password file cannot be opened, ignore it. */
 	if (stat(pgpassfile, &stat_buf) != 0)
 		return NULL;
@@ -6076,21 +6090,19 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
 
 
 static bool
-getPgPassFilename(char *pgpassfile)
+fillDefaultPGPassFile(PGconn *conn)
 {
-	char	   *passfile_env;
-
-	if ((passfile_env = getenv("PGPASSFILE")) != NULL)
-		/* use the literal path from the environment, if set */
-		strlcpy(pgpassfile, passfile_env, MAXPGPATH);
-	else
-	{
-		char		homedir[MAXPGPATH];
-
-		if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
-			return false;
-		snprintf(pgpassfile, MAXPGPATH, "%s/%s", homedir, PGPASSFILE);
-	}
+	char		homedir[MAXPGPATH];
+
+	if (conn->pgpassfile)
+		free(conn->pgpassfile);
+
+	conn->pgpassfile = malloc(MAXPGPATH);
+
+	if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
+		return false;
+	snprintf(conn->pgpassfile, MAXPGPATH, "%s/%s", homedir, PGPASSFILE);
+
 	return true;
 }
 
@@ -6100,21 +6112,17 @@ getPgPassFilename(char *pgpassfile)
  *	password is wrong.
  */
 static void
-dot_pg_pass_warning(PGconn *conn)
+pgpassfileWarning(PGconn *conn)
 {
-	/* If it was 'invalid authorization', add .pgpass mention */
+	/* If it was 'invalid authorization', add pgpassfile mention */
 	/* only works with >= 9.0 servers */
-	if (conn->dot_pgpass_used && conn->password_needed && conn->result &&
+	if (conn->pgpassfile_used && conn->password_needed && conn->result &&
 		strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE),
 			   ERRCODE_INVALID_PASSWORD) == 0)
 	{
-		char		pgpassfile[MAXPGPATH];
-
-		if (!getPgPassFilename(pgpassfile))
-			return;
 		appendPQExpBuffer(&conn->errorMessage,
 					  libpq_gettext("password retrieved from file \"%s\"\n"),
-						  pgpassfile);
+						  conn->pgpassfile);
 	}
 }
 
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 854ec89..a60ebea 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -343,6 +343,7 @@ struct pg_conn
 	char	   *replication;	/* connect as the replication standby? */
 	char	   *pguser;			/* Postgres username and password, if any */
 	char	   *pgpass;
+	char	   *pgpassfile;		/* path to a file containing the password */
 	char	   *keepalives;		/* use TCP keepalives? */
 	char	   *keepalives_idle;	/* time between TCP keepalives */
 	char	   *keepalives_interval;	/* time between TCP keepalive
@@ -404,7 +405,7 @@ struct pg_conn
 	bool		auth_req_received;		/* true if any type of auth req
 										 * received */
 	bool		password_needed;	/* true if server demanded a password */
-	bool		dot_pgpass_used;	/* true if used .pgpass */
+	bool		pgpassfile_used;	/* true if a password from a pgpassfile was used */
 	bool		sigpipe_so;		/* have we masked SIGPIPE via SO_NOSIGPIPE? */
 	bool		sigpipe_flag;	/* can we mask SIGPIPE via MSG_NOSIGNAL? */
 
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to