We have the NegotiateProtocolVersion protocol message [0], but libpq doesn't actually handle it.

Say I increase the protocol number in libpq:

-       conn->pversion = PG_PROTOCOL(3, 0);
+       conn->pversion = PG_PROTOCOL(3, 1);

Then I get

psql: error: connection to server on socket "/tmp/.s.PGSQL.65432" failed: expected authentication request from server, but received v

And the same for a protocol option (_pq_.something).

Over in the column encryption patch, I'm proposing to add such a protocol option, and the above is currently the behavior when the server doesn't support it.

The attached patch adds explicit handling of this protocol message to libpq. So the output in the above case would then be:

psql: error: connection to server on socket "/tmp/.s.PGSQL.65432" failed: protocol version not supported by server: client uses 3.1, server supports 3.0

Or to test a protocol option:

@@ -2250,6 +2291,8 @@ build_startup_packet(const PGconn *conn, char *packet,
    if (conn->client_encoding_initial && conn->client_encoding_initial[0])
ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial);

+   ADD_STARTUP_OPTION("_pq_.foobar", "1");
+
    /* Add any environment-driven GUC settings needed */
    for (next_eo = options; next_eo->envName; next_eo++)
    {

Result:

psql: error: connection to server on socket "/tmp/.s.PGSQL.65432" failed: protocol extension not supported by server: _pq_.foobar


[0]: https://www.postgresql.org/docs/devel/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-NEGOTIATEPROTOCOLVERSION
From 93df3a8d0a15b718669e4b21d8455a91ca1896fd Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Thu, 13 Oct 2022 10:22:49 +0200
Subject: [PATCH] libpq: Handle NegotiateProtocolVersion message

Before, receiving a NegotiateProtocolVersion message would result in a
confusing error message like

    expected authentication request from server, but received v

This adds proper handling of this protocol message and produces an
on-topic error message from it.
---
 src/interfaces/libpq/fe-connect.c   | 18 ++++++++++---
 src/interfaces/libpq/fe-protocol3.c | 41 +++++++++++++++++++++++++++++
 src/interfaces/libpq/libpq-int.h    |  1 +
 3 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/src/interfaces/libpq/fe-connect.c 
b/src/interfaces/libpq/fe-connect.c
index 746e9b4f1efc..1c0d8243a6ca 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -3246,10 +3246,11 @@ PQconnectPoll(PGconn *conn)
 
                                /*
                                 * Validate message type: we expect only an 
authentication
-                                * request or an error here.  Anything else 
probably means
-                                * it's not Postgres on the other end at all.
+                                * request, NegotiateProtocolVersion, or an 
error here.
+                                * Anything else probably means it's not 
Postgres on the other
+                                * end at all.
                                 */
-                               if (!(beresp == 'R' || beresp == 'E'))
+                               if (!(beresp == 'R' || beresp == 'v' || beresp 
== 'E'))
                                {
                                        appendPQExpBuffer(&conn->errorMessage,
                                                                          
libpq_gettext("expected authentication request from server, but received %c\n"),
@@ -3405,6 +3406,17 @@ PQconnectPoll(PGconn *conn)
 
                                        goto error_return;
                                }
+                               else if (beresp == 'v')
+                               {
+                                       if 
(pqGetNegotiateProtocolVersion3(conn))
+                                       {
+                                               /* We'll come back when there 
is more data */
+                                               return PGRES_POLLING_READING;
+                                       }
+                                       /* OK, we read the message; mark data 
consumed */
+                                       conn->inStart = conn->inCursor;
+                                       goto error_return;
+                               }
 
                                /* It is an authentication request. */
                                conn->auth_req_received = true;
diff --git a/src/interfaces/libpq/fe-protocol3.c 
b/src/interfaces/libpq/fe-protocol3.c
index f001137b7692..2f6c1494c1b5 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1397,6 +1397,47 @@ reportErrorPosition(PQExpBuffer msg, const char *query, 
int loc, int encoding)
 }
 
 
+/*
+ * Attempt to read a NegotiateProtocolVersion message.
+ * Entry: 'v' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *              returns EOF if not enough data.
+ */
+int
+pqGetNegotiateProtocolVersion3(PGconn *conn)
+{
+       int                     their_version;
+       int                     num;
+       PQExpBufferData buf;
+
+       initPQExpBuffer(&buf);
+       if (pqGetInt(&their_version, 4, conn) != 0)
+               return EOF;
+       if (pqGetInt(&num, 4, conn) != 0)
+               return EOF;
+       for (int i = 0; i < num; i++)
+       {
+               if (pqGets(&conn->workBuffer, conn))
+                       return EOF;
+               if (buf.len > 0)
+                       appendPQExpBufferChar(&buf, ' ');
+               appendPQExpBufferStr(&buf, conn->workBuffer.data);
+       }
+
+       if (their_version != conn->pversion)
+               appendPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("protocol 
version not supported by server: client uses %d.%d, server supports %d.%d\n"),
+                                                 
PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion),
+                                                 
PG_PROTOCOL_MAJOR(their_version), PG_PROTOCOL_MINOR(their_version));
+       else
+               appendPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("protocol 
extension not supported by server: %s\n"), buf.data);
+
+       termPQExpBuffer(&buf);
+       return 0;
+}
+
+
 /*
  * Attempt to read a ParameterStatus message.
  * This is possible in several places, so we break it out as a subroutine.
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index c75ed63a2c62..c59afac7a086 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -685,6 +685,7 @@ extern void pqParseInput3(PGconn *conn);
 extern int     pqGetErrorNotice3(PGconn *conn, bool isError);
 extern void pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res,
                                                                 PGVerbosity 
verbosity, PGContextVisibility show_context);
+extern int     pqGetNegotiateProtocolVersion3(PGconn *conn);
 extern int     pqGetCopyData3(PGconn *conn, char **buffer, int async);
 extern int     pqGetline3(PGconn *conn, char *s, int maxlen);
 extern int     pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
-- 
2.37.3

Reply via email to