mirror of
https://github.com/postgres/postgres.git
synced 2025-08-05 07:41:25 +03:00
libpq: Handle NegotiateProtocolVersion message differently
Previously libpq would always error out if the server sends a NegotiateProtocolVersion message. This was fine because libpq only supported a single protocol version and did not support any protocol parameters. But in the upcoming commits, we will introduce a new protocol version and the NegotiateProtocolVersion message starts to actually be used. This patch modifies the client side checks to allow a range of supported protocol versions, instead of only allowing the exact version that was requested. Currently this "range" only contains the 3.0 version, but in a future commit we'll change this. Also clarify the error messages, making them suitable for the world where libpq will support multiple protocol versions and protocol extensions. Note that until the later commits that introduce new protocol version, this change does not have any behavioural effect, because libpq will only request version 3.0 and will never send protocol parameters, and therefore will never receive a NegotiateProtocolVersion message from the server. Author: Jelte Fennema-Nio <postgres@jeltef.nl> Reviewed-by: Robert Haas <robertmhaas@gmail.com> (earlier versions) Discussion: https://www.postgresql.org/message-id/CAGECzQTfc_O%2BHXqAo5_-xG4r3EFVsTefUeQzSvhEyyLDba-O9w@mail.gmail.com Discussion: https://www.postgresql.org/message-id/CAGECzQRbAGqJnnJJxTdKewTsNOovUt4bsx3NFfofz3m2j-t7tA@mail.gmail.com
This commit is contained in:
@@ -667,6 +667,7 @@ pqDropServerData(PGconn *conn)
|
|||||||
|
|
||||||
/* Reset assorted other per-connection state */
|
/* Reset assorted other per-connection state */
|
||||||
conn->last_sqlstate[0] = '\0';
|
conn->last_sqlstate[0] = '\0';
|
||||||
|
conn->pversion_negotiated = false;
|
||||||
conn->auth_req_received = false;
|
conn->auth_req_received = false;
|
||||||
conn->client_finished_auth = false;
|
conn->client_finished_auth = false;
|
||||||
conn->password_needed = false;
|
conn->password_needed = false;
|
||||||
@@ -4084,16 +4085,24 @@ keep_going: /* We will come back to here until there is
|
|||||||
|
|
||||||
CONNECTION_FAILED();
|
CONNECTION_FAILED();
|
||||||
}
|
}
|
||||||
|
/* Handle NegotiateProtocolVersion */
|
||||||
else if (beresp == PqMsg_NegotiateProtocolVersion)
|
else if (beresp == PqMsg_NegotiateProtocolVersion)
|
||||||
{
|
{
|
||||||
if (pqGetNegotiateProtocolVersion3(conn))
|
if (conn->pversion_negotiated)
|
||||||
{
|
{
|
||||||
libpq_append_conn_error(conn, "received invalid protocol negotiation message");
|
libpq_append_conn_error(conn, "received duplicate protocol negotiation message");
|
||||||
goto error_return;
|
goto error_return;
|
||||||
}
|
}
|
||||||
|
if (pqGetNegotiateProtocolVersion3(conn))
|
||||||
|
{
|
||||||
|
/* pqGetNegotiateProtocolVersion3 set error already */
|
||||||
|
goto error_return;
|
||||||
|
}
|
||||||
|
conn->pversion_negotiated = true;
|
||||||
|
|
||||||
/* OK, we read the message; mark data consumed */
|
/* OK, we read the message; mark data consumed */
|
||||||
pqParseDone(conn, conn->inCursor);
|
pqParseDone(conn, conn->inCursor);
|
||||||
goto error_return;
|
goto keep_going;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* It is an authentication request. */
|
/* It is an authentication request. */
|
||||||
|
@@ -870,6 +870,7 @@ advance_and_error:
|
|||||||
/*
|
/*
|
||||||
* Attempt to read an Error or Notice response message.
|
* Attempt to read an Error or Notice response message.
|
||||||
* This is possible in several places, so we break it out as a subroutine.
|
* This is possible in several places, so we break it out as a subroutine.
|
||||||
|
*
|
||||||
* Entry: 'E' or 'N' message type and length have already been consumed.
|
* Entry: 'E' or 'N' message type and length have already been consumed.
|
||||||
* Exit: returns 0 if successfully consumed message.
|
* Exit: returns 0 if successfully consumed message.
|
||||||
* returns EOF if not enough data.
|
* returns EOF if not enough data.
|
||||||
@@ -1399,64 +1400,87 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding)
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Attempt to read a NegotiateProtocolVersion message.
|
* Attempt to read a NegotiateProtocolVersion message. Sets conn->pversion
|
||||||
|
* to the version that's negotiated by the server.
|
||||||
|
*
|
||||||
* Entry: 'v' message type and length have already been consumed.
|
* Entry: 'v' message type and length have already been consumed.
|
||||||
* Exit: returns 0 if successfully consumed message.
|
* Exit: returns 0 if successfully consumed message.
|
||||||
* returns EOF if not enough data.
|
* returns 1 on failure. The error message is filled in.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
pqGetNegotiateProtocolVersion3(PGconn *conn)
|
pqGetNegotiateProtocolVersion3(PGconn *conn)
|
||||||
{
|
{
|
||||||
int tmp;
|
int their_version;
|
||||||
ProtocolVersion their_version;
|
|
||||||
int num;
|
int num;
|
||||||
PQExpBufferData buf;
|
|
||||||
|
|
||||||
if (pqGetInt(&tmp, 4, conn) != 0)
|
if (pqGetInt(&their_version, 4, conn) != 0)
|
||||||
return EOF;
|
goto eof;
|
||||||
their_version = tmp;
|
|
||||||
|
|
||||||
if (pqGetInt(&num, 4, conn) != 0)
|
if (pqGetInt(&num, 4, conn) != 0)
|
||||||
return EOF;
|
goto eof;
|
||||||
|
|
||||||
initPQExpBuffer(&buf);
|
/* Check the protocol version */
|
||||||
|
if (their_version > conn->pversion)
|
||||||
|
{
|
||||||
|
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to a higher-numbered version");
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (their_version < PG_PROTOCOL(3, 0))
|
||||||
|
{
|
||||||
|
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to pre-3.0 protocol version");
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num < 0)
|
||||||
|
{
|
||||||
|
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported negative number of unsupported parameters");
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (their_version == conn->pversion && num == 0)
|
||||||
|
{
|
||||||
|
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server negotiated but asks for no changes");
|
||||||
|
goto failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* the version is acceptable */
|
||||||
|
conn->pversion = their_version;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We don't currently request any protocol extensions, so we don't expect
|
||||||
|
* the server to reply with any either.
|
||||||
|
*/
|
||||||
for (int i = 0; i < num; i++)
|
for (int i = 0; i < num; i++)
|
||||||
{
|
{
|
||||||
if (pqGets(&conn->workBuffer, conn))
|
if (pqGets(&conn->workBuffer, conn))
|
||||||
{
|
{
|
||||||
termPQExpBuffer(&buf);
|
goto eof;
|
||||||
return EOF;
|
|
||||||
}
|
}
|
||||||
if (buf.len > 0)
|
if (strncmp(conn->workBuffer.data, "_pq_.", 5) != 0)
|
||||||
appendPQExpBufferChar(&buf, ' ');
|
|
||||||
appendPQExpBufferStr(&buf, conn->workBuffer.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (their_version < conn->pversion)
|
|
||||||
libpq_append_conn_error(conn, "protocol version not supported by server: client uses %u.%u, server supports up to %u.%u",
|
|
||||||
PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion),
|
|
||||||
PG_PROTOCOL_MAJOR(their_version), PG_PROTOCOL_MINOR(their_version));
|
|
||||||
if (num > 0)
|
|
||||||
{
|
{
|
||||||
appendPQExpBuffer(&conn->errorMessage,
|
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported unsupported parameter name without a _pq_. prefix (\"%s\")", conn->workBuffer.data);
|
||||||
libpq_ngettext("protocol extension not supported by server: %s",
|
goto failure;
|
||||||
"protocol extensions not supported by server: %s", num),
|
}
|
||||||
buf.data);
|
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data);
|
||||||
appendPQExpBufferChar(&conn->errorMessage, '\n');
|
goto failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* neither -- server shouldn't have sent it */
|
|
||||||
if (!(their_version < conn->pversion) && !(num > 0))
|
|
||||||
libpq_append_conn_error(conn, "invalid %s message", "NegotiateProtocolVersion");
|
|
||||||
|
|
||||||
termPQExpBuffer(&buf);
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
eof:
|
||||||
|
libpq_append_conn_error(conn, "received invalid protocol negotation message: message too short");
|
||||||
|
failure:
|
||||||
|
conn->asyncStatus = PGASYNC_READY;
|
||||||
|
pqSaveErrorResult(conn);
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Attempt to read a ParameterStatus message.
|
* Attempt to read a ParameterStatus message.
|
||||||
* This is possible in several places, so we break it out as a subroutine.
|
* This is possible in several places, so we break it out as a subroutine.
|
||||||
|
*
|
||||||
* Entry: 'S' message type and length have already been consumed.
|
* Entry: 'S' message type and length have already been consumed.
|
||||||
* Exit: returns 0 if successfully consumed message.
|
* Exit: returns 0 if successfully consumed message.
|
||||||
* returns EOF if not enough data.
|
* returns EOF if not enough data.
|
||||||
@@ -1486,6 +1510,7 @@ getParameterStatus(PGconn *conn)
|
|||||||
/*
|
/*
|
||||||
* Attempt to read a Notify response message.
|
* Attempt to read a Notify response message.
|
||||||
* This is possible in several places, so we break it out as a subroutine.
|
* This is possible in several places, so we break it out as a subroutine.
|
||||||
|
*
|
||||||
* Entry: 'A' message type and length have already been consumed.
|
* Entry: 'A' message type and length have already been consumed.
|
||||||
* Exit: returns 0 if successfully consumed Notify message.
|
* Exit: returns 0 if successfully consumed Notify message.
|
||||||
* returns EOF if not enough data.
|
* returns EOF if not enough data.
|
||||||
|
@@ -496,6 +496,8 @@ struct pg_conn
|
|||||||
SockAddr raddr; /* Remote address */
|
SockAddr raddr; /* Remote address */
|
||||||
ProtocolVersion pversion; /* FE/BE protocol version in use */
|
ProtocolVersion pversion; /* FE/BE protocol version in use */
|
||||||
int sversion; /* server version, e.g. 70401 for 7.4.1 */
|
int sversion; /* server version, e.g. 70401 for 7.4.1 */
|
||||||
|
bool pversion_negotiated; /* true if NegotiateProtocolVersion
|
||||||
|
* was received */
|
||||||
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 gssapi_used; /* true if authenticated via gssapi */
|
bool gssapi_used; /* true if authenticated via gssapi */
|
||||||
|
Reference in New Issue
Block a user