]> granicus.if.org Git - postgresql/commitdiff
Fix failure to reset libpq's state fully between connection attempts.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Aug 2018 14:53:35 +0000 (10:53 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 6 Aug 2018 14:53:35 +0000 (10:53 -0400)
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

src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/libpq-int.h

index 221f1ecdaf8934820ec8f75d099b8ae0d868b33d..986f74ff006ea2c6d095f49a66af6290584ff3f0 100644 (file)
@@ -415,7 +415,8 @@ pgthreadlock_t pg_g_threadlock = default_threadlock;
  *
  * Close any physical connection to the server, and reset associated
  * 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
  * of sending that data.  However, unprocessed input data might still be
@@ -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
  *
@@ -1110,9 +1169,6 @@ connectOptions2(PGconn *conn)
                                                                         conn->dbName,
                                                                         conn->pguser,
                                                                         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)
        conn->inStart = conn->inCursor = conn->inEnd = 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
         * pg_getaddrinfo_all.
@@ -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->addr_cur = conn->connhost[0].addrlist;
-       conn->pversion = PG_PROTOCOL(3, 0);
-       conn->send_appname = true;
+       conn->whichhost = -1;
+       conn->try_next_addr = false;
+       conn->try_next_host = true;
        conn->status = CONNECTION_NEEDED;
 
        /*
@@ -1823,6 +1880,12 @@ connectDBStart(PGconn *conn)
                return 1;
 
 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);
        conn->status = CONNECTION_BAD;
        return 0;
@@ -1887,6 +1950,7 @@ connectDBComplete(PGconn *conn)
                                ret = pqWaitTimed(1, 0, conn, finish_time);
                                if (ret == -1)
                                {
+                                       /* hard failure, eg select() problem, aborts everything */
                                        conn->status = CONNECTION_BAD;
                                        return 0;
                                }
@@ -1896,6 +1960,7 @@ connectDBComplete(PGconn *conn)
                                ret = pqWaitTimed(0, 1, conn, finish_time);
                                if (ret == -1)
                                {
+                                       /* hard failure, eg select() problem, aborts everything */
                                        conn->status = CONNECTION_BAD;
                                        return 0;
                                }
@@ -1910,24 +1975,17 @@ connectDBComplete(PGconn *conn)
                if (ret == 1)                   /* connect_timeout elapsed */
                {
                        /*
-                        * If there are no more hosts, return (the error message is
-                        * already set)
+                        * Attempt connection to the next host, ignoring any remaining
+                        * addresses for the current host.
                         */
-                       if (++conn->whichhost >= conn->nconnhost)
-                       {
-                               conn->whichhost = 0;
-                               conn->status = CONNECTION_BAD;
-                               return 0;
-                       }
+                       conn->try_next_addr = false;
+                       conn->try_next_host = true;
+                       conn->status = CONNECTION_NEEDED;
 
                        /*
-                        * Attempt connection to the next host, starting the
-                        * connect_timeout timer
+                        * Restart the connect_timeout timer for the new host.
                         */
-                       pqDropConnection(conn, true);
-                       conn->addr_cur = conn->connhost[conn->whichhost].addrlist;
-                       conn->status = CONNECTION_NEEDED;
-                       if (conn->connect_timeout != NULL)
+                       if (timeout > 0)
                                finish_time = time(NULL) + timeout;
                }
 
@@ -1940,27 +1998,29 @@ connectDBComplete(PGconn *conn)
 
 /*
  * This subroutine saves conn->errorMessage, which will be restored back by
- * restoreErrorMessage subroutine.
+ * restoreErrorMessage subroutine.  Returns false on OOM failure.
  */
 static bool
 saveErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
 {
        initPQExpBuffer(savedMessage);
+       appendPQExpBufferStr(savedMessage,
+                                                conn->errorMessage.data);
        if (PQExpBufferBroken(savedMessage))
        {
                printfPQExpBuffer(&conn->errorMessage,
                                                  libpq_gettext("out of memory\n"));
                return false;
        }
-
-       appendPQExpBufferStr(savedMessage,
-                                                conn->errorMessage.data);
+       /* Clear whatever is in errorMessage now */
        resetPQExpBuffer(&conn->errorMessage);
        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
 restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
@@ -1968,6 +2028,11 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
        appendPQExpBufferStr(savedMessage, conn->errorMessage.data);
        resetPQExpBuffer(&conn->errorMessage);
        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);
 }
 
