Skip to content

Commit e3f99e0

Browse files
committed
Fix libpq's code for searching .pgpass; rationalize empty-list-item cases.
Before v10, we always searched ~/.pgpass using the host parameter, and nothing else, to match to the "hostname" field of ~/.pgpass. (However, null host or host matching DEFAULT_PGSOCKET_DIR was replaced by "localhost".) In v10, this got broken by commit 274bb2b, repaired by commit bdac983, and broken again by commit 7b02ba6; in the code actually shipped, we'd search with hostaddr if both that and host were specified --- though oddly, *not* if only hostaddr were specified. Since this is directly contrary to the documentation, and not backwards-compatible, it's clearly a bug. However, the change wasn't totally without justification, even though it wasn't done quite right, because the pre-v10 behavior has arguably been buggy since we added hostaddr. If hostaddr is specified and host isn't, the pre-v10 code will search ~/.pgpass for "localhost", and ship that password off to a server that most likely isn't local at all. That's unhelpful at best, and could be a security breach at worst. Therefore, rather than just revert to that old behavior, let's define the behavior as "search with host if provided, else with hostaddr if provided, else search for localhost". (As before, a host name matching DEFAULT_PGSOCKET_DIR is replaced by localhost.) This matches the behavior of the actual connection code, so that we don't pick up an inappropriate password; and it allows useful searches to happen when only hostaddr is given. While we're messing around here, ensure that empty elements within a host or hostaddr list select the same behavior as a totally-empty field would; for instance "host=a,,b" is equivalent to "host=a,/tmp,b" if DEFAULT_PGSOCKET_DIR is /tmp. Things worked that way in some cases already, but not consistently so, which contributed to the confusion about what key ~/.pgpass would get searched with. Update documentation accordingly, and also clarify some nearby text. Back-patch to v10 where the host/hostaddr list functionality was introduced. Discussion: https://postgr.es/m/30805.1532749137@sss.pgh.pa.us
1 parent e80f2b3 commit e3f99e0

File tree

2 files changed

+84
-56
lines changed

2 files changed

+84
-56
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -938,8 +938,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
938938
<para>
939939
If a password file is used, you can have different passwords for
940940
different hosts. All the other connection options are the same for every
941-
host, it is not possible to e.g. specify a different username for
942-
different hosts.
941+
host in the list; it is not possible to e.g. specify different
942+
usernames for different hosts.
943943
</para>
944944
</sect3>
945945
</sect2>
@@ -961,15 +961,16 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
961961
name of the directory in which the socket file is stored. If
962962
multiple host names are specified, each will be tried in turn in
963963
the order given. The default behavior when <literal>host</literal> is
964-
not specified is to connect to a Unix-domain
964+
not specified, or is empty, is to connect to a Unix-domain
965965
socket<indexterm><primary>Unix domain socket</primary></indexterm> in
966966
<filename>/tmp</filename> (or whatever socket directory was specified
967967
when <productname>PostgreSQL</productname> was built). On machines without
968968
Unix-domain sockets, the default is to connect to <literal>localhost</literal>.
969969
</para>
970970
<para>
971971
A comma-separated list of host names is also accepted, in which case
972-
each host name in the list is tried in order. See
972+
each host name in the list is tried in order; an empty item in the
973+
list selects the default behavior as explained above. See
973974
<xref linkend="libpq-multiple-hosts"/> for details.
974975
</para>
975976
</listitem>
@@ -1020,14 +1021,17 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
10201021
</itemizedlist>
10211022
Note that authentication is likely to fail if <literal>host</literal>
10221023
is not the name of the server at network address <literal>hostaddr</literal>.
1023-
Also, note that <literal>host</literal> rather than <literal>hostaddr</literal>
1024+
Also, when both <literal>host</literal> and <literal>hostaddr</literal>
1025+
are specified, <literal>host</literal>
10241026
is used to identify the connection in a password file (see
10251027
<xref linkend="libpq-pgpass"/>).
10261028
</para>
10271029

10281030
<para>
10291031
A comma-separated list of <literal>hostaddr</literal> values is also
1030-
accepted, in which case each host in the list is tried in order. See
1032+
accepted, in which case each host in the list is tried in order.
1033+
An empty item in the list causes the corresponding host name to be
1034+
used, or the default host name if that is empty as well. See
10311035
<xref linkend="libpq-multiple-hosts"/> for details.
10321036
</para>
10331037
<para>
@@ -1047,9 +1051,12 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
10471051
name extension for Unix-domain
10481052
connections.<indexterm><primary>port</primary></indexterm>
10491053
If multiple hosts were given in the <literal>host</literal> or
1050-
<literal>hostaddr</literal> parameters, this parameter may specify a list
1051-
of ports of equal length, or it may specify a single port number to
1052-
be used for all hosts.
1054+
<literal>hostaddr</literal> parameters, this parameter may specify a
1055+
comma-separated list of ports of the same length as the host list, or
1056+
it may specify a single port number to be used for all hosts.
1057+
An empty string, or an empty item in a comma-separated list,
1058+
specifies the default port number established
1059+
when <productname>PostgreSQL</productname> was built.
10531060
</para>
10541061
</listitem>
10551062
</varlistentry>
@@ -1683,6 +1690,17 @@ char *PQuser(const PGconn *conn);
16831690
char *PQpass(const PGconn *conn);
16841691
</synopsis>
16851692
</para>
1693+
1694+
<para>
1695+
<function>PQpass</function> will return either the password specified
1696+
in the connection parameters, or if there was none and the password
1697+
was obtained from the <link linkend="libpq-pgpass">password
1698+
file</link>, it will return that. In the latter case,
1699+
if multiple hosts were specified in the connection parameters, it is
1700+
not possible to rely on the result of <function>PQpass</function> until
1701+
the connection is established. The status of the connection can be
1702+
checked using the function <function>PQstatus</function>.
1703+
</para>
16861704
</listitem>
16871705
</varlistentry>
16881706

@@ -7521,13 +7539,18 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
75217539
used. (Therefore, put more-specific entries first when you are using
75227540
wildcards.) If an entry needs to contain <literal>:</literal> or
75237541
<literal>\</literal>, escape this character with <literal>\</literal>.
7524-
A host name of <literal>localhost</literal> matches both TCP (host name
7525-
<literal>localhost</literal>) and Unix domain socket (<literal>pghost</literal> empty
7526-
or the default socket directory) connections coming from the local
7527-
machine. In a standby server, a database name of <literal>replication</literal>
7542+
The host name field is matched to the <literal>host</literal> connection
7543+
parameter if that is specified, otherwise to
7544+
the <literal>hostaddr</literal> parameter if that is specified; if neither
7545+
are given then the host name <literal>localhost</literal> is searched for.
7546+
The host name <literal>localhost</literal> is also searched for when
7547+
the connection is a Unix-domain socket connection and
7548+
the <literal>host</literal> parameter
7549+
matches <application>libpq</application>'s default socket directory path.
7550+
In a standby server, a database field of <literal>replication</literal>
75287551
matches streaming replication connections made to the master server.
7529-
The <literal>database</literal> field is of limited usefulness because
7530-
users have the same password for all databases in the same cluster.
7552+
The database field is of limited usefulness otherwise, because users have
7553+
the same password for all databases in the same cluster.
75317554
</para>
75327555

75337556
<para>

src/interfaces/libpq/fe-connect.c

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,8 @@ parse_comma_separated_list(char **startptr, bool *more)
901901
static bool
902902
connectOptions2(PGconn *conn)
903903
{
904+
int i;
905+
904906
/*
905907
* Allocate memory for details about each host to which we might possibly
906908
* try to connect. For that, count the number of elements in the hostaddr
@@ -920,11 +922,10 @@ connectOptions2(PGconn *conn)
920922

921923
/*
922924
* We now have one pg_conn_host structure per possible host. Fill in the
923-
* host details for each one.
925+
* host and hostaddr fields for each, by splitting the parameter strings.
924926
*/
925927
if (conn->pghostaddr != NULL && conn->pghostaddr[0] != '\0')
926928
{
927-
int i;
928929
char *s = conn->pghostaddr;
929930
bool more = true;
930931

@@ -933,8 +934,6 @@ connectOptions2(PGconn *conn)
933934
conn->connhost[i].hostaddr = parse_comma_separated_list(&s, &more);
934935
if (conn->connhost[i].hostaddr == NULL)
935936
goto oom_error;
936-
937-
conn->connhost[i].type = CHT_HOST_ADDRESS;
938937
}
939938

940939
/*
@@ -948,7 +947,6 @@ connectOptions2(PGconn *conn)
948947

949948
if (conn->pghost != NULL && conn->pghost[0] != '\0')
950949
{
951-
int i;
952950
char *s = conn->pghost;
953951
bool more = true;
954952

@@ -957,17 +955,9 @@ connectOptions2(PGconn *conn)
957955
conn->connhost[i].host = parse_comma_separated_list(&s, &more);
958956
if (conn->connhost[i].host == NULL)
959957
goto oom_error;
960-
961-
/* Identify the type of host. */
962-
if (conn->pghostaddr == NULL || conn->pghostaddr[0] == '\0')
963-
{
964-
conn->connhost[i].type = CHT_HOST_NAME;
965-
#ifdef HAVE_UNIX_SOCKETS
966-
if (is_absolute_path(conn->connhost[i].host))
967-
conn->connhost[i].type = CHT_UNIX_SOCKET;
968-
#endif
969-
}
970958
}
959+
960+
/* Check for wrong number of host items. */
971961
if (more || i != conn->nconnhost)
972962
{
973963
conn->status = CONNECTION_BAD;
@@ -979,29 +969,48 @@ connectOptions2(PGconn *conn)
979969
}
980970

981971
/*
982-
* If neither host or hostaddr options was given, connect to default host.
972+
* Now, for each host slot, identify the type of address spec, and fill in
973+
* the default address if nothing was given.
983974
*/
984-
if ((conn->pghostaddr == NULL || conn->pghostaddr[0] == '\0') &&
985-
(conn->pghost == NULL || conn->pghost[0] == '\0'))
975+
for (i = 0; i < conn->nconnhost; i++)
986976
{
987-
Assert(conn->nconnhost == 1);
977+
pg_conn_host *ch = &conn->connhost[i];
978+
979+
if (ch->hostaddr != NULL && ch->hostaddr[0] != '\0')
980+
ch->type = CHT_HOST_ADDRESS;
981+
else if (ch->host != NULL && ch->host[0] != '\0')
982+
{
983+
ch->type = CHT_HOST_NAME;
988984
#ifdef HAVE_UNIX_SOCKETS
989-
conn->connhost[0].host = strdup(DEFAULT_PGSOCKET_DIR);
990-
conn->connhost[0].type = CHT_UNIX_SOCKET;
985+
if (is_absolute_path(ch->host))
986+
ch->type = CHT_UNIX_SOCKET;
987+
#endif
988+
}
989+
else
990+
{
991+
if (ch->host)
992+
free(ch->host);
993+
#ifdef HAVE_UNIX_SOCKETS
994+
ch->host = strdup(DEFAULT_PGSOCKET_DIR);
995+
ch->type = CHT_UNIX_SOCKET;
991996
#else
992-
conn->connhost[0].host = strdup(DefaultHost);
993-
conn->connhost[0].type = CHT_HOST_NAME;
997+
ch->host = strdup(DefaultHost);
998+
ch->type = CHT_HOST_NAME;
994999
#endif
995-
if (conn->connhost[0].host == NULL)
996-
goto oom_error;
1000+
if (ch->host == NULL)
1001+
goto oom_error;
1002+
}
9971003
}
9981004

9991005
/*
10001006
* Next, work out the port number corresponding to each host name.
1007+
*
1008+
* Note: unlike the above for host names, this could leave the port fields
1009+
* as null or empty strings. We will substitute DEF_PGPORT whenever we
1010+
* read such a port field.
10011011
*/
10021012
if (conn->pgport != NULL && conn->pgport[0] != '\0')
10031013
{
1004-
int i;
10051014
char *s = conn->pgport;
10061015
bool more = true;
10071016

@@ -1065,8 +1074,8 @@ connectOptions2(PGconn *conn)
10651074
}
10661075

10671076
/*
1068-
* Supply default password if none given. Note that the password might be
1069-
* different for each host/port pair.
1077+
* If password was not given, try to look it up in password file. Note
1078+
* that the result might be different for each host/port pair.
10701079
*/
10711080
if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
10721081
{
@@ -1089,20 +1098,16 @@ connectOptions2(PGconn *conn)
10891098

10901099
if (conn->pgpassfile != NULL && conn->pgpassfile[0] != '\0')
10911100
{
1092-
int i;
1093-
10941101
for (i = 0; i < conn->nconnhost; i++)
10951102
{
10961103
/*
1097-
* Try to get a password for this host from pgpassfile. We use
1098-
* host name rather than host address in the same manner as
1099-
* PQhost().
1104+
* Try to get a password for this host from file. We use host
1105+
* for the hostname search key if given, else hostaddr (at
1106+
* least one of them is guaranteed nonempty by now).
11001107
*/
1101-
char *pwhost = conn->connhost[i].host;
1108+
const char *pwhost = conn->connhost[i].host;
11021109

1103-
if (conn->connhost[i].type == CHT_HOST_ADDRESS &&
1104-
conn->connhost[i].host != NULL &&
1105-
conn->connhost[i].host[0] != '\0')
1110+
if (pwhost == NULL || pwhost[0] == '\0')
11061111
pwhost = conn->connhost[i].hostaddr;
11071112

11081113
conn->connhost[i].password =
@@ -6385,14 +6390,14 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
63856390
#define LINELEN NAMEDATALEN*5
63866391
char buf[LINELEN];
63876392

6388-
if (dbname == NULL || strlen(dbname) == 0)
6393+
if (dbname == NULL || dbname[0] == '\0')
63896394
return NULL;
63906395

6391-
if (username == NULL || strlen(username) == 0)
6396+
if (username == NULL || username[0] == '\0')
63926397
return NULL;
63936398

63946399
/* 'localhost' matches pghost of '' or the default socket directory */
6395-
if (hostname == NULL)
6400+
if (hostname == NULL || hostname[0] == '\0')
63966401
hostname = DefaultHost;
63976402
else if (is_absolute_path(hostname))
63986403

@@ -6403,7 +6408,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
64036408
if (strcmp(hostname, DEFAULT_PGSOCKET_DIR) == 0)
64046409
hostname = DefaultHost;
64056410

6406-
if (port == NULL)
6411+
if (port == NULL || port[0] == '\0')
64076412
port = DEF_PGPORT_STR;
64086413

64096414
/* If password file cannot be opened, ignore it. */

0 commit comments

Comments
 (0)