| From: | Diego <mrstephenamell(at)gmail(dot)com> |
|---|---|
| To: | pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Cc: | Denis Smirnov <darthunix(at)gmail(dot)com>, Jacob Champion <jacob(dot)champion(at)enterprisedb(dot)com> |
| Subject: | [PATCH] libpq: Add PQpassfileLookup() |
| Date: | 2026-09-04 14:06:06 |
| Message-ID: | b650e555-11c7-415d-8bda-c747c492ab4a@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi hackers,
This grew out of the portaddr thread [1]. The problem: an application
that connects through an intermediary, such as a local SSH tunnel on a
dynamic port, connects to a host and port that no longer match the
password file entry written for the real server, so the .pgpass lookup
comes up empty — and the only way out today is to reimplement the
passfile parser on the client side.
After portaddr was withdrawn, Denis Smirnov suggested exposing the
existing lookup as a public API instead [2]:
> I think a better solution would be to expose the existing
> passwordFromFile() code through a new public libpq API, perhaps
> PQpassfileLookup().
>
> Then pgcli could look up the password using the original host and
> port, and connect to the local tunnel port with that password. This
> would avoid both a new connection parameter and a separate .pgpass
> parser in each client.
The attached patch does exactly that. The function is a thin wrapper
around the existing passwordFromFile():
char *PQpassfileLookup(const char *hostname, const char *port,
const char *dbname, const char *username,
const char *passfile);
The first four arguments correspond to the fields of a passfile line
and behave as they do during connection establishment: a NULL or empty
hostname is matched as "localhost" (including the default socket
directory rule), and a NULL or empty port becomes the compiled-in
default. dbname and username get no defaults — resolving those belongs
to the conninfo layer, and a caller of this function already knows
them. If passfile is NULL or empty, PGPASSFILE and then the default
location are used, as for a connection.
The return value is malloc'd (to be freed with PQfreemem), or NULL
when no password could be found. The permission check also behaves
exactly as during connection establishment: warn on stderr and ignore
the file.
For tests, I added a --passfile mode to libpq_testclient and a new TAP
test (t/007_passfile.pl) covering matching, wildcards, escaping and
de-escaping, the localhost rule, PGPASSFILE, and the permission check.
The lookup is purely client-side, so none of it needs a server.
To show it end to end, a small PoC is attached too
(poc-passfilelookup.c). The setup: a server on 127.0.0.1:5440, this
passfile line, written before any tunnel exists:
127.0.0.1:5440:postgres:tuser:sekret
and a "tunnel" (a TCP relay) that binds port 0, so the kernel picks
the local port at that moment — 41395 on this run. The connection
through the tunnel cannot use the passfile today, with or without this
patch applied (nothing changes in connection establishment):
$ psql -w "host=127.0.0.1 port=41395 user=tuser dbname=postgres"
psql: error: connection to server at "127.0.0.1", port 41395
failed: fe_sendauth: no password supplied
With the patch, the client makes the two calls — look up under the
real host and port, connect to the local one:
$ ./poc-passfilelookup 127.0.0.1 5440 postgres tuser 41395
PQpassfileLookup("127.0.0.1", "5440", "postgres", "tuser", NULL) =
"sekret"
connected to 127.0.0.1:41395 as tuser -> OK
Against an unpatched libpq the PoC does not even link (undefined
reference to `PQpassfileLookup'), which is a fair summary of the
status quo: the only way a client can do this today is by parsing the
passfile itself.
I'll register this in the next open CommitFest.
[1] https://postgr.es/m/70436540-9d77-4014-89af-462881ed18d9@gmail.com
[2] https://postgr.es/m/B2EDB5AE-27F7-4580-871E-1C2433BAEF18@gmail.com
Thank you,
BR,
Diego
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0001-libpq-Add-PQpassfileLookup.patch | text/x-patch | 14.5 KB |
| poc-passfilelookup.c | text/x-csrc | 1.9 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Fujii Masao | 2026-09-04 14:07:22 | Re: Backup manifests accept out-of-range LSNs |
| Previous Message | Andrei Lepikhov | 2026-09-04 14:06:02 | Make the transition state of avg(int2)/avg(int4)/sum(int2)/sum(int4) internal |