Allow password file name to be specified as a libpq connection parameter.

Formerly an alternate password file could only be selected via the
environment variable PGPASSFILE; now it can also be selected via a
new connection parameter "passfile", corresponding to the conventions
for most other connection parameters.  There was some concern about
this creating a security weakness, but it was agreed that that argument
was pretty thin, and there are clear use-cases for handling password
files this way.

Julian Markwort, reviewed by Fabien Coelho, some adjustments by me

Discussion: https://postgr.es/m/a4b4f4f1-7b58-a0e8-5268-5f7db8e8ccaa@uni-muenster.de
This commit is contained in:
Tom Lane 2017-01-24 17:06:21 -05:00
parent d1ecd53947
commit ba005f193d
4 changed files with 81 additions and 62 deletions

View File

@ -943,7 +943,7 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
Note that authentication is likely to fail if <literal>host</> Note that authentication is likely to fail if <literal>host</>
is not the name of the server at network address <literal>hostaddr</>. is not the name of the server at network address <literal>hostaddr</>.
Also, note that <literal>host</> rather than <literal>hostaddr</> Also, note that <literal>host</> rather than <literal>hostaddr</>
is used to identify the connection in <filename>~/.pgpass</> (see is used to identify the connection in a password file (see
<xref linkend="libpq-pgpass">). <xref linkend="libpq-pgpass">).
</para> </para>
@ -1002,6 +1002,19 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry id="libpq-connect-passfile" xreflabel="passfile">
<term><literal>passfile</literal></term>
<listitem>
<para>
Specifies the name of the file used to store passwords
(see <xref linkend="libpq-pgpass">).
Defaults to <filename>~/.pgpass</filename>, or
<filename>%APPDATA%\postgresql\pgpass.conf</> on Microsoft Windows.
(No error is reported if this file does not exist.)
</para>
</listitem>
</varlistentry>
<varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout"> <varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout">
<term><literal>connect_timeout</literal></term> <term><literal>connect_timeout</literal></term>
<listitem> <listitem>
@ -6893,8 +6906,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
Use of this environment variable Use of this environment variable
is not recommended for security reasons, as some operating systems is not recommended for security reasons, as some operating systems
allow non-root users to see process environment variables via allow non-root users to see process environment variables via
<application>ps</>; instead consider using the <application>ps</>; instead consider using a password file
<filename>~/.pgpass</> file (see <xref linkend="libpq-pgpass">). (see <xref linkend="libpq-pgpass">).
</para> </para>
</listitem> </listitem>
@ -6903,9 +6916,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
<indexterm> <indexterm>
<primary><envar>PGPASSFILE</envar></primary> <primary><envar>PGPASSFILE</envar></primary>
</indexterm> </indexterm>
<envar>PGPASSFILE</envar> specifies the name of the password file to <envar>PGPASSFILE</envar> behaves the same as the <xref
use for lookups. If not set, it defaults to <filename>~/.pgpass</> linkend="libpq-connect-passfile"> connection parameter.
(see <xref linkend="libpq-pgpass">).
</para> </para>
</listitem> </listitem>
@ -7187,13 +7199,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</indexterm> </indexterm>
<para> <para>
The file <filename>.pgpass</filename> in a user's home directory or the The file <filename>.pgpass</filename> in a user's home directory can
file referenced by <envar>PGPASSFILE</envar> can contain passwords to contain passwords to
be used if the connection requires a password (and no password has been be used if the connection requires a password (and no password has been
specified otherwise). On Microsoft Windows the file is named specified otherwise). On Microsoft Windows the file is named
<filename>%APPDATA%\postgresql\pgpass.conf</> (where <filename>%APPDATA%\postgresql\pgpass.conf</> (where
<filename>%APPDATA%</> refers to the Application Data subdirectory in <filename>%APPDATA%</> refers to the Application Data subdirectory in
the user's profile). the user's profile).
Alternatively, a password file can be specified
using the connection parameter <xref linkend="libpq-connect-passfile">
or the environment variable <envar>PGPASSFILE</envar>.
</para> </para>
<para> <para>
@ -7219,8 +7234,8 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
</para> </para>
<para> <para>
On Unix systems, the permissions on <filename>.pgpass</filename> must On Unix systems, the permissions on a password file must
disallow any access to world or group; achieve this by the command disallow any access to world or group; achieve this by a command such as
<command>chmod 0600 ~/.pgpass</command>. If the permissions are less <command>chmod 0600 ~/.pgpass</command>. If the permissions are less
strict than this, the file will be ignored. On Microsoft Windows, it strict than this, the file will be ignored. On Microsoft Windows, it
is assumed that the file is stored in a directory that is secure, so is assumed that the file is stored in a directory that is secure, so

View File

@ -686,11 +686,12 @@ pg_fe_sendauth(AuthRequest areq, PGconn *conn)
case AUTH_REQ_MD5: case AUTH_REQ_MD5:
case AUTH_REQ_PASSWORD: case AUTH_REQ_PASSWORD:
{ {
char *password = conn->connhost[conn->whichhost].password; char *password;
conn->password_needed = true;
password = conn->connhost[conn->whichhost].password;
if (password == NULL) if (password == NULL)
password = conn->pgpass; password = conn->pgpass;
conn->password_needed = true;
if (password == NULL || password[0] == '\0') if (password == NULL || password[0] == '\0')
{ {
printfPQExpBuffer(&conn->errorMessage, printfPQExpBuffer(&conn->errorMessage,

View File

@ -107,7 +107,6 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#define DefaultTty "" #define DefaultTty ""
#define DefaultOption "" #define DefaultOption ""
#define DefaultAuthtype "" #define DefaultAuthtype ""
#define DefaultPassword ""
#define DefaultTargetSessionAttrs "any" #define DefaultTargetSessionAttrs "any"
#ifdef USE_SSL #ifdef USE_SSL
#define DefaultSSLMode "prefer" #define DefaultSSLMode "prefer"
@ -185,6 +184,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Database-Password", "*", 20, "Database-Password", "*", 20,
offsetof(struct pg_conn, pgpass)}, offsetof(struct pg_conn, pgpass)},
{"passfile", "PGPASSFILE", NULL, NULL,
"Database-Password-File", "", 64,
offsetof(struct pg_conn, pgpassfile)},
{"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL, {"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL,
"Connect-timeout", "", 10, /* strlen(INT32_MAX) == 10 */ "Connect-timeout", "", 10, /* strlen(INT32_MAX) == 10 */
offsetof(struct pg_conn, connect_timeout)}, offsetof(struct pg_conn, connect_timeout)},
@ -382,10 +385,9 @@ static int parseServiceFile(const char *serviceFile,
PQExpBuffer errorMessage, PQExpBuffer errorMessage,
bool *group_found); bool *group_found);
static char *pwdfMatchesString(char *buf, char *token); static char *pwdfMatchesString(char *buf, char *token);
static char *PasswordFromFile(char *hostname, char *port, char *dbname, static char *passwordFromFile(char *hostname, char *port, char *dbname,
char *username); char *username, char *pgpassfile);
static bool getPgPassFilename(char *pgpassfile); static void pgpassfileWarning(PGconn *conn);
static void dot_pg_pass_warning(PGconn *conn);
static void default_threadlock(int acquire); static void default_threadlock(int acquire);
@ -957,19 +959,40 @@ connectOptions2(PGconn *conn)
{ {
int i; int i;
if (conn->pgpass) if (conn->pgpassfile == NULL || conn->pgpassfile[0] == '\0')
free(conn->pgpass);
conn->pgpass = strdup(DefaultPassword);
if (!conn->pgpass)
goto oom_error;
for (i = 0; i < conn->nconnhost; ++i)
{ {
/* Identify password file to use; fail if we can't */
char homedir[MAXPGPATH];
if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not get home directory to locate password file\n"));
return false;
}
if (conn->pgpassfile)
free(conn->pgpassfile);
conn->pgpassfile = malloc(MAXPGPATH);
if (!conn->pgpassfile)
goto oom_error;
snprintf(conn->pgpassfile, MAXPGPATH, "%s/%s", homedir, PGPASSFILE);
}
for (i = 0; i < conn->nconnhost; i++)
{
/* Try to get a password for this host from pgpassfile */
conn->connhost[i].password = conn->connhost[i].password =
PasswordFromFile(conn->connhost[i].host, passwordFromFile(conn->connhost[i].host,
conn->connhost[i].port, conn->connhost[i].port,
conn->dbName, conn->pguser); conn->dbName,
conn->pguser,
conn->pgpassfile);
/* If we got one, set pgpassfile_used */
if (conn->connhost[i].password != NULL) if (conn->connhost[i].password != NULL)
conn->dot_pgpass_used = true; conn->pgpassfile_used = true;
} }
} }
@ -3016,7 +3039,7 @@ keep_going: /* We will come back to here until there is
error_return: error_return:
dot_pg_pass_warning(conn); pgpassfileWarning(conn);
/* /*
* We used to close the socket at this point, but that makes it awkward * We used to close the socket at this point, but that makes it awkward
@ -3147,7 +3170,7 @@ makeEmptyPGconn(void)
conn->sock = PGINVALID_SOCKET; conn->sock = PGINVALID_SOCKET;
conn->auth_req_received = false; conn->auth_req_received = false;
conn->password_needed = false; conn->password_needed = false;
conn->dot_pgpass_used = false; conn->pgpassfile_used = false;
#ifdef USE_SSL #ifdef USE_SSL
conn->allow_ssl_try = true; conn->allow_ssl_try = true;
conn->wait_ssl_try = false; conn->wait_ssl_try = false;
@ -3256,6 +3279,8 @@ freePGconn(PGconn *conn)
free(conn->pguser); free(conn->pguser);
if (conn->pgpass) if (conn->pgpass)
free(conn->pgpass); free(conn->pgpass);
if (conn->pgpassfile)
free(conn->pgpassfile);
if (conn->keepalives) if (conn->keepalives)
free(conn->keepalives); free(conn->keepalives);
if (conn->keepalives_idle) if (conn->keepalives_idle)
@ -5794,6 +5819,9 @@ PQpass(const PGconn *conn)
password = conn->connhost[conn->whichhost].password; password = conn->connhost[conn->whichhost].password;
if (password == NULL) if (password == NULL)
password = conn->pgpass; password = conn->pgpass;
/* Historically we've returned "" not NULL for no password specified */
if (password == NULL)
password = "";
return password; return password;
} }
@ -6160,10 +6188,10 @@ pwdfMatchesString(char *buf, char *token)
/* Get a password from the password file. Return value is malloc'd. */ /* Get a password from the password file. Return value is malloc'd. */
static char * static char *
PasswordFromFile(char *hostname, char *port, char *dbname, char *username) passwordFromFile(char *hostname, char *port, char *dbname,
char *username, char *pgpassfile)
{ {
FILE *fp; FILE *fp;
char pgpassfile[MAXPGPATH];
struct stat stat_buf; struct stat stat_buf;
#define LINELEN NAMEDATALEN*5 #define LINELEN NAMEDATALEN*5
@ -6190,9 +6218,6 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
if (port == NULL) if (port == NULL)
port = DEF_PGPORT_STR; port = DEF_PGPORT_STR;
if (!getPgPassFilename(pgpassfile))
return NULL;
/* If password file cannot be opened, ignore it. */ /* If password file cannot be opened, ignore it. */
if (stat(pgpassfile, &stat_buf) != 0) if (stat(pgpassfile, &stat_buf) != 0)
return NULL; return NULL;
@ -6286,46 +6311,23 @@ PasswordFromFile(char *hostname, char *port, char *dbname, char *username)
} }
static bool
getPgPassFilename(char *pgpassfile)
{
char *passfile_env;
if ((passfile_env = getenv("PGPASSFILE")) != NULL)
/* use the literal path from the environment, if set */
strlcpy(pgpassfile, passfile_env, MAXPGPATH);
else
{
char homedir[MAXPGPATH];
if (!pqGetHomeDirectory(homedir, sizeof(homedir)))
return false;
snprintf(pgpassfile, MAXPGPATH, "%s/%s", homedir, PGPASSFILE);
}
return true;
}
/* /*
* If the connection failed, we should mention if * If the connection failed, we should mention if
* we got the password from .pgpass in case that * we got the password from the pgpassfile in case that
* password is wrong. * password is wrong.
*/ */
static void static void
dot_pg_pass_warning(PGconn *conn) pgpassfileWarning(PGconn *conn)
{ {
/* If it was 'invalid authorization', add .pgpass mention */ /* If it was 'invalid authorization', add pgpassfile mention */
/* only works with >= 9.0 servers */ /* only works with >= 9.0 servers */
if (conn->dot_pgpass_used && conn->password_needed && conn->result && if (conn->pgpassfile_used && conn->password_needed && conn->result &&
strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE), strcmp(PQresultErrorField(conn->result, PG_DIAG_SQLSTATE),
ERRCODE_INVALID_PASSWORD) == 0) ERRCODE_INVALID_PASSWORD) == 0)
{ {
char pgpassfile[MAXPGPATH];
if (!getPgPassFilename(pgpassfile))
return;
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("password retrieved from file \"%s\"\n"), libpq_gettext("password retrieved from file \"%s\"\n"),
pgpassfile); conn->pgpassfile);
} }
} }