@@ -2001,6 +2066,8 @@ restoreErrorMessage(PGconn *conn, PQExpBuffer savedMessage)
 PostgresPollingStatusType
 PQconnectPoll(PGconn *conn)
 {
+       bool            reset_connection_state_machine = false;
+       bool            need_new_connection = false;
        PGresult   *res;
        char            sebuf[256];
        int                     optval;
@@ -2064,6 +2131,82 @@ PQconnectPoll(PGconn *conn)
 
 keep_going:                                            /* We will come back to here until there is
                                                                 * 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)
        {
                case CONNECTION_NEEDED:
@@ -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
                                 * 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
                                         * the addresses for the current host.
                                         */
-                                       if (conn->addr_cur == NULL)
+                                       if (addr_cur == NULL)
                                        {
-                                               if (++conn->whichhost >= conn->nconnhost)
-                                               {
-                                                       conn->whichhost = 0;
-                                                       break;
-                                               }
-                                               conn->addr_cur =
-                                                       conn->connhost[conn->whichhost].addrlist;
+                                               conn->try_next_host = true;
+                                               goto keep_going;
                                        }
 
                                        /* Remember current address for possible error msg */
-                                       addr_cur = conn->addr_cur;
                                        memcpy(&conn->raddr.addr, addr_cur->ai_addr,
                                                   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
                                        if (conn->sock == PGINVALID_SOCKET)
                                        {
                                                /*
-                                                * ignore socket() failure if we have more addresses
-                                                * to try
+                                                * Silently ignore socket() failure if we have more
+                                                * 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 ||
                                                        conn->whichhost + 1 < conn->nconnhost)
                                                {
-                                                       conn->addr_cur = addr_cur->ai_next;
-                                                       continue;
+                                                       conn->try_next_addr = true;
+                                                       goto keep_going;
                                                }
                                                appendPQExpBuffer(&conn->errorMessage,
                                                                                  libpq_gettext("could not create socket: %s\n"),
                                                                                  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
-                                               break;
+                                               goto error_return;
                                        }
 
                                        /*
                                         * Select socket options: no delay of outgoing data for
-                                        * TCP sockets, nonblock mode, close-on-exec. Fail if any
-                                        * of this fails.
+                                        * TCP sockets, nonblock mode, close-on-exec.  Try the
+                                        * next address if any of this fails.
                                         */
                                        if (!IS_AF_UNIX(addr_cur->ai_family))
                                        {
                                                if (!connectNoDelay(conn))
                                                {
-                                                       pqDropConnection(conn, true);
-                                                       conn->addr_cur = addr_cur->ai_next;
-                                                       continue;
+                                                       /* error message already created */
+                                                       conn->try_next_addr = true;
+                                                       goto keep_going;
                                                }
                                        }
                                        if (!pg_set_noblock(conn->sock))
@@ -2136,9 +2277,8 @@ keep_going:                                               /* We will come back to here until there is
                                                appendPQExpBuffer(&conn->errorMessage,
                                                                                  libpq_gettext("could not set socket to nonblocking mode: %s\n"),
                                                                                  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
-                                               pqDropConnection(conn, true);
-                                               conn->addr_cur = addr_cur->ai_next;
-                                               continue;
+                                               conn->try_next_addr = true;
+                                               goto keep_going;
                                        }
 
 #ifdef F_SETFD
@@ -2147,9 +2287,8 @@ keep_going:                                               /* We will come back to here until there is
                                                appendPQExpBuffer(&conn->errorMessage,
                                                                                  libpq_gettext("could not set socket to close-on-exec mode: %s\n"),
                                                                                  SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
-                                               pqDropConnection(conn, true);
-                                               conn->addr_cur = addr_cur->ai_next;
-                                               continue;
+                                               conn->try_next_addr = true;
+                                               goto keep_going;
                                        }
 #endif                                                 /* F_SETFD */
 
@@ -2195,9 +2334,8 @@ keep_going:                                               /* We will come back to here until there is
 
                                                if (err)
                                                {
-                                                       pqDropConnection(conn, true);
-                                                       conn->addr_cur = addr_cur->ai_next;
-                                                       continue;
+                                                       conn->try_next_addr = true;
+                                                       goto keep_going;
                                                }
                                        }
 
@@ -2276,25 +2414,13 @@ keep_going:                                             /* We will come back to here until there is
                                        }
 
                                        /*
-                                        * This connection failed --- set up error report, then
-                                        * close socket (do it this way in case close() affects
-                                        * the value of errno...).  We will ignore the connect()
-                                        * failure and keep going if there are more addresses.
+                                        * This connection failed.  Add the error report to
+                                        * conn->errorMessage, then try the next address if any.
                                         */
                                        connectFailureMessage(conn, SOCK_ERRNO);
-                                       pqDropConnection(conn, true);
-
-                                       /*
-                                        * 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;
+                                       conn->try_next_addr = true;
+                                       goto keep_going;
+                               }
                        }
 
                case CONNECTION_STARTED:
@@ -2327,20 +2453,13 @@ keep_going:                                             /* We will come back to here until there is
                                         * error message.
                                         */
                                        connectFailureMessage(conn, optval);
-                                       pqDropConnection(conn, true);
 
                                        /*
-                                        * If more addresses remain, keep trying, just as in the
-                                        * case where connect() returned failure immediately.
+                                        * Try the next address if any, just as in the case where
+                                        * connect() returned failure immediately.
                                         */
-                                       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;
-                                       }
-                                       goto error_return;
+                                       conn->try_next_addr = true;
+                                       goto keep_going;
                                }
 
                                /* Fill in the client address */
@@ -2615,12 +2734,13 @@ keep_going:                                             /* We will come back to here until there is
                                        {
                                                /* only retry once */
                                                conn->allow_ssl_try = false;
-                                               /* Must drop the old connection */
-                                               pqDropConnection(conn, true);
-                                               conn->status = CONNECTION_NEEDED;
+                                               need_new_connection = true;
                                                goto keep_going;
                                        }
