Commit d1c6a14b authored by Tom Lane's avatar Tom Lane

Fix failure to reset libpq's state fully between connection attempts.

The logic in PQconnectPoll() did not take care to ensure that all of
a PGconn's internal state variables were reset before trying a new
connection attempt.  If we got far enough in the connection sequence
to have changed any of these variables, and then decided to try a new
server address or server name, the new connection might be completed
with some state that really only applied to the failed connection.

While this has assorted bad consequences, the only one that is clearly
a security issue is that password_needed didn't get reset, so that
if the first server asked for a password and the second didn't,
PQconnectionUsedPassword() would return an incorrect result.  This
could be leveraged by unprivileged users of dblink or postgres_fdw
to allow them to use server-side login credentials that they should
not be able to use.

Other notable problems include the possibility of forcing a v2-protocol
connection to a server capable of supporting v3, or overriding
"sslmode=prefer" to cause a non-encrypted connection to a server that
would have accepted an encrypted one.  Those are certainly bugs but
it's harder to paint them as security problems in themselves.  However,
forcing a v2-protocol connection could result in libpq having a wrong
idea of the server's standard_conforming_strings setting, which opens
the door to SQL-injection attacks.  The extent to which that's actually
a problem, given the prerequisite that the attacker needs control of
the client's connection parameters, is unclear.

These problems have existed for a long time, but became more easily
exploitable in v10, both because it introduced easy ways to force libpq
to abandon a connection attempt at a late stage and then try another one
(rather than just giving up), and because it provided an easy way to
specify multiple target hosts.

Fix by rearranging PQconnectPoll's state machine to provide centralized
places to reset state properly when moving to a new target host or when
dropping and retrying a connection to the same host.

Tom Lane, reviewed by Noah Misch.  Our thanks to Andrew Krasichkov
for finding and reporting the problem.

