[PATCH] libpq: Add PQpassfileLookup()

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

Browse pgsql-hackers by date

  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