+                                       /* Else it's a hard failure */
+                                       goto error_return;
                                }
+                               /* Else, return POLLING_READING or POLLING_WRITING status */
                                return pollres;
 #else                                                  /* !USE_SSL */
                                /* can't get here */
@@ -2727,9 +2847,7 @@ keep_going:                                               /* We will come back to here until there is
                                        if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
                                        {
                                                conn->pversion = PG_PROTOCOL(2, 0);
-                                               /* Must drop the old connection */
-                                               pqDropConnection(conn, true);
-                                               conn->status = CONNECTION_NEEDED;
+                                               need_new_connection = true;
                                                goto keep_going;
                                        }
 
@@ -2780,6 +2898,9 @@ keep_going:                                               /* We will come back to here until there is
                                        /* OK, we read the message; mark data consumed */
                                        conn->inStart = conn->inCursor;
 
+                                       /* Check to see if we should mention pgpassfile */
+                                       pgpassfileWarning(conn);
+
 #ifdef USE_SSL
 
                                        /*
@@ -2793,9 +2914,7 @@ keep_going:                                               /* We will come back to here until there is
                                        {
                                                /* only retry once */
                                                conn->wait_ssl_try = false;
-                                               /* Must drop the old connection */
-                                               pqDropConnection(conn, true);
-                                               conn->status = CONNECTION_NEEDED;
+                                               need_new_connection = true;
                                                goto keep_going;
                                        }
 
@@ -2804,14 +2923,13 @@ keep_going:                                             /* We will come back to here until there is
                                         * then do a non-SSL retry
                                         */
                                        if (conn->sslmode[0] == 'p' /* "prefer" */
-                                               && conn->allow_ssl_try
+                                               && conn->ssl_in_use
+                                               && conn->allow_ssl_try  /* redundant? */
                                                && !conn->wait_ssl_try) /* redundant? */
                                        {
                                                /* only retry once */
                                                conn->allow_ssl_try = false;
-                                               /* Must drop the old connection */
-                                               pqDropConnection(conn, true);
-                                               conn->status = CONNECTION_NEEDED;
+                                               need_new_connection = true;
                                                goto keep_going;
                                        }
 #endif
@@ -2947,9 +3065,7 @@ keep_going:                                               /* We will come back to here until there is
                                                {
                                                        PQclear(res);
                                                        conn->send_appname = false;
-                                                       /* Must drop the old connection */
-                                                       pqDropConnection(conn, true);
-                                                       conn->status = CONNECTION_NEEDED;
+                                                       need_new_connection = true;
                                                        goto keep_going;
                                                }
                                        }
