diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 53832d08e2..a98b2d0fcd 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -495,6 +495,18 @@ hostnossl database user
+ redirect>
+
+
+ Redirects the connection to the alternative server specified if it
+ matches the requested database user name and IP address. This is
+ only available for Protocol Version 3.1 and beyond.
+ See for details.
+
+
+
+
+
ldap
@@ -624,7 +636,7 @@ hostnossl database user
non-null error fields indicate problems in the
corresponding lines of the file.
-
+
To connect to a particular database, a user must not only pass the
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 8d543334ae..529226c043 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -1809,7 +1809,7 @@ connectDBStart(PGconn *conn)
*/
conn->whichhost = 0;
conn->addr_cur = conn->connhost[0].addrlist;
- conn->pversion = PG_PROTOCOL(3, 0);
+ conn->pversion = PG_PROTOCOL(3, 1);
conn->send_appname = true;
conn->status = CONNECTION_NEEDED;
@@ -2007,6 +2007,14 @@ PQconnectPoll(PGconn *conn)
int optval;
PQExpBufferData savedMessage;
+ /* Variable declarations for redirection. */
+ int originalMsgLen; /* Length in bytes of message sans msg type */
+ int runningMsgLen; /* Length in bytes of message sans metadata */
+ int availableMsgLen;
+ char *altServer = NULL;
+ char *altPort = NULL;
+ bool redirectionError = false; /* Flag used to mark exceptions */
+
if (conn == NULL)
return PGRES_POLLING_FAILED;
@@ -2024,6 +2032,7 @@ PQconnectPoll(PGconn *conn)
/* These are reading states */
case CONNECTION_AWAITING_RESPONSE:
+ case CONNECTION_REDIRECTION:
case CONNECTION_AUTH_OK:
{
/* Load waiting data */
@@ -2655,6 +2664,12 @@ keep_going: /* We will come back to here until there is
return PGRES_POLLING_READING;
}
+ if (beresp == 'M')
+ {
+ conn->status = CONNECTION_REDIRECTION;
+ goto keep_going;
+ }
+
/*
* Validate message type: we expect only an authentication
* request or an error here. Anything else probably means
@@ -3097,6 +3112,125 @@ keep_going: /* We will come back to here until there is
conn->status = CONNECTION_OK;
return PGRES_POLLING_OK;
}
+
+ case CONNECTION_REDIRECTION:
+ {
+ /* Mark 'M' consumed: a single byte for message type. */
+ conn->inCursor = conn->inStart + 1;
+
+ /* Obtain message length from packet. */
+ if (pqGetInt(&originalMsgLen, sizeof(int32), conn))
+ return PGRES_POLLING_READING;
+
+ /* Obtain the number of bytes in payload. */
+ runningMsgLen = originalMsgLen - sizeof(int32);
+
+ /* Enlarge buffer if payload's size is greater than what is available. */
+ availableMsgLen = conn->inEnd - conn->inCursor;
+ if (availableMsgLen < runningMsgLen)
+ {
+ if (pqCheckInBufferSpace(conn->inCursor + (size_t)runningMsgLen, conn))
+ return PGRES_POLLING_READING;
+ }
+
+ PQExpBuffer buf = createPQExpBuffer();
+ while (pqGets(buf, conn) != EOF)
+ {
+ if (!strcmp(buf->data, "server"))
+ {
+ if (pqGets(buf, conn) == EOF)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("failed to obtain server value from redirection packet"));
+
+ redirectionError = true;
+ break;
+ }
+ altServer = strdup(buf->data);
+ }
+ else if (!strcmp(buf->data, "port"))
+ {
+ if (pqGets(buf, conn) == EOF)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("failed to obtain port value from redirection packet"));
+
+ redirectionError = true;
+ break;
+ }
+ altPort = strdup(buf->data);
+ }
+ else
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("unknown key in redirection server packet"));
+ redirectionError = true;
+ }
+
+ if (redirectionError)
+ {
+ /* Free buffer to prevent memory leak on error. */
+ destroyPQExpBuffer(buf);
+
+ /* Free strdup'd variables. */
+ if (altServer)
+ free(altServer);
+
+ if (altPort)
+ free(altPort);
+
+ goto error_return;
+ }
+
+ /* Buffer length does not account for null-terminated strings. */
+ runningMsgLen -= buf->len + 1;
+ }
+
+ /* Free buffer used for reading string params in packet. */
+ destroyPQExpBuffer(buf);
+
+ /* Check for extraneous data in packet. */
+ if (conn->inCursor != conn->inStart + 1 + originalMsgLen)
+ {
+ appendPQExpBuffer(&conn->errorMessage,
+ libpq_gettext("Extraneous data in redirection packet from server\n"));
+
+ /* Free strdup'd variables. */
+ if (altServer)
+ free(altServer);
+
+ if (altPort)
+ free(altPort);
+
+ goto error_return;
+ }
+
+ /* Mark incoming data consumed */
+ conn->inStart = conn->inCursor;
+
+ /* Drop existing connection. */
+ pqDropConnection(conn, true);
+
+ /* Set connection parameters. */
+ if (conn->pghost)
+ {
+ free(conn->pghost);
+ conn->pghost = altServer;
+ }
+
+ if (conn->pgport)
+ {
+ free(conn->pgport);
+ conn->pgport = altPort;
+ }
+
+ /* connectDBStart() sets appropriate connection status. */
+ if (!connectOptions2(conn) || !connectDBStart(conn))
+ conn->status = CONNECTION_BAD;
+
+ goto keep_going;
+ }
+
case CONNECTION_CHECK_WRITABLE:
{
const char *displayed_host;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index ed9c806861..2a8f036b4b 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -65,8 +65,9 @@ typedef enum
CONNECTION_NEEDED, /* Internal state: connect() needed */
CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable
* connection. */
- CONNECTION_CONSUME /* Wait for any pending message and consume
+ CONNECTION_CONSUME, /* Wait for any pending message and consume
* them. */
+ CONNECTION_REDIRECTION /* Redirection */
} ConnStatusType;
typedef enum