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