@@ -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.
+                                *
+                                * 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)
                                {
                                        /*
-                                        * We are yet to make a connection. Save all existing
-                                        * error messages until we make a successful connection
-                                        * state. This is important because PQsendQuery is going
-                                        * to reset conn->errorMessage and we will lose error
-                                        * messages related to previous hosts we have tried to
-                                        * connect and failed.
+                                        * Save existing error messages across the PQsendQuery
+                                        * attempt.  This is necessary because PQsendQuery is
+                                        * going to reset conn->errorMessage, so we would lose
+                                        * error messages related to previous hosts we have tried
+                                        * and failed to connect to.
                                         */
                                        if (!saveErrorMessage(conn, &savedMessage))
                                                goto error_return;
@@ -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)
                        {
                                if (!saveErrorMessage(conn, &savedMessage))
@@ -3077,7 +3204,6 @@ keep_going:                                               /* We will come back to here until there is
                                if (PQisBusy(conn))
                                {
                                        conn->status = CONNECTION_CONSUME;
-                                       restoreErrorMessage(conn, &savedMessage);
                                        return PGRES_POLLING_READING;
                                }
 
@@ -3127,9 +3253,14 @@ keep_going:                                              /* We will come back to here until there is
                                        val = PQgetvalue(res, 0, 0);
                                        if (strncmp(val, "on", 2) == 0)
                                        {
+                                               /* Not writable; fail this connection. */
                                                const char *displayed_host;
                                                const char *displayed_port;
 
+                                               PQclear(res);
+                                               restoreErrorMessage(conn, &savedMessage);
+
+                                               /* Append error report to conn->errorMessage. */
                                                if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
                                                        displayed_host = conn->connhost[conn->whichhost].hostaddr;
                                                else
@@ -3138,30 +3269,25 @@ keep_going:                                             /* We will come back to here until there is
                                                if (displayed_port == NULL || displayed_port[0] == '\0')
                                                        displayed_port = DEF_PGPORT_STR;
 
-                                               PQclear(res);
-                                               restoreErrorMessage(conn, &savedMessage);
-
-                                               /* Not writable; close connection. */
                                                appendPQExpBuffer(&conn->errorMessage,
                                                                                  libpq_gettext("could not make a writable "
                                                                                                                "connection to server "
                                                                                                                "\"%s:%s\"\n"),
                                                                                  displayed_host, displayed_port);
+
+                                               /* Close connection politely. */
                                                conn->status = CONNECTION_OK;
                                                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);
                                        termPQExpBuffer(&savedMessage);
 
@@ -3184,6 +3310,7 @@ keep_going:                                               /* We will come back to here until there is
                                        PQclear(res);
                                restoreErrorMessage(conn, &savedMessage);
 
+                               /* Append error report to conn->errorMessage. */
                                if (conn->connhost[conn->whichhost].type == CHT_HOST_ADDRESS)
                                        displayed_host = conn->connhost[conn->whichhost].hostaddr;
                                else
@@ -3195,20 +3322,14 @@ keep_going:                                             /* We will come back to here until there is
                                                                  libpq_gettext("test \"SHOW transaction_read_only\" failed "
                                                                                                "on server \"%s:%s\"\n"),
                                                                  displayed_host, displayed_port);
+
+                               /* Close connection politely. */
                                conn->status = CONNECTION_OK;
                                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. */
-                               goto error_return;
+                               /* Try next address */
+                               conn->try_next_addr = true;
+                               goto keep_going;
                        }
 
                default:
@@ -3223,8 +3344,6 @@ keep_going:                                               /* We will come back to here until there is
 
 error_return:
 
-       pgpassfileWarning(conn);
-
        /*
         * 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
@@ -3352,14 +3471,6 @@ makeEmptyPGconn(void)
        conn->verbosity = PQERRORS_DEFAULT;
        conn->show_context = PQSHOW_CONTEXT_ERRORS;
        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
@@ -3581,9 +3692,9 @@ sendTerminateConn(PGconn *conn)
 static void
 closePGconn(PGconn *conn)
 {
-       PGnotify   *notify;
-       pgParameterStatus *pstatus;
-
+       /*
+        * If possible, send Terminate message to close the connection politely.
+        */
        sendTerminateConn(conn);
 
        /*
@@ -3600,31 +3711,13 @@ closePGconn(PGconn *conn)
        pqDropConnection(conn, true);
        conn->status = CONNECTION_BAD;  /* Well, not really _bad_ - just absent */
        conn->asyncStatus = PGASYNC_IDLE;
+       conn->xactStatus = PQTRANS_IDLE;
        pqClearAsyncResult(conn);       /* deallocate result */
        resetPQExpBuffer(&conn->errorMessage);
        release_all_addrinfo(conn);
 
-       notify = conn->notifyHead;
-       while (notify != NULL)
-       {
-               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;
+       /* Reset all state obtained from server, too */
+       pqDropServerData(conn);
 }
 
 /*
@@ -6505,7 +6598,9 @@ pgpassfileWarning(PGconn *conn)
 {
        /* If it was 'invalid authorization', add pgpassfile mention */
        /* 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,
                                                                                                  PG_DIAG_SQLSTATE);
index 4a836b186efdea04d16b9baa83174c914f95f417..bc60373cb0ffd7b43a1f0b727b817e795bb1f632 100644 (file)
@@ -290,6 +290,7 @@ typedef struct pgDataValue
        const char *value;                      /* data value, without zero-termination */
 } PGdataValue;
 
+/* Host address type enum for struct pg_conn_host */
 typedef enum pg_conn_host_type
 {
        CHT_HOST_NAME,
@@ -298,20 +299,19 @@ typedef enum pg_conn_host_type
 } pg_conn_host_type;
 
 /*
- * pg_conn_host stores all information about one of possibly several hosts
- * mentioned in the connection string.  Derived by splitting the pghost
- * on the comma character and then parsing each segment.
+ * pg_conn_host stores all information about each of possibly several hosts
+ * mentioned in the connection string.  Most fields are derived by splitting
+ * the relevant connection parameter (e.g., pghost) at commas.
  */
 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       *hostaddr;           /* host address */
-       char       *port;                       /* port number for this host; if not NULL,
-                                                                * overrides the PGConn's pgport */
+       char       *hostaddr;           /* host numeric IP address */
+       char       *port;                       /* port number (always provided) */
        char       *password;           /* password for this host, read from the
-                                                                * password file.  only set if the PGconn's
-                                                                * pgpass field is NULL. */
+                                                                * password file; NULL if not sought or not
+                                                                * found in password file. */
        struct addrinfo *addrlist;      /* list of possible backend addresses */
 } pg_conn_host;
 
@@ -325,12 +325,13 @@ struct pg_conn
        char       *pghost;                     /* the machine on which the server is running,
                                                                 * or a path to a UNIX-domain socket, or a
                                                                 * comma-separated list of machines and/or
-                                                                * paths, optionally with port suffixes; if
-                                                                * NULL, use DEFAULT_PGSOCKET_DIR */
+                                                                * paths; if NULL, use DEFAULT_PGSOCKET_DIR */
        char       *pghostaddr;         /* the numeric IP address of the machine on
-                                                                * which the server is running.  Takes
-                                                                * precedence over above. */
-       char       *pgport;                     /* the server's communication port number */
+                                                                * which the server is running, or a
+                                                                * comma-separated list of same.  Takes
+                                                                * 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
                                                                 * displayed (OBSOLETE, NOT USED) */
        char       *connect_timeout;    /* connection timeout (numeric string) */
@@ -392,9 +393,9 @@ struct pg_conn
        PGnotify   *notifyTail;         /* newest unreported Notify msg */
 
        /* Support for multiple hosts in connection string */
-       int                     nconnhost;              /* # of possible hosts */
-       int                     whichhost;              /* host we're currently considering */
-       pg_conn_host *connhost;         /* details about each possible host */
+       int                     nconnhost;              /* # of hosts named in conn string */
+       int                     whichhost;              /* host we're currently trying/connected to */
+       pg_conn_host *connhost;         /* details about each named host */
 
        /* Connection data */
        pgsocket        sock;                   /* FD for socket, PGINVALID_SOCKET if
@@ -405,11 +406,12 @@ struct pg_conn
        int                     sversion;               /* server version, e.g. 70401 for 7.4.1 */
        bool            auth_req_received;      /* true if any type of auth req received */
        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_flag;   /* can we mask SIGPIPE via MSG_NOSIGNAL? */
 
        /* 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 */
        PGSetenvStatusType setenv_state;        /* for 2.0 protocol only */
        const PQEnvironmentOption *next_eo;