View File

@ -343,6 +343,7 @@ struct pg_conn
char *replication; /* connect as the replication standby? */ char *replication; /* connect as the replication standby? */
char *pguser; /* Postgres username and password, if any */ char *pguser; /* Postgres username and password, if any */
char *pgpass; char *pgpass;
char *pgpassfile; /* path to a file containing password(s) */
char *keepalives; /* use TCP keepalives? */ char *keepalives; /* use TCP keepalives? */
char *keepalives_idle; /* time between TCP keepalives */ char *keepalives_idle; /* time between TCP keepalives */
char *keepalives_interval; /* time between TCP keepalive char *keepalives_interval; /* time between TCP keepalive
@ -407,7 +408,7 @@ struct pg_conn
bool auth_req_received; /* true if any type of auth req bool auth_req_received; /* true if any type of auth req
* received */ * received */
bool password_needed; /* true if server demanded a password */ bool password_needed; /* true if server demanded a password */
bool dot_pgpass_used; /* true if used .pgpass */ bool pgpassfile_used; /* true if password is from pgpassfile */
bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */ bool sigpipe_so; /* have we masked SIGPIPE via SO_NOSIGPIPE? */
bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */ bool sigpipe_flag; /* can we mask SIGPIPE via MSG_NOSIGNAL? */