mirror of
https://github.com/postgres/postgres.git
synced 2025-05-05 09:19:17 +03:00
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 2eb3bc588, 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 74a308cf5: 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
This commit is contained in:
parent
b235936a1d
commit
df8020b329
@ -6459,9 +6459,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
|
|||||||
{
|
{
|
||||||
FILE *fp;
|
FILE *fp;
|
||||||
struct stat stat_buf;
|
struct stat stat_buf;
|
||||||
|
PQExpBufferData buf;
|
||||||
#define LINELEN NAMEDATALEN*5
|
|
||||||
char buf[LINELEN];
|
|
||||||
|
|
||||||
if (dbname == NULL || dbname[0] == '\0')
|
if (dbname == NULL || dbname[0] == '\0')
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -6517,40 +6515,54 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
|
|||||||
if (fp == NULL)
|
if (fp == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
/* Use an expansible buffer to accommodate any reasonable line length */
|
||||||
|
initPQExpBuffer(&buf);
|
||||||
|
|
||||||
while (!feof(fp) && !ferror(fp))
|
while (!feof(fp) && !ferror(fp))
|
||||||
{
|
{
|
||||||
char *t = buf,
|
/* Make sure there's a reasonable amount of room in the buffer */
|
||||||
*ret,
|
if (!enlargePQExpBuffer(&buf, 128))
|
||||||
*p1,
|
|
||||||
*p2;
|
|
||||||
int len;
|
|
||||||
|
|
||||||
if (fgets(buf, sizeof(buf), fp) == NULL)
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
len = strlen(buf);
|
/* Read some data, appending it to what we already have */
|
||||||
|
if (fgets(buf.data + buf.len, buf.maxlen - buf.len, fp) == NULL)
|
||||||
|
break;
|
||||||
|
buf.len += strlen(buf.data + buf.len);
|
||||||
|
|
||||||
|
/* If we don't yet have a whole line, loop around to read more */
|
||||||
|
if (!(buf.len > 0 && buf.data[buf.len - 1] == '\n') && !feof(fp))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* ignore comments */
|
||||||
|
if (buf.data[0] != '#')
|
||||||
|
{
|
||||||
|
char *t = buf.data;
|
||||||
|
int len = buf.len;
|
||||||
|
|
||||||
/* Remove trailing newline */
|
/* Remove trailing newline */
|
||||||
if (len > 0 && buf[len - 1] == '\n')
|
if (len > 0 && t[len - 1] == '\n')
|
||||||
{
|
{
|
||||||
buf[--len] = '\0';
|
t[--len] = '\0';
|
||||||
/* Handle DOS-style line endings, too, even when not on Windows */
|
/* Handle DOS-style line endings, too */
|
||||||
if (len > 0 && buf[len - 1] == '\r')
|
if (len > 0 && t[len - 1] == '\r')
|
||||||
buf[--len] = '\0';
|
t[--len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len == 0)
|
if (len > 0 &&
|
||||||
continue;
|
(t = pwdfMatchesString(t, hostname)) != NULL &&
|
||||||
|
(t = pwdfMatchesString(t, port)) != NULL &&
|
||||||
if ((t = pwdfMatchesString(t, hostname)) == NULL ||
|
(t = pwdfMatchesString(t, dbname)) != NULL &&
|
||||||
(t = pwdfMatchesString(t, port)) == NULL ||
|
(t = pwdfMatchesString(t, username)) != NULL)
|
||||||
(t = pwdfMatchesString(t, dbname)) == NULL ||
|
{
|
||||||
(t = pwdfMatchesString(t, username)) == NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Found a match. */
|
/* Found a match. */
|
||||||
|
char *ret,
|
||||||
|
*p1,
|
||||||
|
*p2;
|
||||||
|
|
||||||
ret = strdup(t);
|
ret = strdup(t);
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
termPQExpBuffer(&buf);
|
||||||
|
|
||||||
if (!ret)
|
if (!ret)
|
||||||
{
|
{
|
||||||
@ -6569,11 +6581,15 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname,
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No match, reset buffer to prepare for next line. */
|
||||||
|
buf.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
|
termPQExpBuffer(&buf);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
#undef LINELEN
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ if ($windows_os)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
plan tests => 8;
|
plan tests => 11;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -45,7 +45,9 @@ sub test_role
|
|||||||
|
|
||||||
$status_string = 'success' if ($expected_res eq 0);
|
$status_string = 'success' if ($expected_res eq 0);
|
||||||
|
|
||||||
my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role ]);
|
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||||
|
|
||||||
|
my $res = $node->psql('postgres', undef, extra_params => [ '-U', $role, '-w' ]);
|
||||||
is($res, $expected_res,
|
is($res, $expected_res,
|
||||||
"authentication $status_string for method $method, role $role");
|
"authentication $status_string for method $method, role $role");
|
||||||
return;
|
return;
|
||||||
@ -86,3 +88,25 @@ test_role($node, 'md5_role', 'scram-sha-256', 2);
|
|||||||
reset_pg_hba($node, 'md5');
|
reset_pg_hba($node, 'md5');
|
||||||
test_role($node, 'scram_role', 'md5', 0);
|
test_role($node, 'scram_role', 'md5', 0);
|
||||||
test_role($node, 'md5_role', 'md5', 0);
|
test_role($node, 'md5_role', 'md5', 0);
|
||||||
|
|
||||||
|
# Test .pgpass processing; but use a temp file, don't overwrite the real one!
|
||||||
|
my $pgpassfile = "${TestLib::tmp_check}/pgpass";
|
||||||
|
|
||||||
|
delete $ENV{"PGPASSWORD"};
|
||||||
|
$ENV{"PGPASSFILE"} = $pgpassfile;
|
||||||
|
|
||||||
|
append_to_file($pgpassfile, qq!
|
||||||
|
# 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.
|
||||||
|
*:*:postgres:scram_role:pass:this is not part of the password.
|
||||||
|
!);
|
||||||
|
chmod 0600, $pgpassfile or die;
|
||||||
|
|
||||||
|
reset_pg_hba($node, 'password');
|
||||||
|
test_role($node, 'scram_role', 'password from pgpass', 0);
|
||||||
|
test_role($node, 'md5_role', 'password from pgpass', 2);
|
||||||
|
|
||||||
|
append_to_file($pgpassfile, qq!
|
||||||
|
*:*:*:md5_role:p\\ass
|
||||||
|
!);
|
||||||
|
|
||||||
|
test_role($node, 'md5_role', 'password from pgpass', 0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user