libpq: Handle NegotiateProtocolVersion message
authorPeter Eisentraut <peter@eisentraut.org>
Thu, 17 Nov 2022 14:14:44 +0000 (15:14 +0100)
committerPeter Eisentraut <peter@eisentraut.org>
Thu, 17 Nov 2022 14:42:09 +0000 (15:42 +0100)
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.

Reviewed-by: Jacob Champion <jchampion@timescale.com>
Reviewed-by: Nathan Bossart <nathandbossart@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/f9c7862f-b864-8ef7-a861-c4638c83e209%40enterprisedb.com

src/interfaces/libpq/fe-connect.c
src/interfaces/libpq/fe-protocol3.c
src/interfaces/libpq/libpq-int.h

index 7d54aa35899c0d0baa51163dcbd41ef32f3a693a..f88d672c6c8a3c97d5f7b04d619880c48b20e08b 100644 (file)
@@ -3194,10 +3194,11 @@ keep_going:                                             /* We will come back to here until there is
 
                                /*
                                 * 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'))
                                {
                                        libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
                                                                           beresp);
@@ -3214,14 +3215,15 @@ keep_going:                                             /* We will come back to here until there is
                                /*
                                 * Try to validate message length before using it.
                                 * Authentication requests can't be very large, although GSS
-                                * auth requests may not be that small.  Errors can be a
+                                * auth requests may not be that small.  Same for
+                                * NegotiateProtocolVersion.  Errors can be a
                                 * little larger, but not huge.  If we see a large apparent
                                 * length in an error, it means we're really talking to a
                                 * pre-3.0-protocol server; cope.  (Before version 14, the
                                 * server also used the old protocol for errors that happened
                                 * before processing the startup packet.)
                                 */
-                               if (beresp == 'R' && (msgLength < 8 || msgLength > 2000))
+                               if ((beresp == 'R' || beresp == 'v') && (msgLength < 8 || msgLength > 2000))
                                {
                                        libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
                                                                           beresp);
@@ -3351,6 +3353,16 @@ keep_going:                                              /* We will come back to here until there is
 
                                        goto error_return;
                                }
+                               else if (beresp == 'v')
+                               {
+                                       if (pqGetNegotiateProtocolVersion3(conn))
+                                       {
+                                               goto error_return;
+                                       }
+                                       /* 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;
index 88dd360c905fce76e2db86e4a452a2dd87381384..364bad2b882c839f62fa02d59b20fb8ee6d99a27 100644 (file)
@@ -1385,6 +1385,61 @@ 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                     tmp;
+       ProtocolVersion their_version;
+       int                     num;
+       PQExpBufferData buf;
+
+       if (pqGetInt(&tmp, 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++)
+       {
+               if (pqGets(&conn->workBuffer, conn))
+               {
+                       termPQExpBuffer(&buf);
+                       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 %u.%u, server supports up to %u.%u\n"),
+                                                 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\n",
+                                                                                "protocol extensions not supported by server: %s\n", num),
+                                                 buf.data);
+
+       /* neither -- server shouldn't have sent it */
+       if (!(their_version < conn->pversion) && !(num > 0))
+               appendPQExpBuffer(&conn->errorMessage,
+                                                 libpq_gettext("invalid %s message"), "NegotiateProtocolVersion");
+
+       termPQExpBuffer(&buf);
+       return 0;
+}
+
+
 /*
  * Attempt to read a ParameterStatus message.
  * This is possible in several places, so we break it out as a subroutine.
index c24645b469652089731e059a16a796cb20549033..512762f99917c49369c18ff9e5439ee50a040c92 100644 (file)
@@ -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);