Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4178b74

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 73018f5 commit 4178b74

File tree

2 files changed

+79
-69
lines changed

2 files changed

+79
-69
lines changed

src/interfaces/libpq/fe-connect.c

+52-67
Original file line numberDiff line numberDiff line change
@@ -6933,10 +6933,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
69336933
{
69346934
FILE *fp;
69356935
struct stat stat_buf;
6936-
int line_number = 0;
6937-
6938-
#define LINELEN NAMEDATALEN*5
6939-
char buf[LINELEN];
6936+
PQExpBufferData buf;
69406937

69416938
if (dbname == NULL || dbname[0] == '\0')
69426939
return NULL;
@@ -6992,89 +6989,77 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
69926989
if (fp == NULL)
69936990
return NULL;
69946991

6992+
/* Use an expansible buffer to accommodate any reasonable line length */
6993+
initPQExpBuffer(&buf);
6994+
69956995
while (!feof(fp) && !ferror(fp))
69966996
{
6997-
char *t = buf,
6998-
*ret,
6999-
*p1,
7000-
*p2;
7001-
int len;
7002-
int buflen;
6997+
/* Make sure there's a reasonable amount of room in the buffer */
6998+
if (!enlargePQExpBuffer(&buf, 128))
6999+
break;
70037000

7004-
if (fgets(buf, sizeof(buf), fp) == NULL)
7001+
/* Read some data, appending it to what we already have */
7002+
if (fgets(buf.data + buf.len, buf.maxlen - buf.len, fp) == NULL)
70057003
break;
7004+
buf.len += strlen(buf.data + buf.len);
70067005

7007-
line_number++;
7008-
buflen = strlen(buf);
7009-
if (buflen >= sizeof(buf) - 1 && buf[buflen - 1] != '\n')
7006+
/* If we don't yet have a whole line, loop around to read more */
7007+
if (!(buf.len > 0 && buf.data[buf.len - 1] == '\n') && !feof(fp))
7008+
continue;
7009+
7010+
/* ignore comments */
7011+
if (buf.data[0] != '#')
70107012
{
7011-
char rest[LINELEN];
7012-
int restlen;
7013+
char *t = buf.data;
7014+
int len;
70137015

7014-
/*
7015-
* Warn if this password setting line is too long, because it's
7016-
* unexpectedly truncated.
7017-
*/
7018-
if (buf[0] != '#')
7019-
fprintf(stderr,
7020-
libpq_gettext("WARNING: line %d too long in password file \"%s\"\n"),
7021-
line_number, pgpassfile);
7016+
/* strip trailing newline and carriage return */
7017+
len = pg_strip_crlf(t);
70227018

7023-
/* eat rest of the line */
7024-
while (!feof(fp) && !ferror(fp))
7019+
if (len > 0 &&
7020+
(t = pwdfMatchesString(t, hostname)) != NULL &&
7021+
(t = pwdfMatchesString(t, port)) != NULL &&
7022+
(t = pwdfMatchesString(t, dbname)) != NULL &&
7023+
(t = pwdfMatchesString(t, username)) != NULL)
70257024
{
7026-
if (fgets(rest, sizeof(rest), fp) == NULL)
7027-
break;
7028-
restlen = strlen(rest);
7029-
if (restlen < sizeof(rest) - 1 || rest[restlen - 1] == '\n')
7030-
break;
7031-
}
7032-
}
7033-
7034-
/* ignore comments */
7035-
if (buf[0] == '#')
7036-
continue;
7037-
7038-
/* strip trailing newline and carriage return */
7039-
len = pg_strip_crlf(buf);
7025+
/* Found a match. */
7026+
char *ret,
7027+
*p1,
7028+
*p2;
70407029

7041-
if (len == 0)
7042-
continue;
7030+
ret = strdup(t);
70437031

7044-
if ((t = pwdfMatchesString(t, hostname)) == NULL ||
7045-
(t = pwdfMatchesString(t, port)) == NULL ||
7046-
(t = pwdfMatchesString(t, dbname)) == NULL ||
7047-
(t = pwdfMatchesString(t, username)) == NULL)
7048-
continue;
7032+
fclose(fp);
7033+
explicit_bzero(buf.data, buf.maxlen);
7034+
termPQExpBuffer(&buf);
70497035

7050-
/* Found a match. */
7051-
ret = strdup(t);
7052-
fclose(fp);
7036+
if (!ret)
7037+
{
7038+
/* Out of memory. XXX: an error message would be nice. */
7039+
return NULL;
7040+
}
70537041

7054-
if (!ret)
7055-
{
7056-
/* Out of memory. XXX: an error message would be nice. */
7057-
explicit_bzero(buf, sizeof(buf));
7058-
return NULL;
7059-
}
7042+
/* De-escape password. */
7043+
for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
7044+
{
7045+
if (*p1 == '\\' && p1[1] != '\0')
7046+
++p1;
7047+
*p2 = *p1;
7048+
}
7049+
*p2 = '\0';
70607050

7061-
/* De-escape password. */
7062-
for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2)
7063-
{
7064-
if (*p1 == '\\' && p1[1] != '\0')
7065-
++p1;
7066-
*p2 = *p1;
7051+
return ret;
7052+
}
70677053
}
7068-
*p2 = '\0';
70697054

7070-
return ret;
7055+
/* No match, reset buffer to prepare for next line. */
7056+
buf.len = 0;
70717057
}
70727058

70737059
fclose(fp);
7074-
explicit_bzero(buf, sizeof(buf));
7060+
explicit_bzero(buf.data, buf.maxlen);
7061+
termPQExpBuffer(&buf);
70757062
return NULL;
7076-
7077-
#undef LINELEN
70787063
}
70797064

70807065

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

+27-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818
else
1919
{
20-
plan tests => 10;
20+
plan tests => 13;
2121
}
2222

2323

@@ -45,7 +45,9 @@ sub test_role
4545

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

48-
my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role ]);
48+
local $Test::Builder::Level = $Test::Builder::Level + 1;
49+
50+
my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role, '-w' ]);
4951
is($res, $expected_res,
5052
"authentication $status_string for method $method, role $role");
5153
return;
@@ -96,3 +98,26 @@ sub test_role
9698
reset_pg_hba($node, 'scram-sha-256');
9799
$ENV{"PGCHANNELBINDING"} = 'require';
98100
test_role($node, 'scram_role', 'scram-sha-256', 2);
101+
102+
# Test .pgpass processing; but use a temp file, don't overwrite the real one!
103+
my $pgpassfile = "${TestLib::tmp_check}/pgpass";
104+
105+
delete $ENV{"PGPASSWORD"};
106+
delete $ENV{"PGCHANNELBINDING"};
107+
$ENV{"PGPASSFILE"} = $pgpassfile;
108+
109+
append_to_file($pgpassfile, qq!
110+
# 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.
111+
*:*:postgres:scram_role:pass:this is not part of the password.
112+
!);
113+
chmod 0600, $pgpassfile or die;
114+
115+
reset_pg_hba($node, 'password');
116+
test_role($node, 'scram_role', 'password from pgpass', 0);
117+
test_role($node, 'md5_role', 'password from pgpass', 2);
118+
119+
append_to_file($pgpassfile, qq!
120+
*:*:*:md5_role:p\\ass
121+
!);
122+
123+
test_role($node, 'md5_role', 'password from pgpass', 0);

0 commit comments

Comments
 (0)