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:
parent
d1ecd53947
commit
ba005f193d
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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? */
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user