mirror of
https://github.com/postgres/postgres.git
synced 2025-07-28 23:42:10 +03:00
libpq: Add min/max_protocol_version connection options
All supported version of the PostgreSQL server send the NegotiateProtocolVersion message when an unsupported minor protocol version is requested by a client. But many other applications that implement the PostgreSQL protocol (connection poolers, or other databases) do not, and the same is true for PostgreSQL server versions older than 9.3. Connecting to such other applications thus fails if a client requests a protocol version different than 3.0. This patch adds a max_protocol_version connection option to libpq that specifies the protocol version that libpq should request from the server. Currently only 3.0 is supported, but that will change in a future commit that bumps the protocol version. Even after that version bump the default will likely stay 3.0 for the time being. Once more of the ecosystem supports the NegotiateProtocolVersion message we might want to change the default to the latest minor version. This also adds the similar min_protocol_version connection option, to allow the client to specify that connecting should fail if a lower protocol version is attempted by the server. This can be used to ensure that certain protocol features are used, which can be particularly useful if those features impact security. 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:
@ -91,11 +91,10 @@ is_unixsock_path(const char *path)
|
||||
|
||||
/*
|
||||
* The earliest and latest frontend/backend protocol version supported.
|
||||
* (Only protocol version 3 is currently supported)
|
||||
*/
|
||||
|
||||
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(3,0)
|
||||
#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,0)
|
||||
#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,2)
|
||||
|
||||
typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
|
||||
|
||||
|
@ -325,6 +325,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
|
||||
"Require-Auth", "", 14, /* sizeof("scram-sha-256") == 14 */
|
||||
offsetof(struct pg_conn, require_auth)},
|
||||
|
||||
{"min_protocol_version", "PGMINPROTOCOLVERSION",
|
||||
NULL, NULL,
|
||||
"Min-Protocol-Version", "", 6, /* sizeof("latest") = 6 */
|
||||
offsetof(struct pg_conn, min_protocol_version)},
|
||||
|
||||
{"max_protocol_version", "PGMAXPROTOCOLVERSION",
|
||||
NULL, NULL,
|
||||
"Max-Protocol-Version", "", 6, /* sizeof("latest") = 6 */
|
||||
offsetof(struct pg_conn, max_protocol_version)},
|
||||
|
||||
{"ssl_min_protocol_version", "PGSSLMINPROTOCOLVERSION", "TLSv1.2", NULL,
|
||||
"SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
|
||||
offsetof(struct pg_conn, ssl_min_protocol_version)},
|
||||
@ -483,6 +493,7 @@ static void pgpassfileWarning(PGconn *conn);
|
||||
static void default_threadlock(int acquire);
|
||||
static bool sslVerifyProtocolVersion(const char *version);
|
||||
static bool sslVerifyProtocolRange(const char *min, const char *max);
|
||||
static bool pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn, const char *context);
|
||||
|
||||
|
||||
/* global variable because fe-auth.c needs to access it */
|
||||
@ -2081,6 +2092,48 @@ pqConnectOptions2(PGconn *conn)
|
||||
}
|
||||
}
|
||||
|
||||
if (conn->min_protocol_version)
|
||||
{
|
||||
if (!pqParseProtocolVersion(conn->min_protocol_version, &conn->min_pversion, conn, "min_protocol_version"))
|
||||
{
|
||||
conn->status = CONNECTION_BAD;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
conn->min_pversion = PG_PROTOCOL_EARLIEST;
|
||||
}
|
||||
|
||||
if (conn->max_protocol_version)
|
||||
{
|
||||
if (!pqParseProtocolVersion(conn->max_protocol_version, &conn->max_pversion, conn, "max_protocol_version"))
|
||||
{
|
||||
conn->status = CONNECTION_BAD;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* To not break connecting to older servers/poolers that do not yet
|
||||
* support NegotiateProtocolVersion, default to the 3.0 protocol at
|
||||
* least for a while longer. Except when min_protocol_version is set
|
||||
* to something larger, then we might as well default to the latest.
|
||||
*/
|
||||
if (conn->min_pversion > PG_PROTOCOL(3, 0))
|
||||
conn->max_pversion = PG_PROTOCOL_LATEST;
|
||||
else
|
||||
conn->max_pversion = PG_PROTOCOL(3, 0);
|
||||
}
|
||||
|
||||
if (conn->min_pversion > conn->max_pversion)
|
||||
{
|
||||
conn->status = CONNECTION_BAD;
|
||||
libpq_append_conn_error(conn, "min_protocol_version is greater than max_protocol_version");
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Resolve special "auto" client_encoding from the locale
|
||||
*/
|
||||
@ -3084,7 +3137,7 @@ keep_going: /* We will come back to here until there is
|
||||
* must persist across individual connection attempts, but we must
|
||||
* reset them when we start to consider a new server.
|
||||
*/
|
||||
conn->pversion = PG_PROTOCOL(3, 0);
|
||||
conn->pversion = conn->max_pversion;
|
||||
conn->send_appname = true;
|
||||
conn->failed_enc_methods = 0;
|
||||
conn->current_enc_method = 0;
|
||||
@ -4102,6 +4155,7 @@ keep_going: /* We will come back to here until there is
|
||||
|
||||
/* OK, we read the message; mark data consumed */
|
||||
pqParseDone(conn, conn->inCursor);
|
||||
|
||||
goto keep_going;
|
||||
}
|
||||
|
||||
@ -8157,6 +8211,38 @@ error:
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse and try to interpret "value" as a ProtocolVersion value, and if
|
||||
* successful, store it in *result.
|
||||
*/
|
||||
static bool
|
||||
pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn,
|
||||
const char *context)
|
||||
{
|
||||
if (strcmp(value, "latest") == 0)
|
||||
{
|
||||
*result = PG_PROTOCOL_LATEST;
|
||||
return true;
|
||||
}
|
||||
if (strcmp(value, "3.0") == 0)
|
||||
{
|
||||
*result = PG_PROTOCOL(3, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 3.1 never existed, we went straight from 3.0 to 3.2 */
|
||||
|
||||
if (strcmp(value, "3.2") == 0)
|
||||
{
|
||||
*result = PG_PROTOCOL(3, 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
|
||||
context, value);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* To keep the API consistent, the locking stubs are always provided, even
|
||||
* if they are not required.
|
||||
|
@ -1432,6 +1432,13 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/* 3.1 never existed, we went straight from 3.0 to 3.2 */
|
||||
if (their_version == PG_PROTOCOL(3, 1))
|
||||
{
|
||||
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requests downgrade to non-existent 3.1 protocol version");
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (num < 0)
|
||||
{
|
||||
libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported negative number of unsupported parameters");
|
||||
@ -1444,6 +1451,17 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
|
||||
goto failure;
|
||||
}
|
||||
|
||||
if (their_version < conn->min_pversion)
|
||||
{
|
||||
libpq_append_conn_error(conn, "server only supports protocol version %d.%d, but min_protocol_version was set to %d.%d",
|
||||
PG_PROTOCOL_MAJOR(their_version),
|
||||
PG_PROTOCOL_MINOR(their_version),
|
||||
PG_PROTOCOL_MAJOR(conn->min_pversion),
|
||||
PG_PROTOCOL_MINOR(conn->min_pversion));
|
||||
|
||||
goto failure;
|
||||
}
|
||||
|
||||
/* the version is acceptable */
|
||||
conn->pversion = their_version;
|
||||
|
||||
|
@ -417,6 +417,8 @@ struct pg_conn
|
||||
char *gsslib; /* What GSS library to use ("gssapi" or
|
||||
* "sspi") */
|
||||
char *gssdelegation; /* Try to delegate GSS credentials? (0 or 1) */
|
||||
char *min_protocol_version; /* minimum used protocol version */
|
||||
char *max_protocol_version; /* maximum used protocol version */
|
||||
char *ssl_min_protocol_version; /* minimum TLS protocol version */
|
||||
char *ssl_max_protocol_version; /* maximum TLS protocol version */
|
||||
char *target_session_attrs; /* desired session properties */
|
||||
@ -539,6 +541,8 @@ struct pg_conn
|
||||
void *scram_client_key_binary; /* binary SCRAM client key */
|
||||
size_t scram_server_key_len;
|
||||
void *scram_server_key_binary; /* binary SCRAM server key */
|
||||
ProtocolVersion min_pversion; /* protocol version to request */
|
||||
ProtocolVersion max_pversion; /* protocol version to request */
|
||||
|
||||
/* Miscellaneous stuff */
|
||||
int be_pid; /* PID of backend --- needed for cancels */
|
||||
|
@ -206,15 +206,17 @@ copy_connection(PGconn *conn)
|
||||
PQconninfoOption *opts = PQconninfo(conn);
|
||||
const char **keywords;
|
||||
const char **vals;
|
||||
int nopts = 1;
|
||||
int i = 0;
|
||||
int nopts = 0;
|
||||
int i;
|
||||
|
||||
for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
|
||||
nopts++;
|
||||
nopts++; /* for the NULL terminator */
|
||||
|
||||
keywords = pg_malloc(sizeof(char *) * nopts);
|
||||
vals = pg_malloc(sizeof(char *) * nopts);
|
||||
|
||||
i = 0;
|
||||
for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
|
||||
{
|
||||
if (opt->val)
|
||||
@ -1405,6 +1407,110 @@ test_prepared(PGconn *conn)
|
||||
fprintf(stderr, "ok\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Test max_protocol_version options.
|
||||
*/
|
||||
static void
|
||||
test_protocol_version(PGconn *conn)
|
||||
{
|
||||
const char **keywords;
|
||||
const char **vals;
|
||||
int nopts;
|
||||
PQconninfoOption *opts = PQconninfo(conn);
|
||||
int protocol_version;
|
||||
int max_protocol_version_index;
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Prepare keywords/vals arrays, copied from the existing connection, with
|
||||
* an extra slot for 'max_protocol_version'.
|
||||
*/
|
||||
nopts = 0;
|
||||
for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
|
||||
nopts++;
|
||||
nopts++; /* max_protocol_version */
|
||||
nopts++; /* NULL terminator */
|
||||
|
||||
keywords = pg_malloc0(sizeof(char *) * nopts);
|
||||
vals = pg_malloc0(sizeof(char *) * nopts);
|
||||
|
||||
i = 0;
|
||||
for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
|
||||
{
|
||||
if (opt->val)
|
||||
{
|
||||
keywords[i] = opt->keyword;
|
||||
vals[i] = opt->val;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
max_protocol_version_index = i;
|
||||
keywords[i] = "max_protocol_version"; /* value is filled in below */
|
||||
i++;
|
||||
keywords[i] = vals[i] = NULL;
|
||||
|
||||
/*
|
||||
* Test max_protocol_version=3.0
|
||||
*/
|
||||
vals[max_protocol_version_index] = "3.0";
|
||||
conn = PQconnectdbParams(keywords, vals, false);
|
||||
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
pg_fatal("Connection to database failed: %s",
|
||||
PQerrorMessage(conn));
|
||||
|
||||
protocol_version = PQfullProtocolVersion(conn);
|
||||
if (protocol_version != 30000)
|
||||
pg_fatal("expected 30000, got %d", protocol_version);
|
||||
|
||||
PQfinish(conn);
|
||||
|
||||
/*
|
||||
* Test max_protocol_version=3.1. It's not valid, we went straight from
|
||||
* 3.0 to 3.2.
|
||||
*/
|
||||
vals[max_protocol_version_index] = "3.1";
|
||||
conn = PQconnectdbParams(keywords, vals, false);
|
||||
|
||||
if (PQstatus(conn) != CONNECTION_BAD)
|
||||
pg_fatal("Connecting with max_protocol_version 3.1 should have failed.");
|
||||
|
||||
PQfinish(conn);
|
||||
|
||||
/*
|
||||
* Test max_protocol_version=3.2
|
||||
*/
|
||||
vals[max_protocol_version_index] = "3.2";
|
||||
conn = PQconnectdbParams(keywords, vals, false);
|
||||
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
pg_fatal("Connection to database failed: %s",
|
||||
PQerrorMessage(conn));
|
||||
|
||||
protocol_version = PQfullProtocolVersion(conn);
|
||||
if (protocol_version != 30002)
|
||||
pg_fatal("expected 30002, got %d", protocol_version);
|
||||
|
||||
PQfinish(conn);
|
||||
|
||||
/*
|
||||
* Test max_protocol_version=latest. 'latest' currently means '3.2'.
|
||||
*/
|
||||
vals[max_protocol_version_index] = "latest";
|
||||
conn = PQconnectdbParams(keywords, vals, false);
|
||||
|
||||
if (PQstatus(conn) != CONNECTION_OK)
|
||||
pg_fatal("Connection to database failed: %s",
|
||||
PQerrorMessage(conn));
|
||||
|
||||
protocol_version = PQfullProtocolVersion(conn);
|
||||
if (protocol_version != 30002)
|
||||
pg_fatal("expected 30002, got %d", protocol_version);
|
||||
|
||||
PQfinish(conn);
|
||||
}
|
||||
|
||||
/* Notice processor: print notices, and count how many we got */
|
||||
static void
|
||||
notice_processor(void *arg, const char *message)
|
||||
@ -2153,6 +2259,7 @@ print_test_list(void)
|
||||
printf("pipeline_idle\n");
|
||||
printf("pipelined_insert\n");
|
||||
printf("prepared\n");
|
||||
printf("protocol_version\n");
|
||||
printf("simple_pipeline\n");
|
||||
printf("singlerow\n");
|
||||
printf("transaction\n");
|
||||
@ -2263,6 +2370,8 @@ main(int argc, char **argv)
|
||||
test_pipelined_insert(conn, numrows);
|
||||
else if (strcmp(testname, "prepared") == 0)
|
||||
test_prepared(conn);
|
||||
else if (strcmp(testname, "protocol_version") == 0)
|
||||
test_protocol_version(conn);
|
||||
else if (strcmp(testname, "simple_pipeline") == 0)
|
||||
test_simple_pipeline(conn);
|
||||
else if (strcmp(testname, "singlerow") == 0)
|
||||
|
Reference in New Issue
Block a user