diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index c8ea59e5a2a..0287fb54bb5 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -6459,9 +6459,7 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname, { FILE *fp; struct stat stat_buf; - -#define LINELEN NAMEDATALEN*5 - char buf[LINELEN]; + PQExpBufferData buf; if (dbname == NULL || dbname[0] == '\0') return NULL; @@ -6517,63 +6515,81 @@ passwordFromFile(const char *hostname, const char *port, const char *dbname, if (fp == NULL) return NULL; + /* Use an expansible buffer to accommodate any reasonable line length */ + initPQExpBuffer(&buf); + while (!feof(fp) && !ferror(fp)) { - char *t = buf, - *ret, - *p1, - *p2; - int len; - - if (fgets(buf, sizeof(buf), fp) == NULL) + /* Make sure there's a reasonable amount of room in the buffer */ + if (!enlargePQExpBuffer(&buf, 128)) 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); - /* Remove trailing newline */ - if (len > 0 && buf[len - 1] == '\n') - { - buf[--len] = '\0'; - /* Handle DOS-style line endings, too, even when not on Windows */ - if (len > 0 && buf[len - 1] == '\r') - buf[--len] = '\0'; - } - - if (len == 0) + /* 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; - if ((t = pwdfMatchesString(t, hostname)) == NULL || - (t = pwdfMatchesString(t, port)) == NULL || - (t = pwdfMatchesString(t, dbname)) == NULL || - (t = pwdfMatchesString(t, username)) == NULL) - continue; - - /* Found a match. */ - ret = strdup(t); - fclose(fp); - - if (!ret) + /* ignore comments */ + if (buf.data[0] != '#') { - /* Out of memory. XXX: an error message would be nice. */ - return NULL; + char *t = buf.data; + int len = buf.len; + + /* Remove trailing newline */ + if (len > 0 && t[len - 1] == '\n') + { + t[--len] = '\0'; + /* Handle DOS-style line endings, too */ + if (len > 0 && t[len - 1] == '\r') + t[--len] = '\0'; + } + + if (len > 0 && + (t = pwdfMatchesString(t, hostname)) != NULL && + (t = pwdfMatchesString(t, port)) != NULL && + (t = pwdfMatchesString(t, dbname)) != NULL && + (t = pwdfMatchesString(t, username)) != NULL) + { + /* Found a match. */ + char *ret, + *p1, + *p2; + + ret = strdup(t); + + fclose(fp); + termPQExpBuffer(&buf); + + if (!ret) + { + /* Out of memory. XXX: an error message would be nice. */ + return NULL; + } + + /* De-escape password. */ + for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2) + { + if (*p1 == '\\' && p1[1] != '\0') + ++p1; + *p2 = *p1; + } + *p2 = '\0'; + + return ret; + } } - /* De-escape password. */ - for (p1 = p2 = ret; *p1 != ':' && *p1 != '\0'; ++p1, ++p2) - { - if (*p1 == '\\' && p1[1] != '\0') - ++p1; - *p2 = *p1; - } - *p2 = '\0'; - - return ret; + /* No match, reset buffer to prepare for next line. */ + buf.len = 0; } fclose(fp); + termPQExpBuffer(&buf); return NULL; - -#undef LINELEN } diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl index 3a3b0eb7e80..5976d877936 100644 --- a/src/test/authentication/t/001_password.pl +++ b/src/test/authentication/t/001_password.pl @@ -17,7 +17,7 @@ if ($windows_os) } else { - plan tests => 8; + plan tests => 11; } @@ -45,7 +45,9 @@ sub test_role $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, "authentication $status_string for method $method, role $role"); return; @@ -86,3 +88,25 @@ test_role($node, 'md5_role', 'scram-sha-256', 2); reset_pg_hba($node, 'md5'); test_role($node, 'scram_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);