Security: CVE-2018-10915
parent aa291a4c
...@@ -415,7 +415,8 @@ pgthreadlock_t pg_g_threadlock = default_threadlock; ...@@ -415,7 +415,8 @@ pgthreadlock_t pg_g_threadlock = default_threadlock;
* *
* Close any physical connection to the server, and reset associated * Close any physical connection to the server, and reset associated
* state inside the connection object. We don't release state that * state inside the connection object. We don't release state that
* would be needed to reconnect, though. * would be needed to reconnect, though, nor local state that might still
* be useful later.
* *
* We can always flush the output buffer, since there's no longer any hope * We can always flush the output buffer, since there's no longer any hope
* of sending that data. However, unprocessed input data might still be * of sending that data. However, unprocessed input data might still be
...@@ -482,6 +483,64 @@ pqDropConnection(PGconn *conn, bool flushInput) ...@@ -482,6 +483,64 @@ pqDropConnection(PGconn *conn, bool flushInput)
} }
/*
* pqDropServerData
*
* Clear all connection state data that was received from (or deduced about)
* the server. This is essential to do between connection attempts to
* different servers, else we may incorrectly hold over some data from the
* old server.
*
* It would be better to merge this into pqDropConnection, perhaps, but
* right now we cannot because that function is called immediately on
* detection of connection loss (cf. pqReadData, for instance). This data
* should be kept until we are actually starting a new connection.
*/
static void
pqDropServerData(PGconn *conn)
{
PGnotify *notify;
pgParameterStatus *pstatus;
/* Forget pending notifies */
notify = conn->notifyHead;
while (notify != NULL)
{
PGnotify *prev = notify;
notify = notify->next;
free(prev);
}
conn->notifyHead = conn->notifyTail = NULL;
/* Reset ParameterStatus data, as well as variables deduced from it */
pstatus = conn->pstatus;
while (pstatus != NULL)
{
pgParameterStatus *prev = pstatus;
pstatus = pstatus->next;
free(prev);
}
conn->pstatus = NULL;
conn->client_encoding = PG_SQL_ASCII;
conn->std_strings = false;
conn->sversion = 0;
/* Drop large-object lookup data */
if (conn->lobjfuncs)
free(conn->lobjfuncs);
conn->lobjfuncs = NULL;
/* Reset assorted other per-connection state */
conn->last_sqlstate[0] = '\0';
conn->auth_req_received = false;
conn->password_needed = false;
conn->be_pid = 0;
conn->be_key = 0;
}
/* /*
* Connecting to a Database * Connecting to a Database
* *
...@@ -1110,9 +1169,6 @@ connectOptions2(PGconn *conn) ...@@ -1110,9 +1169,6 @@ connectOptions2(PGconn *conn)
conn->dbName, conn->dbName,
conn->pguser, conn->pguser,
conn->pgpassfile); conn->pgpassfile);
/* If we got one, set pgpassfile_used */
if (conn->connhost[i].password != NULL)
conn->pgpassfile_used = true;
} }
} }
} }
...@@ -1700,6 +1756,14 @@ connectDBStart(PGconn *conn) ...@@ -1700,6 +1756,14 @@ connectDBStart(PGconn *conn)
conn->inStart = conn->inCursor = conn->inEnd = 0; conn->inStart = conn->inCursor = conn->inEnd = 0;
conn->outCount = 0; conn->outCount = 0;
/*
* Ensure errorMessage is empty, too. PQconnectPoll will append messages
* to it in the process of scanning for a working server. Thus, if we
* fail to connect to multiple hosts, the final error message will include
* details about each failure.
*/
resetPQExpBuffer(&conn->errorMessage);
/* /*
* Look up socket addresses for each possible host using * Look up socket addresses for each possible host using
* pg_getaddrinfo_all. * pg_getaddrinfo_all.
...@@ -1795,21 +1859,14 @@ connectDBStart(PGconn *conn) ...@@ -1795,21 +1859,14 @@ connectDBStart(PGconn *conn)
} }
} }
#ifdef USE_SSL
/* setup values based on SSL mode */
if (conn->sslmode[0] == 'd') /* "disable" */
conn->allow_ssl_try = false;
else if (conn->sslmode[0] == 'a') /* "allow" */
conn->wait_ssl_try = true;
#endif
/* /*
* Set up to try to connect, with protocol 3.0 as the first attempt. * Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
* anything else looks at it.)
*/ */
conn->whichhost = 0; conn->whichhost = -1;
conn->addr_cur = conn->connhost[0].addrlist; conn->try_next_addr = false;
conn->pversion = PG_PROTOCOL(3, 0); conn->try_next_host = true;
conn->send_appname = true;
conn->status = CONNECTION_NEEDED; conn->status = CONNECTION_NEEDED;
/* /*
...@@ -1823,6 +1880,12 @@ connectDBStart(PGconn *conn) ...@@ -1823,6 +1880,12 @@ connectDBStart(PGconn *conn)
return 1; return 1;
connect_errReturn: connect_errReturn:
/*
* If we managed to open a socket, close it immediately rather than
* waiting till PQfinish. (The application cannot have gotten the socket
* from PQsocket yet, so this doesn't risk breaking anything.)
*/
pqDropConnection(conn, true); pqDropConnection(conn, true);
conn->status = CONNECTION_BAD; conn->status = CONNECTION_BAD;
return 0; return 0;
...@@ -1887,6 +1950,7 @@ connectDBComplete(PGconn *conn) ...@@ -1887,6 +1950,7 @@ connectDBComplete(PGconn *conn)
ret = pqWaitTimed(1, 0, conn, finish_time); ret = pqWaitTimed(1, 0, conn, finish_time);
if (ret == -1) if (ret == -1)
{ {
/* hard failure, eg select() problem, aborts everything */
conn->status = CONNECTION_BAD; conn->status = CONNECTION_BAD;
return 0; return 0;
} }
...@@ -1896,6 +1960,7 @@ connectDBComplete(PGconn *conn) ...@@ -1896,6 +1960,7 @@ connectDBComplete(PGconn *conn)
ret = pqWaitTimed(0, 1, conn, finish_time); ret = pqWaitTimed(0, 1, conn, finish_time);
if (ret == -1) if (ret == -1)
{ {
/* hard failure, eg select() problem, aborts everything */
conn->status = CONNECTION_BAD; conn->status = CONNECTION_BAD;
return 0; return 0;
} }
...@@ -1910,24 +1975,17 @@ connectDBComplete(PGconn *conn) ...@@ -1910,24 +1975,17 @@ connectDBComplete(PGconn *conn)
if (ret == 1) /* connect_timeout elapsed */ if (ret == 1) /* connect_timeout elapsed */
{ {
/* /*
* If there are no more hosts, return (the error message is * Attempt connection to the next host, ignoring any remaining
* already set) * addresses for the current host.
*/ */
if (++conn->whichhost >= conn->nconnhost) conn->try_next_addr = false;
{ conn->try_next_host = true;
conn->whichhost = 0; conn->status = CONNECTION_NEEDED;
conn->status = CONNECTION_BAD;
return 0;
}
/* /*
* Attempt connection to the next host, starting the * Restart the connect_timeout timer for the new host.
* connect_timeout timer
*/ */
pqDropConnection(conn, true); if (timeout > 0)
conn->addr_cur = conn->connhost[conn->whichhost].addrlist;
conn->status = CONNECTION_NEEDED;
if (conn->connect_timeout != NULL)
finish_time = time(NULL) + timeout; finish_time = time(NULL) + timeout;
} }
...@@ -1940,27 +1998,29 @@ connectDBComplete(PGconn *conn) ...@@ -1940,27 +1998,29 @@ connectDBComplete(PGconn *conn)
/* /*
* This subroutine saves conn->errorMessage, which will be restored back by * This subroutine saves conn->errorMessage, which will be restored back by
* restoreErrorMessage subroutine. * restoreErrorMessage subroutine. Returns false on OOM failure.
*/ */
static bool static bool
saveErrorMessage(PGconn *conn, PQExpBuffer savedMessage) saveErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
{ {
initPQExpBuffer(savedMessage); initPQExpBuffer(savedMessage);
appendPQExpBufferStr(savedMessage,
conn->errorMessage.data);
if (PQExpBufferBroken(savedMessage)) if (PQExpBufferBroken(savedMessage))
{ {
printfPQExpBuffer(&conn->errorMessage, printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n")); libpq_gettext("out of memory\n"));
return false; return false;
} }
/* Clear whatever is in errorMessage now */
appendPQExpBufferStr(savedMessage,
conn->errorMessage.data);
resetPQExpBuffer(&conn->errorMessage); resetPQExpBuffer(&conn->errorMessage);
return true; return true;
} }
/* /*
* Restores saved error messages back to conn->errorMessage. * Restores saved error messages back to conn->errorMessage, prepending them
* to whatever is in conn->errorMessage already. (This does the right thing
* if anything's been added to conn->errorMessage since saveErrorMessage.)
*/ */
static void static void
restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage) restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
...@@ -1968,6 +2028,11 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage) ...@@ -1968,6 +2028,11 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
appendPQExpBufferStr(savedMessage, conn->errorMessage.data); appendPQExpBufferStr(savedMessage, conn->errorMessage.data);
resetPQExpBuffer(&conn->errorMessage); resetPQExpBuffer(&conn->errorMessage);
appendPQExpBufferStr(&conn->errorMessage, savedMessage->data); appendPQExpBufferStr(&conn->errorMessage, savedMessage->data);
/* If any step above hit OOM, just report that */
if (PQExpBufferBroken(savedMessage) ||
PQExpBufferBroken(&conn->errorMessage))
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
termPQExpBuffer(savedMessage); termPQExpBuffer(savedMessage);
} }
...@@ -2001,6 +2066,8 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage) ...@@ -2001,6 +2066,8 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
PostgresPollingStatusType PostgresPollingStatusType
PQconnectPoll(PGconn *conn) PQconnectPoll(PGconn *conn)
{ {
bool reset_connection_state_machine = false;
bool need_new_connection = false;
PGresult *res; PGresult *res;
char sebuf[256]; char sebuf[256];
int optval; int optval;
...@@ -2064,6 +2131,82 @@ PQconnectPoll(PGconn *conn) ...@@ -2064,6 +2131,82 @@ PQconnectPoll(PGconn *conn)
keep_going: /* We will come back to here until there is keep_going: /* We will come back to here until there is
* nothing left to do. */ * nothing left to do. */
/* Time to advance to next address, or next host if no more addresses? */
if (conn->try_next_addr)
{
if (conn->addr_cur && conn->addr_cur->ai_next)
{
conn->addr_cur = conn->addr_cur->ai_next;
reset_connection_state_machine = true;
}
else
conn->try_next_host = true;
conn->try_next_addr = false;
}
/* Time to advance to next connhost[] entry? */
if (conn->try_next_host)
{
if (conn->whichhost + 1 >= conn->nconnhost)
{
/*
* Oops, no more hosts. An appropriate error message is already
* set up, so just set the right status.
*/
goto error_return;
}
conn->whichhost++;
conn->addr_cur = conn->connhost[conn->whichhost].addrlist;
/* If no addresses for this host, just try the next one */
if (conn->addr_cur == NULL)
goto keep_going;
reset_connection_state_machine = true;
conn->try_next_host = false;
}
/* Reset connection state machine? */
if (reset_connection_state_machine)
{
/*
* (Re) initialize our connection control variables for a set of
* connection attempts to a single server address. These variables
* must persist across individual connection attempts, but we must
* reset them when we start to consider a new server.
*/
conn->pversion = PG_PROTOCOL(3, 0);
conn->send_appname = true;
#ifdef USE_SSL
/* initialize these values based on SSL mode */
conn->allow_ssl_try = (conn->sslmode[0] != 'd'); /* "disable" */
conn->wait_ssl_try = (conn->sslmode[0] == 'a'); /* "allow" */
#endif
reset_connection_state_machine = false;
need_new_connection = true;
}
/* Force a new connection (perhaps to the same server as before)? */
if (need_new_connection)
{
/* Drop any existing connection */
pqDropConnection(conn, true);
/* Reset all state obtained from old server */
pqDropServerData(conn);
/* Drop any PGresult we might have, too */
conn->asyncStatus = PGASYNC_IDLE;
conn->xactStatus = PQTRANS_IDLE;
pqClearAsyncResult(conn);
/* Reset conn->status to put the state machine in the right state */
conn->status = CONNECTION_NEEDED;
need_new_connection = false;
}
/* Now try to advance the state machine for this connection */
switch (conn->status) switch (conn->status)
{ {
case CONNECTION_NEEDED: case CONNECTION_NEEDED:
...@@ -2071,29 +2214,25 @@ keep_going: /* We will come back to here until there is ...@@ -2071,29 +2214,25 @@ keep_going: /* We will come back to here until there is
/* /*
* Try to initiate a connection to one of the addresses * Try to initiate a connection to one of the addresses
* returned by pg_getaddrinfo_all(). conn->addr_cur is the * returned by pg_getaddrinfo_all(). conn->addr_cur is the
* next one to try. We fail when we run out of addresses. * next one to try.
*
* The extra level of braces here is historical. It's not
* worth reindenting this whole switch case to remove 'em.
*/ */
for (;;)
{ {
struct addrinfo *addr_cur; struct addrinfo *addr_cur = conn->addr_cur;
/* /*
* Advance to next possible host, if we've tried all of * Advance to next possible host, if we've tried all of
* the addresses for the current host. * the addresses for the current host.
*/ */
if (conn->addr_cur == NULL) if (addr_cur == NULL)
{ {
if (++conn->whichhost >= conn->nconnhost) conn->try_next_host = true;
{ goto keep_going;
conn->whichhost = 0;
break;
}
conn->addr_cur =
conn->connhost[conn->whichhost].addrlist;
} }
/* Remember current address for possible error msg */ /* Remember current address for possible error msg */
addr_cur = conn->addr_cur;
memcpy(&conn->raddr.addr, addr_cur->ai_addr, memcpy(&conn->raddr.addr, addr_cur->ai_addr,
addr_cur->ai_addrlen); addr_cur->ai_addrlen);
conn->raddr.salen = addr_cur->ai_addrlen; conn->raddr.salen = addr_cur->ai_addrlen;
...@@ -2102,33 +2241,35 @@ keep_going: /* We will come back to here until there is ...@@ -2102,33 +2241,35 @@ keep_going: /* We will come back to here until there is
if (conn->sock == PGINVALID_SOCKET) if (conn->sock == PGINVALID_SOCKET)
{ {
/* /*
* ignore socket() failure if we have more addresses * Silently ignore socket() failure if we have more
* to try * addresses to try; this reduces useless chatter in
* cases where the address list includes both IPv4 and
* IPv6 but kernel only accepts one family.
*/ */
if (addr_cur->ai_next != NULL || if (addr_cur->ai_next != NULL ||
conn->whichhost + 1 < conn->nconnhost) conn->whichhost + 1 < conn->nconnhost)
{ {
conn->addr_cur = addr_cur->ai_next; conn->try_next_addr = true;
continue; goto keep_going;
} }
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not create socket: %s\n"), libpq_gettext("could not create socket: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
break; goto error_return;
} }
/* /*
* Select socket options: no delay of outgoing data for * Select socket options: no delay of outgoing data for
* TCP sockets, nonblock mode, close-on-exec. Fail if any * TCP sockets, nonblock mode, close-on-exec. Try the
* of this fails. * next address if any of this fails.
*/ */
if (!IS_AF_UNIX(addr_cur->ai_family)) if (!IS_AF_UNIX(addr_cur->ai_family))
{ {
if (!connectNoDelay(conn)) if (!connectNoDelay(conn))
{ {
pqDropConnection(conn, true); /* error message already created */
conn->addr_cur = addr_cur->ai_next; conn->try_next_addr = true;
continue; goto keep_going;
} }
} }
if (!pg_set_noblock(conn->sock)) if (!pg_set_noblock(conn->sock))
...@@ -2136,9 +2277,8 @@ keep_going: /* We will come back to here until there is ...@@ -2136,9 +2277,8 @@ keep_going: /* We will come back to here until there is
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not set socket to nonblocking mode: %s\n"), libpq_gettext("could not set socket to nonblocking mode: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
pqDropConnection(conn, true); conn->try_next_addr = true;
conn->addr_cur = addr_cur->ai_next; goto keep_going;
continue;
} }
#ifdef F_SETFD #ifdef F_SETFD
...@@ -2147,9 +2287,8 @@ keep_going: /* We will come back to here until there is ...@@ -2147,9 +2287,8 @@ keep_going: /* We will come back to here until there is
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not set socket to close-on-exec mode: %s\n"), libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
pqDropConnection(conn, true); conn->try_next_addr = true;
conn->addr_cur = addr_cur->ai_next; goto keep_going;
continue;
} }
#endif /* F_SETFD */ #endif /* F_SETFD */
...@@ -2195,9 +2334,8 @@ keep_going: /* We will come back to here until there is ...@@ -2195,9 +2334,8 @@ keep_going: /* We will come back to here until there is
if (err) if (err)
{ {
pqDropConnection(conn, true); conn->try_next_addr = true;
conn->addr_cur = addr_cur->ai_next; goto keep_going;
continue;
} }
} }
...@@ -2276,25 +2414,13 @@ keep_going: /* We will come back to here until there is ...@@ -2276,25 +2414,13 @@ keep_going: /* We will come back to here until there is
} }
/* /*
* This connection failed --- set up error report, then * This connection failed. Add the error report to
* close socket (do it this way in case close() affects * conn->errorMessage, then try the next address if any.
* the value of errno...). We will ignore the connect()
* failure and keep going if there are more addresses.
*/ */
connectFailureMessage(conn, SOCK_ERRNO); connectFailureMessage(conn, SOCK_ERRNO);
pqDropConnection(conn, true); conn->try_next_addr = true;
goto keep_going;
/* }
* Try the next address, if any.
*/
conn->addr_cur = addr_cur->ai_next;
} /* loop over addresses */
/*
* Oops, no more addresses. An appropriate error message is
* already set up, so just set the right status.
*/
goto error_return;
} }
case CONNECTION_STARTED: case CONNECTION_STARTED:
...@@ -2327,20 +2453,13 @@ keep_going: /* We will come back to here until there is ...@@ -2327,20 +2453,13 @@ keep_going: /* We will come back to here until there is
* error message. * error message.
*/ */
connectFailureMessage(conn, optval); connectFailureMessage(conn, optval);
pqDropConnection(conn, true);
/* /*
* If more addresses remain, keep trying, just as in the * Try the next address if any, just as in the case where
* case where connect() returned failure immediately. * connect() returned failure immediately.
*/ */
if (conn->addr_cur->ai_next != NULL || conn->try_next_addr = true;
conn->whichhost + 1 < conn->nconnhost) goto keep_going;
{
conn->addr_cur = conn->addr_cur->ai_next;
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
goto error_return;
} }
/* Fill in the client address */ /* Fill in the client address */
...@@ -2615,12 +2734,13 @@ keep_going: /* We will come back to here until there is ...@@ -2615,12 +2734,13 @@ keep_going: /* We will come back to here until there is
{ {
/* only retry once */ /* only retry once */
conn->allow_ssl_try = false; conn->allow_ssl_try = false;
/* Must drop the old connection */ need_new_connection = true;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going; goto keep_going;
} }
/* Else it's a hard failure */
goto error_return;
} }
/* Else, return POLLING_READING or POLLING_WRITING status */
return pollres; return pollres;
#else /* !USE_SSL */ #else /* !USE_SSL */
/* can't get here */ /* can't get here */
...@@ -2727,9 +2847,7 @@ keep_going: /* We will come back to here until there is ...@@ -2727,9 +2847,7 @@ keep_going: /* We will come back to here until there is
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
{ {
conn->pversion = PG_PROTOCOL(2, 0); conn->pversion = PG_PROTOCOL(2, 0);
/* Must drop the old connection */ need_new_connection = true;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going; goto keep_going;
} }
...@@ -2780,6 +2898,9 @@ keep_going: /* We will come back to here until there is ...@@ -2780,6 +2898,9 @@ keep_going: /* We will come back to here until there is
/* OK, we read the message; mark data consumed */ /* OK, we read the message; mark data consumed */
conn->inStart = conn->inCursor; conn->inStart = conn->inCursor;
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
#ifdef USE_SSL #ifdef USE_SSL
/* /*
...@@ -2793,9 +2914,7 @@ keep_going: /* We will come back to here until there is ...@@ -2793,9 +2914,7 @@ keep_going: /* We will come back to here until there is
{ {
/* only retry once */ /* only retry once */
conn->wait_ssl_try = false; conn->wait_ssl_try = false;
/* Must drop the old connection */ need_new_connection = true;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going; goto keep_going;
} }
...@@ -2804,14 +2923,13 @@ keep_going: /* We will come back to here until there is ...@@ -2804,14 +2923,13 @@ keep_going: /* We will come back to here until there is
* then do a non-SSL retry * then do a non-SSL retry
*/ */
if (conn->sslmode[0] == 'p' /* "prefer" */ if (conn->sslmode[0] == 'p' /* "prefer" */
&& conn->allow_ssl_try && conn->ssl_in_use
&& conn->allow_ssl_try /* redundant? */
&& !conn->wait_ssl_try) /* redundant? */ && !conn->wait_ssl_try) /* redundant? */
{ {
/* only retry once */ /* only retry once */
conn->allow_ssl_try = false; conn->allow_ssl_try = false;
/* Must drop the old connection */ need_new_connection = true;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going; goto keep_going;
} }
#endif #endif
...@@ -2947,9 +3065,7 @@ keep_going: /* We will come back to here until there is ...@@ -2947,9 +3065,7 @@ keep_going: /* We will come back to here until there is
{ {
PQclear(res); PQclear(res);
conn->send_appname = false; conn->send_appname = false;
/* Must drop the old connection */ need_new_connection = true;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going; goto keep_going;
} }
} }
...@@ -2978,17 +3094,21 @@ keep_going: /* We will come back to here until there is ...@@ -2978,17 +3094,21 @@ keep_going: /* We will come back to here until there is
/* /*
* If a read-write connection is required, see if we have one. * If a read-write connection is required, see if we have one.
*
* Servers before 7.4 lack the transaction_read_only GUC, but
* by the same token they don't have any read-only mode, so we
* may just skip the test in that case.
*/ */
if (conn->target_session_attrs != NULL && if (conn->sversion >= 70400 &&
conn->target_session_attrs != NULL &&
strcmp(conn->target_session_attrs, "read-write") == 0) strcmp(conn->target_session_attrs, "read-write") == 0)
{ {
/* /*
* We are yet to make a connection. Save all existing * Save existing error messages across the PQsendQuery
* error messages until we make a successful connection * attempt. This is necessary because PQsendQuery is
* state. This is important because PQsendQuery is going * going to reset conn->errorMessage, so we would lose
* to reset conn->errorMessage and we will lose error * error messages related to previous hosts we have tried
* messages related to previous hosts we have tried to * and failed to connect to.
* connect and failed.
*/ */
if (!saveErrorMessage(conn, &savedMessage)) if (!saveErrorMessage(conn, &savedMessage))
goto error_return; goto error_return;
...@@ -3041,9 +3161,16 @@ keep_going: /* We will come back to here until there is ...@@ -3041,9 +3161,16 @@ keep_going: /* We will come back to here until there is
} }
/* /*
* If a read-write connection is requested check for same. * If a read-write connection is required, see if we have one.
* (This should match the stanza in the CONNECTION_AUTH_OK case
* above.)
*
* Servers before 7.4 lack the transaction_read_only GUC, but by
* the same token they don't have any read-only mode, so we may
* just skip the test in that case.
*/ */
if (conn->target_session_attrs != NULL && if (conn->sversion >= 70400 &&
conn->target_session_attrs != NULL &&
strcmp(conn->target_session_attrs, "read-write") == 0) strcmp(conn->target_session_attrs, "read-write") == 0)
{ {
if (!saveErrorMessage(conn, &savedMessage)) if (!saveErrorMessage(conn, &savedMessage))
...@@ -3077,7 +3204,6 @@ keep_going: /* We will come back to here until there is ...@@ -3077,7 +3204,6 @@ keep_going: /* We will come back to here until there is
if (PQisBusy(conn)) if (PQisBusy(conn))
{ {
conn->status = CONNECTION_CONSUME; conn->status = CONNECTION_CONSUME;
restoreErrorMessage(conn, &savedMessage);
return PGRES_POLLING_READING; return PGRES_POLLING_READING;
} }
...@@ -3127,9 +3253,14 @@ keep_going: /* We will come back to here until there is ...@@ -3127,9 +3253,14 @@ keep_going: /* We will come back to here until there is
val = PQgetvalue(res, 0, 0); val = PQgetvalue(res, 0, 0);
if (strncmp(val, "on", 2) == 0) if (strncmp(val, "on", 2) == 0)
{ {
/* Not writable; fail this connection. */
const char *displayed_host; const char *displayed_host;
const char *displayed_port; const char *displayed_port;
PQclear(res);
restoreErrorMessage(conn, &savedMessage);
/* Append error report to conn->errorMessage. */
if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS) if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
displayed_host = conn->connhost[conn->whichhost].hostaddr; displayed_host = conn->connhost[conn->whichhost].hostaddr;
else else
...@@ -3138,30 +3269,25 @@ keep_going: /* We will come back to here until there is ...@@ -3138,30 +3269,25 @@ keep_going: /* We will come back to here until there is
if (displayed_port == NULL || displayed_port[0] == '\0') if (displayed_port == NULL || displayed_port[0] == '\0')
displayed_port = DEF_PGPORT_STR; displayed_port = DEF_PGPORT_STR;
PQclear(res);
restoreErrorMessage(conn, &savedMessage);
/* Not writable; close connection. */
appendPQExpBuffer(&conn->errorMessage, appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not make a writable " libpq_gettext("could not make a writable "
"connection to server " "connection to server "
"\"%s:%s\"\n"), "\"%s:%s\"\n"),
displayed_host, displayed_port); displayed_host, displayed_port);
/* Close connection politely. */
conn->status = CONNECTION_OK; conn->status = CONNECTION_OK;
sendTerminateConn(conn); sendTerminateConn(conn);
pqDropConnection(conn, true);
/* Skip any remaining addresses for this host. */
conn->addr_cur = NULL;
if (conn->whichhost + 1 < conn->nconnhost)
{
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
/* No more addresses to try. So we fail. */ /*
goto error_return; * Try next host if any, but we don't want to consider
* additional addresses for this host.
*/
conn->try_next_host = true;
goto keep_going;
} }
/* Session is read-write, so we're good. */
PQclear(res); PQclear(res);
termPQExpBuffer(&savedMessage); termPQExpBuffer(&savedMessage);
...@@ -3184,6 +3310,7 @@ keep_going: /* We will come back to here until there is ...@@ -3184,6 +3310,7 @@ keep_going: /* We will come back to here until there is
PQclear(res); PQclear(res);
restoreErrorMessage(conn, &savedMessage); restoreErrorMessage(conn, &savedMessage);
/* Append error report to conn->errorMessage. */
if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS) if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
displayed_host = conn->connhost[conn->whichhost].hostaddr; displayed_host = conn->connhost[conn->whichhost].hostaddr;
else else
...@@ -3195,20 +3322,14 @@ keep_going: /* We will come back to here until there is ...@@ -3195,20 +3322,14 @@ keep_going: /* We will come back to here until there is
libpq_gettext("test \"SHOW transaction_read_only\" failed " libpq_gettext("test \"SHOW transaction_read_only\" failed "
"on server \"%s:%s\"\n"), "on server \"%s:%s\"\n"),
displayed_host, displayed_port); displayed_host, displayed_port);
/* Close connection politely. */
conn->status = CONNECTION_OK; conn->status = CONNECTION_OK;
sendTerminateConn(conn); sendTerminateConn(conn);
pqDropConnection(conn, true);
if (conn->addr_cur->ai_next != NULL ||
conn->whichhost + 1 < conn->nconnhost)
{
conn->addr_cur = conn->addr_cur->ai_next;
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
/* No more addresses to try. So we fail. */ /* Try next address */
goto error_return; conn->try_next_addr = true;
goto keep_going;
} }
default: default:
...@@ -3223,8 +3344,6 @@ keep_going: /* We will come back to here until there is ...@@ -3223,8 +3344,6 @@ keep_going: /* We will come back to here until there is
error_return: error_return:
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
* for those above us if they wish to remove this socket from their own * for those above us if they wish to remove this socket from their own
...@@ -3352,14 +3471,6 @@ makeEmptyPGconn(void) ...@@ -3352,14 +3471,6 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT; conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS; conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET; conn->sock = PGINVALID_SOCKET;
conn->auth_req_received = false;
conn->password_needed = false;
conn->pgpassfile_used = false;
#ifdef USE_SSL
conn->allow_ssl_try = true;
conn->wait_ssl_try = false;
conn->ssl_in_use = false;
#endif
/* /*
* We try to send at least 8K at a time, which is the usual size of pipe * We try to send at least 8K at a time, which is the usual size of pipe
...@@ -3581,9 +3692,9 @@ sendTerminateConn(PGconn *conn) ...@@ -3581,9 +3692,9 @@ sendTerminateConn(PGconn *conn)
static void static void
closePGconn(PGconn *conn) closePGconn(PGconn *conn)
{ {
PGnotify *notify; /*
pgParameterStatus *pstatus; * If possible, send Terminate message to close the connection politely.
*/
sendTerminateConn(conn); sendTerminateConn(conn);
/* /*
...@@ -3600,31 +3711,13 @@ closePGconn(PGconn *conn) ...@@ -3600,31 +3711,13 @@ closePGconn(PGconn *conn)
pqDropConnection(conn, true); pqDropConnection(conn, true);
conn->status = CONNECTION_BAD; /* Well, not really _bad_ - just absent */ conn->status = CONNECTION_BAD; /* Well, not really _bad_ - just absent */
conn->asyncStatus = PGASYNC_IDLE; conn->asyncStatus = PGASYNC_IDLE;
conn->xactStatus = PQTRANS_IDLE;
pqClearAsyncResult(conn); /* deallocate result */ pqClearAsyncResult(conn); /* deallocate result */
resetPQExpBuffer(&conn->errorMessage); resetPQExpBuffer(&conn->errorMessage);
release_all_addrinfo(conn); release_all_addrinfo(conn);
notify = conn->notifyHead; /* Reset all state obtained from server, too */
while (notify != NULL) pqDropServerData(conn);
{
PGnotify *prev = notify;
notify = notify->next;
free(prev);
}
conn->notifyHead = conn->notifyTail = NULL;
pstatus = conn->pstatus;
while (pstatus != NULL)
{
pgParameterStatus *prev = pstatus;
pstatus = pstatus->next;
free(prev);
}
conn->pstatus = NULL;
if (conn->lobjfuncs)
free(conn->lobjfuncs);
conn->lobjfuncs = NULL;
} }
/* /*
...@@ -6505,7 +6598,9 @@ pgpassfileWarning(PGconn *conn) ...@@ -6505,7 +6598,9 @@ pgpassfileWarning(PGconn *conn)
{ {
/* If it was 'invalid authorization', add pgpassfile mention */ /* If it was 'invalid authorization', add pgpassfile mention */
/* only works with >= 9.0 servers */ /* only works with >= 9.0 servers */
if (conn->pgpassfile_used && conn->password_needed && conn->result) if (conn->password_needed &&
conn->connhost[conn->whichhost].password != NULL &&
conn->result)
{ {
const char *sqlstate = PQresultErrorField(conn->result, const char *sqlstate = PQresultErrorField(conn->result,
PG_DIAG_SQLSTATE); PG_DIAG_SQLSTATE);
......
...@@ -290,6 +290,7 @@ typedef struct pgDataValue ...@@ -290,6 +290,7 @@ typedef struct pgDataValue
const char *value; /* data value, without zero-termination */ const char *value; /* data value, without zero-termination */
} PGdataValue; } PGdataValue;
/* Host address type enum for struct pg_conn_host */
typedef enum pg_conn_host_type typedef enum pg_conn_host_type
{ {
CHT_HOST_NAME, CHT_HOST_NAME,
...@@ -298,20 +299,19 @@ typedef enum pg_conn_host_type ...@@ -298,20 +299,19 @@ typedef enum pg_conn_host_type
} pg_conn_host_type; } pg_conn_host_type;
/* /*
* pg_conn_host stores all information about one of possibly several hosts * pg_conn_host stores all information about each of possibly several hosts
* mentioned in the connection string. Derived by splitting the pghost * mentioned in the connection string. Most fields are derived by splitting
* on the comma character and then parsing each segment. * the relevant connection parameter (e.g., pghost) at commas.
*/ */
typedef struct pg_conn_host typedef struct pg_conn_host
{ {
pg_conn_host_type type; /* type of host */ pg_conn_host_type type; /* type of host address */
char *host; /* host name or socket path */ char *host; /* host name or socket path */
char *hostaddr; /* host address */ char *hostaddr; /* host numeric IP address */
char *port; /* port number for this host; if not NULL, char *port; /* port number (always provided) */
* overrides the PGConn's pgport */
char *password; /* password for this host, read from the char *password; /* password for this host, read from the
* password file. only set if the PGconn's * password file; NULL if not sought or not
* pgpass field is NULL. */ * found in password file. */
struct addrinfo *addrlist; /* list of possible backend addresses */ struct addrinfo *addrlist; /* list of possible backend addresses */
} pg_conn_host; } pg_conn_host;
...@@ -325,12 +325,13 @@ struct pg_conn ...@@ -325,12 +325,13 @@ struct pg_conn
char *pghost; /* the machine on which the server is running, char *pghost; /* the machine on which the server is running,
* or a path to a UNIX-domain socket, or a * or a path to a UNIX-domain socket, or a
* comma-separated list of machines and/or * comma-separated list of machines and/or
* paths, optionally with port suffixes; if * paths; if NULL, use DEFAULT_PGSOCKET_DIR */
* NULL, use DEFAULT_PGSOCKET_DIR */
char *pghostaddr; /* the numeric IP address of the machine on char *pghostaddr; /* the numeric IP address of the machine on
* which the server is running. Takes * which the server is running, or a
* precedence over above. */ * comma-separated list of same. Takes
char *pgport; /* the server's communication port number */ * precedence over pghost. */
char *pgport; /* the server's communication port number, or
* a comma-separated list of ports */
char *pgtty; /* tty on which the backend messages is char *pgtty; /* tty on which the backend messages is
* displayed (OBSOLETE, NOT USED) */ * displayed (OBSOLETE, NOT USED) */
char *connect_timeout; /* connection timeout (numeric string) */ char *connect_timeout; /* connection timeout (numeric string) */
...@@ -392,9 +393,9 @@ struct pg_conn ...@@ -392,9 +393,9 @@ struct pg_conn
PGnotify *notifyTail; /* newest unreported Notify msg */ PGnotify *notifyTail; /* newest unreported Notify msg */
/* Support for multiple hosts in connection string */ /* Support for multiple hosts in connection string */
int nconnhost; /* # of possible hosts */ int nconnhost; /* # of hosts named in conn string */
int whichhost; /* host we're currently considering */ int whichhost; /* host we're currently trying/connected to */
pg_conn_host *connhost; /* details about each possible host */ pg_conn_host *connhost; /* details about each named host */
/* Connection data */ /* Connection data */
pgsocket sock; /* FD for socket, PGINVALID_SOCKET if pgsocket sock; /* FD for socket, PGINVALID_SOCKET if
...@@ -405,11 +406,12 @@ struct pg_conn ...@@ -405,11 +406,12 @@ struct pg_conn
int sversion; /* server version, e.g. 70401 for 7.4.1 */ int sversion; /* server version, e.g. 70401 for 7.4.1 */
bool auth_req_received; /* true if any type of auth req received */ bool auth_req_received; /* true if any type of auth req received */
bool password_needed; /* true if server demanded a password */ bool password_needed; /* true if server demanded a password */
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? */
/* Transient state needed while establishing connection */ /* Transient state needed while establishing connection */
bool try_next_addr; /* time to advance to next address/host? */
bool try_next_host; /* time to advance to next connhost[]? */
struct addrinfo *addr_cur; /* backend address currently being tried */ struct addrinfo *addr_cur; /* backend address currently being tried */
PGSetenvStatusType setenv_state; /* for 2.0 protocol only */ PGSetenvStatusType setenv_state; /* for 2.0 protocol only */
const PQEnvironmentOption *next_eo; const PQEnvironmentOption *next_eo;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment