public inbox for [email protected]
help / color / mirror / Atom feedFrom: Diego <[email protected]>
To: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Subject: Re: [PATCH] - Re: libpq: decouple the .pgpass lookup port from the connection port
Date: Wed, 8 Jul 2026 14:04:04 -0300
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
<[email protected]>
Hello,
It has been a couple of weeks with no comments, so let me add a small,
self-contained reproducer that makes both the problem and the fix easy
to see. I am also cc'ing a few people who have
worked on the relevant code, in case it is of interest: Tom (the
host/hostaddr split for the .pgpass lookup that this mirrors, and the
2018 .pgpass fix I referenced), Michael (the recent passwordFromFile()
work), and Heikki (the multi-host port parsing that the patch reuses).
Apologies for the noise if this is not the right moment.
The reproducer below uses a real SSH tunnel to itself, so the port that
libpq connects to genuinely differs from the real server port. Only the
patch changes the outcome; everything else (server, database, .pgpass)
is identical. It needs a libpq built with the v1 patch posted upstream
in this thread.
daf@t:postgres$ DEMO=/home/daf/scripts/postgres/passfileport-demo
daf@t:postgres$
daf@t:postgres$ BIN_CON="$DEMO/con-el-patch/usr/local/bin"
daf@t:postgres$
daf@t:postgres$ LIB_CON="$DEMO/con-el-patch/usr/local/lib/x86_64-linux-gnu"
daf@t:postgres$
daf@t:postgres$ PSQL_SIN="$DEMO/sin-patch/usr/local/bin/psql"
daf@t:postgres$
daf@t:postgres$ LIB_SIN="$DEMO/sin-patch/usr/local/lib/x86_64-linux-gnu"
daf@t:postgres$
daf@t:postgres$ PSQL_CON="$DEMO/con-el-patch/usr/local/bin/psql"
daf@t:postgres$
daf@t:postgres$ unset PGPASSWORD PGPASSFILEPORT
daf@t:postgres$
daf@t:postgres$ export LD_LIBRARY_PATH="$LIB_CON"
daf@t:postgres$
daf@t:postgres$ export PGDATA=/tmp/pgd-demo
daf@t:postgres$
daf@t:postgres$ export PGPASSFILE=/tmp/pgpass-demo
daf@t:postgres$
daf@t:postgres$ rm -rf "$PGDATA"
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/initdb" -D "$PGDATA" -U postgres -A trust
--no-sync
The files belonging to this database system will be owned by user "daf".
This user must also own the server process.
The database cluster will be initialized with locale "C.UTF-8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".
Data page checksums are enabled.
creating directory /tmp/pgd-demo ... ok
creating subdirectories ... ok
selecting dynamic shared memory implementation ... posix
selecting default "max_connections" ... 100
selecting default "shared_buffers" ... 128MB
selecting default time zone ... America/Argentina/Buenos_Aires
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok
Sync to disk skipped.
The data directory might become corrupt if the operating system crashes.
Success. You can now start the database server using:
/home/daf/scripts/postgres/passfileport-demo/con-el-patch/usr/local/bin/pg_ctl
-D /tmp/pgd-demo -l logfile start
daf@t:postgres$ printf 'local all all trust\nhost all all 127.0.0.1/32
scram-sha-256\n' > "$PGDATA/pg_hba.conf"
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/pg_ctl" -D "$PGDATA" -o "-p 5440 -c
listen_addresses=127.0.0.1" -w start
waiting for server to start....2026-07-08 13:39:19.186 -03 [666970]
LOG: starting PostgreSQL 19beta1 on x86_64-linux, compiled by
gcc-12.2.0, 64-bit
2026-07-08 13:39:19.186 -03 [666970] LOG: listening on IPv4 address
"127.0.0.1", port 5440
2026-07-08 13:39:19.189 -03 [666970] LOG: listening on Unix socket
"/tmp/.s.PGSQL.5440"
2026-07-08 13:39:19.196 -03 [666975] LOG: database system was shut down
at 2026-07-08 13:38:56 -03
2026-07-08 13:39:19.200 -03 [666970] LOG: database system is ready to
accept connections
done
server started
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/psql" -X -p 5440 -U postgres -h /tmp -d
postgres -c "create role tuser login password 'sekret';"
CREATE ROLE
daf@t:postgres$
daf@t:postgres$ echo "127.0.0.1:5441:postgres:tuser:sekret" > "$PGPASSFILE"
daf@t:postgres$
daf@t:postgres$ chmod 600 "$PGPASSFILE"
daf@t:postgres$
daf@t:postgres$ LD_LIBRARY_PATH="$LIB_SIN" "$PSQL_SIN" -X -w
"host=127.0.0.1 port=5440 user=tuser dbname=postgres" -tAc "select
'AUTENTICO-OK'"
psql: error: connection to server at "127.0.0.1", port 5440 failed:
fe_sendauth: no password supplied
daf@t:postgres$
daf@t:postgres$ LD_LIBRARY_PATH="$LIB_SIN" "$PSQL_SIN" -X -w
"host=127.0.0.1 port=5440 user=tuser dbname=postgres passfileport=5441"
-tAc "select 'AUTENTICO-OK'"
psql: error: invalid connection option "passfileport"
daf@t:postgres$
daf@t:postgres$ LD_LIBRARY_PATH="$LIB_CON" "$PSQL_CON" -X -w
"host=127.0.0.1 port=5440 user=tuser dbname=postgres passfileport=5441"
-tAc "select 'AUTENTICO-OK'"
AUTENTICO-OK
daf@t:postgres$
daf@t:postgres$ cat $PGPASSFILE
127.0.0.1:5441:postgres:tuser:sekret
daf@t:postgres$
daf@t:postgres$ "$BIN_CON/pg_ctl" -D "$PGDATA" -m immediate stop
waiting for server to shut down....2026-07-08 13:44:30.677 -03 [666970]
LOG: received immediate shutdown request
2026-07-08 13:44:30.688 -03 [666970] LOG: database system is shut down
done
server stopped
daf@t:postgres$
The connection port stays 5441; only the password-file lookup key uses
5440. Without passfileport the default is unchanged, so existing setups
behave exactly as before.
As a sanity check that the difference is solely this patch, I built two
libpq trees from the same commit, identical except for the change, and
ran the same command line against both through the same tunnel: with the
unpatched libpq passfileport is simply "invalid connection option", and
with the patched one the connection succeeds. Same server, same .pgpass,
same command; the patch is the only variable.
The design is unchanged from the original proposal, and the v1 patch is
the one already posted in this thread (adds the passfileport conninfo
option and PGPASSFILEPORT env var, splits per host like port, uses it as
the .pgpass lookup key, with docs and a TAP test). I would still welcome
opinions on two points in particular:
- the parameter name (passfileport is only tentative), and
- the security angle: this matches the real server port explicitly,
which is stricter than the port wildcard people use today, so I
believe it improves on the "password sent to the wrong server" risk
rather than adding to it.
Thanks for taking a look.
Regards,
Diego
On 2026-06-29 14:14, Diego wrote:
>
> Hi,
>
> Following up on my proposal from last week. Since the idea didn't draw
> objections, I went ahead and wrote the patch (code, docs and a TAP test)
> so the discussion can be more concrete. v1 attached.
>
> It compiles cleanly, the new TAP test 008_passfileport passes (10
> subtests), and the full authentication suite stays green. I also
> verified it end-to-end against a real server reached through an SSH
> tunnel: the .pgpass entry matches with passfileport set and fails
> without it.
>
> I'd really value your read on whether the
> host/hostaddr-style decoupling is the right shape here, and on the
> parameter name (still tentative).
>
> I'm not a professional C developer and I need help.
> Ofc, I used Cursor and Claude to review the code before this mail. ;P
>
> Thanks,
> Diego
>
> On 2026-06-23 12:57, Diego wrote:
>>
>> Hello hackers,
>>
>> I would like to float an idea before writing a patch, to find out whether
>> it is wanted and to get the design right.
>>
>> Problem
>> -------
>>
>> libpq looks up a password in .pgpass using the connection's host and port
>> as part of the key (host:port:database:user:password). When a client
>> connects through an SSH tunnel, or through a connection pooler that
>> listens on a different local port, the port that libpq actually connects
>> to is not the port of the real server. As a result, the .pgpass lookup is
>> done against the local/tunnel port and fails to match entries written for
>> the real server port.
>>
>> Concretely, suppose the real server is db.example.com:5432 and a user
>> opens an SSH tunnel so that 127.0.0.1:54321 forwards to it. The natural
>> .pgpass entry is:
>>
>> db.example.com:5432:appdb:alice:secret
>>
>> The client then connects with host=db.example.com (kept for .pgpass and
>> TLS), hostaddr=127.0.0.1 and port=54321 (the tunnel). libpq looks up
>>
>> db.example.com:54321:appdb:alice
>>
>> which does not match the 5432 entry, so no password is found and the user
>> is prompted (or the connection fails under -w).
>>
>> The host side of this exact problem was already solved
>> -------------------------------------------------------
>>
>> libpq already decouples the *host* used for the .pgpass lookup from the
>> real network endpoint: hostaddr gives the address actually connected to,
>> while host remains the logical name used for the .pgpass lookup and for
>> TLS verification. This is the pwhost logic in fe-connect.c, which goes
>> back to the 2018 thread "Bizarre behavior in libpq's searching of
>> ~/.pgpass":
>>
>> https://www.postgresql.org/message-id/30805.1532749137%40sss.pgh.pa.us
>>
>> The port has no equivalent. passwordFromFile() is called with
>> conn->connhost[i].port, i.e. the real connection port, with no way to say
>> "connect to this port, but look up .pgpass under that port". The host has
>> host/hostaddr; the port only has port. This proposal is to close that
>> asymmetry.
>>
>> Why the port wildcard is not enough
>> -----------------------------------
>>
>> One can write the entry with a wildcard port:
>>
>> db.example.com:*:appdb:alice:secret
>>
>> and it does match the tunnel. But the wildcard over-matches: a single
>> local forwarding port (say 54321, or even a fixed local port reused for
>> several tunnels at different times) ends up matching every server reached
>> through that port, so the same password line can be applied to different
>> servers. That is precisely the kind of "password sent to the wrong
>> server" situation the 2018 host fix was trying to avoid. The wildcard
>> trades safety for convenience; it is not a substitute for matching the
>> real server port.
>>
>> Proposal
>> --------
>>
>> Add a libpq connection parameter that specifies the port to be used for
>> the .pgpass lookup, independently of the port libpq connects to. The
>> connection still uses port (and hostaddr); only the password-file lookup
>> key uses the new value. When the new parameter is not set, behavior is
>> unchanged: the lookup uses port exactly as today.
>>
>> I do not have a strong opinion on the name and would rather not bikeshed
>> it before the idea itself is judged. Candidates that came to mind:
>>
>> - pgpassport / passfileport (it only affects the password file)
>> - portaddr (mirrors hostaddr: "port stays logical, portaddr is the
>> real endpoint"), though that would invert today's meaning of port,
>> which is probably too invasive
>>
>> A dedicated parameter that affects only the .pgpass lookup (the first
>> option) seems the least surprising and the smallest change. It is also
>> easy to reason about for security: it is an explicit, opt-in assertion by
>> the user, exactly like hostaddr/host.
>>
>> This is not hypothetical. I ran into it myself while adding SSH tunnel
>> support to pgcli (a widely used Postgres CLI): with the tunnel active, an
>> explicit-port .pgpass entry never matches, because the lookup happens
>> against the random local forwarding port. The user is prompted for a
>> password even though the matching entry is right there, and only a
>> wildcard port papers over it. Other tools hit the same wall:
>>
>> - pgcli: SSH tunnel rewrites the port before the .pgpass lookup
>> https://github.com/dbcli/pgcli/pull/1546
>> - DBeaver: .pgpass looked up by 127.0.0.1 through an SSH tunnel
>> https://github.com/dbeaver/dbeaver/issues/16499
>> - pgAdmin 4: control the SSH tunnel local port for .pgpass matching
>> https://github.com/pgadmin-org/pgadmin4/issues/6903
>>
>> Questions for the list
>> ----------------------
>>
>> 1. Is decoupling the .pgpass-lookup port from the connection port
>> something libpq wants, given that host/hostaddr already does the
>> equivalent for the host?
>> 2. Is a dedicated lookup-only parameter the right shape, or would you
>> prefer a different model?
>> 3. Naming preferences?
>>
>> If there is interest, I am happy to write the patch (code, docs and
>> tests).
>>
>> Thanks for reading,
>> Diego
>>
Attachments:
[text/x-patch] v1-0001-libpq-add-passfileport-to-decouple-the-.pgpass-lo.patch (14.0K, ../[email protected]/3-v1-0001-libpq-add-passfileport-to-decouple-the-.pgpass-lo.patch)
download | inline diff:
From eb9866fa7e8a1057466b641f7079dc87832a7a47 Mon Sep 17 00:00:00 2001
From: Diego <[email protected]>
Date: Mon, 29 Jun 2026 13:53:37 -0300
Subject: [PATCH v1] libpq: add passfileport to decouple the .pgpass lookup
port
libpq looks up passwords in .pgpass using the connection's host and
port as part of the key. When a client connects through an SSH tunnel,
or through a connection pooler that listens on a different local port,
the port libpq actually connects to is not the real server's port, so
the .pgpass lookup is done against the local/tunnel port and fails to
match entries written for the real server.
libpq already decouples the *host* used for the lookup from the network
endpoint: hostaddr is the address actually connected to, while host
remains the logical name used for the .pgpass lookup and for TLS
verification (the pwhost logic, from the 2018 thread "Bizarre behavior
in libpq's searching of ~/.pgpass"). The port had no equivalent.
This adds a connection parameter, passfileport (environment variable
PGPASSFILEPORT), specifying the port used as the .pgpass lookup key,
independently of the port libpq connects to. When it is not set,
behavior is unchanged: the lookup uses the connection port exactly as
before. The value is split per-host like port, and applied at the
passwordFromFile() call site analogously to pwhost (pwport).
Documentation and a TAP test (008_passfileport) are included.
Discussion: https://www.postgresql.org/message-id/flat/001a6f1d-4adb-42b2-8bf6-44154ed0ab97%40gmail.com
---
doc/src/sgml/libpq.sgml | 43 +++++++++
src/interfaces/libpq/fe-connect.c | 58 +++++++++++-
src/interfaces/libpq/libpq-int.h | 6 ++
src/test/authentication/meson.build | 1 +
src/test/authentication/t/008_passfileport.pl | 94 +++++++++++++++++++
5 files changed, 200 insertions(+), 2 deletions(-)
create mode 100644 src/test/authentication/t/008_passfileport.pl
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..6a139432f7a 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -1296,6 +1296,34 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem>
</varlistentry>
+ <varlistentry id="libpq-connect-passfileport" xreflabel="passfileport">
+ <term><literal>passfileport</literal></term>
+ <listitem>
+ <para>
+ Specifies the port number to use as the lookup key in the password
+ file (see <xref linkend="libpq-pgpass"/>), instead of the port given
+ by the <xref linkend="libpq-connect-port"/> parameter. This is useful
+ when the port actually connected to is not the port of the target
+ server, for example when connecting through an SSH tunnel or a
+ connection pooler that listens on a different local port: the
+ connection still uses <literal>port</literal>, while the password-file
+ lookup uses <literal>passfileport</literal>, so an entry written for
+ the real server port keeps matching. This mirrors the way
+ <xref linkend="libpq-connect-host"/> and
+ <xref linkend="libpq-connect-hostaddr"/> already separate the
+ password-file lookup key from the address actually connected to.
+ </para>
+ <para>
+ As with <literal>port</literal>, a comma-separated list of port
+ numbers may be given, in which case it must have either exactly one
+ element (applied to all hosts) or the same number of elements as the
+ host list. When this parameter is empty or not specified, the
+ connection port is used for the lookup, which is the historical
+ behavior.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="libpq-connect-require-auth" xreflabel="require_auth">
<term><literal>require_auth</literal></term>
<listitem>
@@ -9152,6 +9180,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para>
</listitem>
+ <listitem>
+ <para>
+ <indexterm>
+ <primary><envar>PGPASSFILEPORT</envar></primary>
+ </indexterm>
+ <envar>PGPASSFILEPORT</envar> behaves the same as the <xref
+ linkend="libpq-connect-passfileport"/> connection parameter.
+ </para>
+ </listitem>
+
<listitem>
<para>
<indexterm>
@@ -9580,6 +9618,11 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
the connection is a Unix-domain socket connection and
the <literal>host</literal> parameter
matches <application>libpq</application>'s default socket directory path.
+ The port field is matched to the <literal>port</literal> connection
+ parameter, or to the <literal>passfileport</literal> parameter if that is
+ specified; the latter is useful when the connection is made through an SSH
+ tunnel or a pooler on a different port, so that the entry can still be
+ written for the real server port.
In a standby server, a database field of <literal>replication</literal>
matches streaming replication connections made to the primary server.
The database field is of limited usefulness otherwise, because users have
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 38422becc48..a065734e415 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -219,6 +219,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Database-Password-File", "", 64,
offsetof(struct pg_conn, pgpassfile)},
+ {"passfileport", "PGPASSFILEPORT", NULL, NULL,
+ "Database-Password-File-Port", "", 6,
+ offsetof(struct pg_conn, passfileport)},
+
{"channel_binding", "PGCHANNELBINDING", DefaultChannelBinding, NULL,
"Channel-Binding", "", 8, /* sizeof("require") == 8 */
offsetof(struct pg_conn, channel_binding)},
@@ -1402,6 +1406,46 @@ pqConnectOptions2(PGconn *conn)
}
}
+ /*
+ * If a separate port for the password-file lookup was given, work out the
+ * value corresponding to each host name, exactly as for the connection
+ * port above. When not given, connhost[i].passfileport stays NULL and the
+ * lookup falls back to the connection port.
+ */
+ if (conn->passfileport != NULL && conn->passfileport[0] != '\0')
+ {
+ char *s = conn->passfileport;
+ bool more = true;
+
+ for (i = 0; i < conn->nconnhost && more; i++)
+ {
+ conn->connhost[i].passfileport = parse_comma_separated_list(&s, &more);
+ if (conn->connhost[i].passfileport == NULL)
+ goto oom_error;
+ }
+
+ /*
+ * If exactly one port was given, use it for every host. Otherwise,
+ * there must be exactly as many ports as there were hosts.
+ */
+ if (i == 1 && !more)
+ {
+ for (i = 1; i < conn->nconnhost; i++)
+ {
+ conn->connhost[i].passfileport = strdup(conn->connhost[0].passfileport);
+ if (conn->connhost[i].passfileport == NULL)
+ goto oom_error;
+ }
+ }
+ else if (more || i != conn->nconnhost)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "could not match %d password file port numbers to %d hosts",
+ count_comma_separated_elems(conn->passfileport), conn->nconnhost);
+ return false;
+ }
+ }
+
/*
* If user name was not given, fetch it. (Most likely, the fetch will
* fail, since the only way we get here is if pg_fe_getauthname() failed
@@ -1458,17 +1502,25 @@ pqConnectOptions2(PGconn *conn)
/*
* Try to get a password for this host from file. We use host
* for the hostname search key if given, else hostaddr (at
- * least one of them is guaranteed nonempty by now).
+ * least one of them is guaranteed nonempty by now). For the
+ * port search key we use passfileport if given, else the
+ * connection port; this lets the lookup match the real server
+ * port even when the connection goes through an SSH tunnel or a
+ * pooler listening on a different port.
*/
const char *pwhost = conn->connhost[i].host;
+ const char *pwport = conn->connhost[i].passfileport;
const char *password_errmsg = NULL;
if (pwhost == NULL || pwhost[0] == '\0')
pwhost = conn->connhost[i].hostaddr;
+ if (pwport == NULL || pwport[0] == '\0')
+ pwport = conn->connhost[i].port;
+
conn->connhost[i].password =
passwordFromFile(pwhost,
- conn->connhost[i].port,
+ pwport,
conn->dbName,
conn->pguser,
conn->pgpassfile,
@@ -5122,6 +5174,7 @@ freePGconn(PGconn *conn)
free(conn->pgpass);
}
free(conn->pgpassfile);
+ free(conn->passfileport);
free(conn->channel_binding);
free(conn->keepalives);
free(conn->keepalives_idle);
@@ -5196,6 +5249,7 @@ pqReleaseConnHosts(PGconn *conn)
free(conn->connhost[i].host);
free(conn->connhost[i].hostaddr);
free(conn->connhost[i].port);
+ free(conn->connhost[i].passfileport);
if (conn->connhost[i].password != NULL)
{
explicit_bzero(conn->connhost[i].password,
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 461b39620c3..990d254b519 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -361,6 +361,9 @@ typedef struct pg_conn_host
char *hostaddr; /* host numeric IP address */
char *port; /* port number (if NULL or empty, use
* DEF_PGPORT[_STR]) */
+ char *passfileport; /* port to use as the lookup key in the
+ * password file for this host; if NULL or
+ * empty, the connection port is used */
char *password; /* password for this host, read from the
* password file; NULL if not sought or not
* found in password file. */
@@ -397,6 +400,9 @@ struct pg_conn
char *pguser; /* Postgres username and password, if any */
char *pgpass;
char *pgpassfile; /* path to a file containing password(s) */
+ char *passfileport; /* port to use as the lookup key in the
+ * password file, instead of the connection
+ * port; or a comma-separated list of same */
char *channel_binding; /* channel binding mode
* (require,prefer,disable) */
char *keepalives; /* use TCP keepalives? */
diff --git a/src/test/authentication/meson.build b/src/test/authentication/meson.build
index 282a5054e2c..54aecdd8abb 100644
--- a/src/test/authentication/meson.build
+++ b/src/test/authentication/meson.build
@@ -16,6 +16,7 @@ tests += {
't/005_sspi.pl',
't/006_login_trigger.pl',
't/007_pre_auth.pl',
+ 't/008_passfileport.pl',
],
},
}
diff --git a/src/test/authentication/t/008_passfileport.pl b/src/test/authentication/t/008_passfileport.pl
new file mode 100644
index 00000000000..92d754c453e
--- /dev/null
+++ b/src/test/authentication/t/008_passfileport.pl
@@ -0,0 +1,94 @@
+
+# Copyright (c) 2024-2026, PostgreSQL Global Development Group
+
+# Tests for the passfileport connection parameter, which sets the port used as
+# the lookup key in the password file independently of the connection port.
+# This is what lets a .pgpass entry written for the real server port keep
+# matching when the connection is made through an SSH tunnel or a pooler that
+# listens on a different port.
+#
+# The connection is made on the cluster's real port, but the .pgpass entry is
+# written under a different (bogus) port. Without passfileport the lookup uses
+# the connection port and finds nothing; with passfileport set to the bogus
+# port the lookup matches. This test can only run with Unix-domain sockets.
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if (!$use_unix_sockets)
+{
+ plan skip_all =>
+ "authentication tests cannot run without Unix-domain sockets";
+}
+
+my $node = PostgreSQL::Test::Cluster->new('main');
+$node->init;
+$node->start;
+
+my $role = 'passfileport_user';
+my $password = 'secret_pw';
+
+$node->safe_psql('postgres', "CREATE ROLE $role LOGIN PASSWORD '$password'");
+
+# Require a password (SCRAM) for this role; everything else stays trust so the
+# rest of the test can keep administering the cluster.
+unlink($node->data_dir . '/pg_hba.conf');
+$node->append_conf('pg_hba.conf', "local postgres $role scram-sha-256");
+$node->append_conf('pg_hba.conf', "local all all trust");
+$node->reload;
+
+# A port that is deliberately different from the real connection port.
+my $bogus_port = '1';
+
+# Write the password file entry under the bogus port, with a wildcard host so
+# the Unix-domain socket path does not have to be matched.
+my $pgpassfile = $node->basedir . '/pgpass';
+open(my $fh, '>', $pgpassfile) or die "could not open $pgpassfile: $!";
+print $fh "*:$bogus_port:postgres:$role:$password\n";
+close($fh);
+chmod(0600, $pgpassfile) or die "could not chmod $pgpassfile: $!";
+
+local $ENV{PGPASSFILE} = $pgpassfile;
+delete local $ENV{PGPASSWORD};
+delete local $ENV{PGPASSFILEPORT};
+
+my $connstr = $node->connstr('postgres') . " user=$role";
+
+# Without passfileport, the entry written under the bogus port does not match
+# the real connection port, so no password is found.
+$node->connect_fails(
+ $connstr,
+ 'without passfileport the .pgpass entry under another port does not match',
+ expected_stderr => qr/no password supplied/);
+
+# With passfileport pointing at the entry's port, the lookup matches and the
+# connection authenticates.
+$node->connect_ok(
+ "$connstr passfileport=$bogus_port",
+ 'passfileport makes the .pgpass lookup use the given port');
+
+# Same behavior through the PGPASSFILEPORT environment variable.
+{
+ local $ENV{PGPASSFILEPORT} = $bogus_port;
+ $node->connect_ok($connstr,
+ 'PGPASSFILEPORT environment variable makes the lookup use the given port'
+ );
+}
+
+# A passfileport with no matching entry still fails.
+$node->connect_fails(
+ "$connstr passfileport=2",
+ 'passfileport with no matching .pgpass entry fails',
+ expected_stderr => qr/no password supplied/);
+
+# More passfileport values than hosts is rejected when the options are parsed.
+$node->connect_fails(
+ "$connstr passfileport=$bogus_port,2",
+ 'more passfileport values than hosts is rejected',
+ expected_stderr =>
+ qr/could not match \d+ password file port numbers to \d+ hosts/);
+
+done_testing();
base-commit: 56f2b0b5334df68b16964d4f9a0cbe9dae913227
--
2.43.0
view thread (4+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: [PATCH] - Re: libpq: decouple the .pgpass lookup port from the connection port
In-Reply-To: <[email protected]>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox