Skip to content

Commit 0c0a3a8

Browse files
committed
Teach libpq to handle arbitrary-length lines in .pgpass files.
Historically there's been a hard-wired assumption here that no line of a .pgpass file could be as long as NAMEDATALEN*5 bytes. That's a bit shaky to start off with, because (a) there's no reason to suppose that host names fit in NAMEDATALEN, and (b) this figure fails to allow for backslash escape characters. However, it fails completely if someone wants to use a very long password, and we're now hearing reports of people wanting to use "security tokens" that can run up to several hundred bytes. Another angle is that the file is specified to allow comment lines, but there's no reason to assume that long comment lines aren't possible. Rather than guessing at what might be a more suitable limit, let's replace the fixed-size buffer with an expansible PQExpBuffer. That adds one malloc/free cycle to the typical use-case, but that's surely pretty cheap relative to the I/O this code has to do. Also, add TAP test cases to exercise this code, because there was no test coverage before. This reverts most of commit 2eb3bc5, as there's no longer a need for a warning message about overlength .pgpass lines. (I kept the explicit check for comment lines, though.) In HEAD and v13, this also fixes an oversight in 74a308c: there's not much point in explicit_bzero'ing the line buffer if we only do so in two of the three exit paths. Back-patch to all supported branches, except that the test case only goes back to v10 where src/test/authentication/ was added. Discussion: https://postgr.es/m/4187382.1598909041@sss.pgh.pa.us
1 parent ffc61f8 commit 0c0a3a8

File tree

2 files changed

+84
-44
lines changed

2 files changed

+84
-44
lines changed

src/interfaces/libpq/fe-connect.c

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6458,9 +6458,7 @@ passwordFromFile(char *hostname, char *port, char *dbname,
64586458
{
64596459
FILE *fp;
64606460
struct stat stat_buf;
6461-
6462-
#define LINELEN NAMEDATALEN*5
6463-
char buf[LINELEN];
6461+
PQExpBufferData buf;
64646462

64656463
if (dbname == NULL || dbname[0] == '\0')
64666464
return NULL;
@@ -6516,63 +6514,81 @@ passwordFromFile(char *hostname, char *port, char *dbname,
65166514
if (fp == NULL)
65176515
return NULL;
65186516

6517+
/* Use an expansible buffer to accommodate any reasonable line length */
6518+
initPQExpBuffer(&buf);
6519+
65196520
while (!feof(fp) && !ferror(fp))
65206521
{
6521-
char *t = buf,
6522-
*ret,
6523-
*p1,
6524-
*p2;
6525-
int len;
6522+
/* Make sure there's a reasonable amount of room in the buffer */
6523+
if (!enlargePQExpBuffer(&buf, 128))
6524+
break;
65266525

6527-
if (fgets(buf, sizeof(buf), fp) == NULL)
6526+
/* Read some data, appending it to what we already have */
6527+
if (fgets(buf.data + buf.len, buf.maxlen - buf.len, fp) == NULL)
65286528
break;
6529+
buf.len += strlen(buf.data + buf.len);
65296530

6530-
len = strlen(buf);
6531+
/* If we don't yet have a whole line, loop around to read more */
6532+
if (!(buf.len > 0 && buf.data[buf.len - 1] == '\n') && !feof(fp))
6533+
continue;
65316534

6532-
/* Remove trailing newline */
6533-
if (len > 0 && buf[len - 1] == '\n')
6535+
/* ignore comments */
6536+
if (buf.data[0] != '#')
65346537
{
6535-
buf[--len] = '\0';
6536-
/* Handle DOS-style line endings, too, even when not on Windows */
6537-
if (len > 0 && buf[len - 1] == '\r')
6538-
buf[--len] = '\0';
6539-
}
6538+
char *t = buf.data;
6539+
int len = buf.len;
65406540

6541-
if (len == 0)
6542-
continue;
6541+
/* Remove trailing newline */
6542+
if (len > 0 && t[len - 1] == '\n')
6543+
{
6544+
t[--len] = '\0';
6545+
/* Handle DOS-style line endings, too */
6546+
if (len > 0 && t[len - 1] == '\r')
6547+
t[--len] = '\0';
6548+
}
65436549

6544-
if ((t = pwdfMatchesString(t, hostname)) == NULL ||
6545-
(t = pwdfMatchesString(t, port)) == NULL ||
6546-
(t = pwdfMatchesString(t, dbname)) == NULL ||
6547-
(t = pwdfMatchesString(t, username)) == NULL)
6548-
continue;
6550+
if (len > 0 &&
6551+
(t = pwdfMatchesString(t, hostname)) != NULL &&
6552+
(t = pwdfMatchesString(t, port)) != NULL &&
6553+
(t = pwdfMatchesString(t, dbname)) != NULL &&
6554+
(t = pwdfMatchesString(t, username)) != NULL)
6555+
{
6556+
/* Found a match. */
6557+
char *ret,
6558+
*p1,
6559+
*p2;
65496560

6550-
/* Found a match. */
6551-
ret = strdup(t);
6552-
fclose(fp);
6561+
ret = strdup(t);
65536562

6554-
if (!ret)
6555-
{
6556-
/* Out of memory. XXX: an error message would be nice. */
6557-
return NULL;
6558-
}
6563+
fclose(fp);
6564+
termPQExpBuffer(&buf);
65596565

6560-
/* De-escape password. */
6561-
for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
6562-
{
6563-
if (*p1 == '\\' && p1[1] != '\0')
6564-
++p1;
6565-
*p2 = *p1;
6566+
if (!ret)
6567+
{
6568+
/* Out of memory. XXX: an error message would be nice. */
6569+
return NULL;
6570+
}
6571+
6572+
/* De-escape password. */
6573+
for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
6574+
{
6575+
if (*p1 == '\\' && p1[1] != '\0')
6576+
++p1;
6577+
*p2 = *p1;
6578+
}
6579+
*p2 = '\0';
6580+
6581+
return ret;
6582+
}
65666583
}
6567-
*p2 = '\0';
65686584

6569-
return ret;
6585+
/* No match, reset buffer to prepare for next line. */
6586+
buf.len = 0;
65706587
}
65716588

65726589
fclose(fp);
6590+
termPQExpBuffer(&buf);
65736591
return NULL;
6574-
6575-
#undef LINELEN
65766592
}
65776593

65786594

src/test/authentication/t/001_password.pl

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818
else
1919
{
20-
plan tests => 8;
20+
plan tests => 11;
2121
}
2222

2323

@@ -44,7 +44,9 @@ sub test_role
4444

4545
$status_string = 'success' if ($expected_res eq 0);
4646

47-
my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role ]);
47+
local $Test::Builder::Level = $Test::Builder::Level + 1;
48+
49+
my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role, '-w' ]);
4850
is($res, $expected_res,
4951
"authentication $status_string for method $method, role $role");
5052
}
@@ -83,3 +85,25 @@ sub test_role
8385
reset_pg_hba($node, 'md5');
8486
test_role($node, 'scram_role', 'md5', 0);
8587
test_role($node, 'md5_role', 'md5', 0);
88+
89+
# Test .pgpass processing; but use a temp file, don't overwrite the real one!
90+
my $pgpassfile = "${TestLib::tmp_check}/pgpass";
91+
92+
delete $ENV{"PGPASSWORD"};
93+
$ENV{"PGPASSFILE"} = $pgpassfile;
94+
95+
append_to_file($pgpassfile, qq!
96+
# This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file. This very long comment is just here to exercise handling of long lines in the file.
97+
*:*:postgres:scram_role:pass:this is not part of the password.
98+
!);
99+
chmod 0600, $pgpassfile or die;
100+
101+
reset_pg_hba($node, 'password');
102+
test_role($node, 'scram_role', 'password from pgpass', 0);
103+
test_role($node, 'md5_role', 'password from pgpass', 2);
104+
105+
append_to_file($pgpassfile, qq!
106+
*:*:*:md5_role:p\\ass
107+
!);
108+
109+
test_role($node, 'md5_role', 'password from pgpass', 0);

0 commit comments

Comments
 (0)