From 1fcb349688a02c044f675c8c84ae70a107cac76f Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Tue, 2 Jan 2024 11:16:19 +0100 Subject: [PATCH v6 02/10] 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 that it had requested 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 21195e0e728..98818970ba8 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 28b861fd93b..ca9744801a8 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -192,3 +192,4 @@ PQclosePortal 189 PQsendClosePrepared 190 PQsendClosePortal 191 PQchangePassword 192 +PQunsupportedProtocolExtensions 193 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 85da2a4e8dc..fac178817dd 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 701d58e1087..75a0ee96785 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 f0ec660cb69..809204d2eb4 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 f0143726bbc..d302a95ceaa 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 */ -- 2.34.1