Skip to content

Commit ba005f1

Browse files
committed
Allow password file name to be specified as a libpq connection parameter.
Formerly an alternate password file could only be selected via the environment variable PGPASSFILE; now it can also be selected via a new connection parameter "passfile", corresponding to the conventions for most other connection parameters. There was some concern about this creating a security weakness, but it was agreed that that argument was pretty thin, and there are clear use-cases for handling password files this way. Julian Markwort, reviewed by Fabien Coelho, some adjustments by me Discussion: https://postgr.es/m/a4b4f4f1-7b58-a0e8-5268-5f7db8e8ccaa@uni-muenster.de
1 parent d1ecd53 commit ba005f1

File tree

4 files changed

+81
-62
lines changed

4 files changed

+81
-62
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
943943
Note that authentication is likely to fail if <literal>host</>
944944
is not the name of the server at network address <literal>hostaddr</>.
945945
Also, note that <literal>host</> rather than <literal>hostaddr</>
946-
is used to identify the connection in <filename>~/.pgpass</> (see
946+
is used to identify the connection in a password file (see
947947
<xref linkend="libpq-pgpass">).
948948
</para>
949949

@@ -1002,6 +1002,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
10021002
</listitem>
10031003
</varlistentry>
10041004

1005+
<varlistentry id="libpq-connect-passfile" xreflabel="passfile">
1006+
<term><literal>passfile</literal></term>
1007+
<listitem>
1008+
<para>
1009+
Specifies the name of the file used to store passwords
1010+
(see <xref linkend="libpq-pgpass">).
1011+
Defaults to <filename>~/.pgpass</filename>, or
1012+
<filename>%APPDATA%\postgresql\pgpass.conf</> on Microsoft Windows.
1013+
(No error is reported if this file does not exist.)
1014+
</para>
1015+
</listitem>
1016+
</varlistentry>
1017+
10051018
<varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout">
10061019
<term><literal>connect_timeout</literal></term>
10071020
<listitem>
@@ -6893,8 +6906,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
68936906
Use of this environment variable
68946907
is not recommended for security reasons, as some operating systems
68956908
allow non-root users to see process environment variables via
6896-
<application>ps</>; instead consider using the
6897-
<filename>~/.pgpass</> file (see <xref linkend="libpq-pgpass">).
6909+
<application>ps</>; instead consider using a password file
6910+
(see <xref linkend="libpq-pgpass">).
68986911
</para>
68996912
</listitem>
69006913

@@ -6903,9 +6916,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
69036916
<indexterm>
69046917
<primary><envar>PGPASSFILE</envar></primary>
69056918
</indexterm>
6906-
<envar>PGPASSFILE</envar> specifies the name of the password file to
6907-
use for lookups. If not set, it defaults to <filename>~/.pgpass</>
6908-
(see <xref linkend="libpq-pgpass">).
6919+
<envar>PGPASSFILE</envar> behaves the same as the <xref
6920+
linkend="libpq-connect-passfile"> connection parameter.
69096921
</para>
69106922
</listitem>
69116923

@@ -7187,13 +7199,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
71877199
</indexterm>
71887200

71897201
<para>
7190-
The file <filename>.pgpass</filename> in a user's home directory or the
7191-
file referenced by <envar>PGPASSFILE</envar> can contain passwords to
7202+
The file <filename>.pgpass</filename> in a user's home directory can
7203+
contain passwords to
71927204
be used if the connection requires a password (and no password has been
71937205
specified otherwise). On Microsoft Windows the file is named
71947206
<filename>%APPDATA%\postgresql\pgpass.conf</> (where
71957207
<filename>%APPDATA%</> refers to the Application Data subdirectory in
71967208
the user's profile).
7209+
Alternatively, a password file can be specified
7210+
using the connection parameter <xref linkend="libpq-connect-passfile">
7211+
or the environment variable <envar>PGPASSFILE</envar>.
71977212
</para>
71987213

71997214
<para>
@@ -7219,8 +7234,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
72197234
</para>
72207235

72217236
<para>
7222-
On Unix systems, the permissions on <filename>.pgpass</filename> must
7223-
disallow any access to world or group; achieve this by the command
7237+
On Unix systems, the permissions on a password file must
7238+
disallow any access to world or group; achieve this by a command such as
72247239
<command>chmod 0600 ~/.pgpass</command>. If the permissions are less
72257240
strict than this, the file will be ignored. On Microsoft Windows, it
72267241
is assumed that the file is stored in a directory that is secure, so

src/interfaces/libpq/fe-auth.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,11 +686,12 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
686686
case AUTH_REQ_MD5:
687687
case AUTH_REQ_PASSWORD:
688688
{
689-
char *password = conn->connhost[conn->whichhost].password;
689+
char *password;
690690

691+
conn->password_needed = true;
692+
password = conn->connhost[conn->whichhost].password;
691693
if (password == NULL)
692694
password = conn->pgpass;
693-
conn->password_needed = true;
694695
if (password == NULL || password[0] == '\0')
695696
{
696697
printfPQExpBuffer(&conn->errorMessage,

src/interfaces/libpq/fe-connect.c

Lines changed: 51 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
107107
#define DefaultTty ""
108108
#define DefaultOption ""
109109
#define DefaultAuthtype ""
110-
#define DefaultPassword ""
111110
#define DefaultTargetSessionAttrs "any"
112111
#ifdef USE_SSL
113112
#define DefaultSSLMode "prefer"
@@ -185,6 +184,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
185184
"Database-Password", "*", 20,
186185
offsetof(struct pg_conn, pgpass)},
187186

187+
{"passfile", "PGPASSFILE", NULL, NULL,
188+
"Database-Password-File", "", 64,
189+
offsetof(struct pg_conn, pgpassfile)},
190+
188191
{"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL,
189192
"Connect-timeout", "", 10, /* strlen(INT32_MAX) == 10 */
190193
offsetof(struct pg_conn, connect_timeout)},
@@ -382,10 +385,9 @@ static int parseServiceFile(const char *serviceFile,
382385
PQExpBuffer errorMessage,
383386
bool *group_found);
384387
static char *pwdfMatchesString(char *buf, char *token);
385-
static char *PasswordFromFile(char *hostname, char *port, char *dbname,
386-
char *username);
387-
static bool getPgPassFilename(char *pgpassfile);
388-
static void dot_pg_pass_warning(PGconn *conn);
388+
static char *passwordFromFile(char *hostname, char *port, char *dbname,
389+
char *username, char *pgpassfile);
390+
static void pgpassfileWarning(PGconn *conn);
389391
static void default_threadlock(int acquire);
390392

391393

@@ -957,19 +959,40 @@ connectOptions2(PGconn *conn)
957959
{
958960
int i;
959961

960-
if (conn->pgpass)
961-
free(conn->pgpass);
962-
conn->pgpass = strdup(DefaultPassword);
963-
if (!conn->pgpass)
964-
goto oom_error;
965-
for (i = 0; i < conn->nconnhost; ++i)
962+
if (conn->pgpassfile == NULL || conn->pgpassfile[0] == '\0')
963+
{
964+
/* Identify password file to use; fail if we can't */
965+
char homedir[MAXPGPATH];
966+
967+
if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
968+
{
969+
conn->status = CONNECTION_BAD;
970+
printfPQExpBuffer(&conn->errorMessage,
971+
libpq_gettext("could not get home directory to locate password file\n"));
972+
return false;
973+
}
974+
975+
if (conn->pgpassfile)
976+
free(conn->pgpassfile);
977+
conn->pgpassfile = malloc(MAXPGPATH);
978+
if (!conn->pgpassfile)
979+
goto oom_error;
980+
981+
snprintf(conn->pgpassfile, MAXPGPATH, "%s/%s", homedir, PGPASSFILE);
982+
}
983+
984+
for (i = 0; i < conn->nconnhost; i++)
966985
{
986+
/* Try to get a password for this host from pgpassfile */
967987
conn->connhost[i].password =
968-
PasswordFromFile(conn->connhost[i].host,
988+
passwordFromFile(conn->connhost[i].host,
969989
conn->connhost[i].port,
970-
conn->dbName, conn->pguser);
990+
conn->dbName,
991+
conn->pguser,
992+
conn->pgpassfile);
993+
/* If we got one, set pgpassfile_used */
971994
if (conn->connhost[i].password != NULL)
972-
conn->dot_pgpass_used = true;
995+
conn->pgpassfile_used = true;
973996
}
974997
}
975998

@@ -3016,7 +3039,7 @@ PQconnectPoll(PGconn *conn)
30163039

30173040
error_return:
30183041

3019-
dot_pg_pass_warning(conn);
3042+
pgpassfileWarning(conn);
30203043

30213044
/*
30223045
* We used to close the socket at this point, but that makes it awkward
@@ -3147,7 +3170,7 @@ makeEmptyPGconn(void)
31473170
conn->sock = PGINVALID_SOCKET;
31483171
conn->auth_req_received = false;
31493172
conn->password_needed = false;
3150-
conn->dot_pgpass_used = false;
3173+
conn->pgpassfile_used = false;
31513174
#ifdef USE_SSL
31523175
conn->allow_ssl_try = true;
31533176
conn->wait_ssl_try = false;
@@ -3256,6 +3279,8 @@ freePGconn(PGconn *conn)
32563279
free(conn->pguser);
32573280
if (conn->pgpass)
32583281
free(conn->pgpass);
3282+
if (conn->pgpassfile)
3283+
free(conn->pgpassfile);
32593284
if (conn->keepalives)
32603285
free(conn->keepalives);
32613286
if (conn->keepalives_idle)
@@ -5794,6 +5819,9 @@ PQpass(const PGconn *conn)
57945819
password = conn->connhost[conn->whichhost].password;
57955820
if (password == NULL)
57965821
password = conn->pgpass;
5822+
/* Historically we've returned "" not NULL for no password specified */
5823+
if (password == NULL)
5824+
password = "";
57975825
return password;
57985826
}
57995827

@@ -6160,10 +6188,10 @@ pwdfMatchesString(char *buf, char *token)
61606188

61616189
/* Get a password from the password file. Return value is malloc'd. */
61626190
static char *
6163-
PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
6191+
passwordFromFile(char *hostname, char *port, char *dbname,
6192+
char *username, char *pgpassfile)
61646193
{
61656194
FILE *fp;
6166-
char pgpassfile[MAXPGPATH];
61676195
struct stat stat_buf;
61686196

61696197
#define LINELEN NAMEDATALEN*5
@@ -6190,9 +6218,6 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
61906218
if (port == NULL)
61916219
port = DEF_PGPORT_STR;
61926220

6193-
if (!getPgPassFilename(pgpassfile))
6194-
return NULL;
6195-
61966221
/* If password file cannot be opened, ignore it. */
61976222
if (stat(pgpassfile, &stat_buf) != 0)
61986223
return NULL;
@@ -6286,46 +6311,23 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
62866311
}
62876312

62886313

6289-
static bool
6290-
getPgPassFilename(char *pgpassfile)
6291-
{
6292-
char *passfile_env;
6293-
6294-
if ((passfile_env = getenv("PGPASSFILE")) != NULL)
6295-
/* use the literal path from the environment, if set */
6296-
strlcpy(pgpassfile, passfile_env, MAXPGPATH);
6297-
else
6298-
{
6299-
char homedir[MAXPGPATH];
6300-
6301-
if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
6302-
return false;
6303-
snprintf(pgpassfile, MAXPGPATH, "%s/%s", homedir, PGPASSFILE);
6304-
}
6305-
return true;
6306-
}
6307-
63086314
/*
63096315
* If the connection failed, we should mention if
6310-
* we got the password from .pgpass in case that
6316+
* we got the password from the pgpassfile in case that
63116317
* password is wrong.
63126318
*/
63136319
static void
6314-
dot_pg_pass_warning(PGconn *conn)
6320+
pgpassfileWarning(PGconn *conn)
63156321
{
6316-
/* If it was 'invalid authorization', add .pgpass mention */
6322+
/* If it was 'invalid authorization', add pgpassfile mention */
63176323
/* only works with >= 9.0 servers */
6318-
if (conn->dot_pgpass_used && conn->password_needed && conn->result &&
6324+
if (conn->pgpassfile_used && conn->password_needed && conn->result &&
63196325
strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE),
63206326
ERRCODE_INVALID_PASSWORD) == 0)
63216327
{
6322-
char pgpassfile[MAXPGPATH];
6323-
6324-
if (!getPgPassFilename(pgpassfile))
6325-
return;
63266328
appendPQExpBuffer(&conn->errorMessage,
63276329
libpq_gettext("password retrieved from file \"%s\"\n"),
6328-
pgpassfile);
6330+
conn->pgpassfile);
63296331
}
63306332
}
63316333

src/interfaces/libpq/libpq-int.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ struct pg_conn
343343
char *replication; /* connect as the replication standby? */
344344
char *pguser; /* Postgres username and password, if any */
345345
char *pgpass;
346+
char *pgpassfile; /* path to a file containing password(s) */
346347
char *keepalives; /* use TCP keepalives? */
347348
char *keepalives_idle; /* time between TCP keepalives */
348349
char *keepalives_interval; /* time between TCP keepalive
@@ -407,7 +408,7 @@ struct pg_conn
407408
bool auth_req_received; /* true if any type of auth req
408409
* received */
409410
bool password_needed; /* true if server demanded a password */
410-
bool dot_pgpass_used; /* true if used .pgpass */
411+
bool pgpassfile_used; /* true if password is from pgpassfile */
411412
bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */
412413
bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */
413414

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy