From 4b54f3811038911e3cfaafe75a727e710119e1cd Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Tue, 2 Jan 2024 11:16:19 +0100 Subject: [PATCH v3 1/4] libpq: Handle NegotiateProtocolVersion message more leniently Currently libpq would always error when the server returned a NegotiateProtocolVersion message. This was fine because libpq only supports a single protocol version and did not support any protocol extensions. But we now need to change that to be able to add support for future protocol changes, with a working fallback when connecting to an older server. 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. In addition it now allows connecting when the server does not support some of the requested protocol extensions. This patch also adds a new PQunsupportedProtocolExtensions API to libpq, since a user might want to take some action in case a protocol extension is not supported. --- doc/src/sgml/libpq.sgml | 19 ++++++++++++ src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-connect.c | 46 ++++++++++++++++++++++++++++- src/interfaces/libpq/fe-protocol3.c | 46 ++++++++++------------------- src/interfaces/libpq/libpq-fe.h | 1 + src/interfaces/libpq/libpq-int.h | 2 ++ 6 files changed, 83 insertions(+), 32 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index ed88ac001a1..2e9ae41e389 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2576,6 +2576,25 @@ int PQprotocolVersion(const PGconn *conn); + + PQprotocolVersionPQprotocolVersion + + + + Returns a null-terminated array of protocol extensions that were + requested by the client but are not supported by the server. + +int PQunsupportedProtocolExtensions(const PGconn *conn); + + Applications might wish to use this function to determine whether certain + protocol extensions they intended to use are supported. Even when some + extension is not supported the connection can still be used, only the + unsupported extensions cannot be used. Returns NULL if the connection is + bad. + + + + PQserverVersionPQserverVersion diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 850734ac96c..849617cb9b2 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -191,3 +191,4 @@ PQclosePrepared 188 PQclosePortal 189 PQsendClosePrepared 190 PQsendClosePortal 191 +PQunsupportedProtocolExtensions 192 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index bf83a9b5697..14214cac62d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -382,6 +382,8 @@ static const PQEnvironmentOption EnvironmentOptions[] = } }; +static const char *no_unsupported_protocol_extensions[1] = {NULL}; + /* The connection URI must start with either of the following designators: */ static const char uri_designator[] = "postgresql://"; static const char short_uri_designator[] = "postgres://"; @@ -3782,9 +3784,25 @@ keep_going: /* We will come back to here until there is libpq_append_conn_error(conn, "received invalid protocol negotiation message"); goto error_return; } + + if (conn->pversion < PG_PROTOCOL_EARLIEST) + { + libpq_append_conn_error(conn, "protocol version not supported by server: client supports down to %u.%u, server supports up to %u.%u", + PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST), PG_PROTOCOL_MINOR(PG_PROTOCOL_EARLIEST), + PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion)); + goto error_return; + } + + /* neither -- server shouldn't have sent it */ + if (!(conn->pversion < PG_PROTOCOL_LATEST) && !conn->unsupported_pextensions) + { + libpq_append_conn_error(conn, "invalid %s message", "NegotiateProtocolVersion"); + goto error_return; + } + /* OK, we read the message; mark data consumed */ conn->inStart = conn->inCursor; - goto error_return; + goto keep_going; } /* It is an authentication request. */ @@ -4411,6 +4429,20 @@ freePGconn(PGconn *conn) } free(conn->connhost); + if (conn->unsupported_pextensions) + { + /* clean up unsupported_pextensions entries */ + int i = 0; + + while (conn->unsupported_pextensions[i]) + { + free(conn->unsupported_pextensions[i]); + i++; + } + free(conn->unsupported_pextensions); + } + + free(conn->client_encoding_initial); free(conn->events); free(conn->pghost); @@ -7234,6 +7266,18 @@ PQprotocolVersion(const PGconn *conn) return PG_PROTOCOL_MAJOR(conn->pversion); } +const char ** +PQunsupportedProtocolExtensions(const PGconn *conn) +{ + if (!conn) + return NULL; + if (conn->status == CONNECTION_BAD) + return NULL; + if (!conn->unsupported_pextensions) + return no_unsupported_protocol_extensions; + return (const char **) conn->unsupported_pextensions; +} + int PQserverVersion(const PGconn *conn) { diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 8c4ec079caa..89ce0d3962f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1410,49 +1410,33 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding) int pqGetNegotiateProtocolVersion3(PGconn *conn) { - int tmp; - ProtocolVersion their_version; + int their_version; int num; - PQExpBufferData buf; - if (pqGetInt(&tmp, 4, conn) != 0) + if (pqGetInt(&their_version, 4, conn) != 0) return EOF; - their_version = tmp; if (pqGetInt(&num, 4, conn) != 0) return EOF; - initPQExpBuffer(&buf); - for (int i = 0; i < num; i++) + conn->pversion = their_version; + if (num) { - if (pqGets(&conn->workBuffer, conn)) + conn->unsupported_pextensions = calloc(num + 1, sizeof(char *)); + for (int i = 0; i < num; i++) { - termPQExpBuffer(&buf); - return EOF; + if (pqGets(&conn->workBuffer, conn)) + { + return EOF; + } + conn->unsupported_pextensions[i] = strdup(conn->workBuffer.data); + if (!conn->unsupported_pextensions[i]) + { + return EOF; + } } - if (buf.len > 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_ngettext("protocol extension not supported by server: %s", - "protocol extensions not supported by server: %s", num), - buf.data); - appendPQExpBufferChar(&conn->errorMessage, '\n'); - } - - /* 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; } diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 97762d56f5d..408ba495088 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -347,6 +347,7 @@ extern PGTransactionStatusType PQtransactionStatus(const PGconn *conn); extern const char *PQparameterStatus(const PGconn *conn, const char *paramName); extern int PQprotocolVersion(const PGconn *conn); +extern const char **PQunsupportedProtocolExtensions(const PGconn *conn); extern int PQserverVersion(const PGconn *conn); extern char *PQerrorMessage(const PGconn *conn); extern int PQsocket(const PGconn *conn); diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7888199b0d9..c379391a6b2 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -461,6 +461,8 @@ struct pg_conn SockAddr laddr; /* Local address */ SockAddr raddr; /* Remote address */ ProtocolVersion pversion; /* FE/BE protocol version in use */ + char **unsupported_pextensions; /* Unsupported protocol + * extensions, null-terminated */ 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 */ base-commit: dffde5bf16a590543a35bedffc936a33686651d4 -- 2.34.1