From 442f54a04fa814fb99f3bf46dc66060d1c58555e Mon Sep 17 00:00:00 2001 From: Badrul Chowdhury Date: Mon, 25 Sep 2017 13:42:44 -0700 Subject: [PATCH] =?UTF-8?q?Introducing=20pgwire=20v3.1:=201.=20adds=20supp?= =?UTF-8?q?ort=20for=20backend=20(BE)=20to=20accept=20optional=20parameter?= =?UTF-8?q?s,=20ie=20parameters=20that=20have=20=E2=80=9C=5Fpq=5F=E2=80=9D?= =?UTF-8?q?=20as=20a=20proper=20prefix=20in=20their=20names=202.=20creates?= =?UTF-8?q?=20data=20structure=20for=20passing=20in=20additional=20paramet?= =?UTF-8?q?ers=20in=20FE,=20adding=20command=20line=20support=20is=20out?= =?UTF-8?q?=20of=20scope=20of=20this=20item=203.=20enhances=20FE->BE=20pro?= =?UTF-8?q?tocol=20negotiation:=20adds=20support=20for=20newer=20FE=20to?= =?UTF-8?q?=20connect=20to=20older=20BE=20while=20maintaining=20back-compa?= =?UTF-8?q?tibility,=20ie=20same=20FE=20<->=20BE,=20older=20FE=20to=20newe?= =?UTF-8?q?r=20BE=20work=20as=20before=204.=20adds=20support=20for=20newer?= =?UTF-8?q?=20FE=20to=20connect=20to=20old=20BE=20that=20doesn't=20support?= =?UTF-8?q?=20protocol=20negotiation:=20FE=20downgrades=20to=20v3.0=20if?= =?UTF-8?q?=20sending=20startup=20parameters=20fails?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Makefile | 2 +- src/Makefile.global.in | 2 +- src/backend/postmaster/postmaster.c | 112 +++++++++++++++--- src/backend/utils/misc/guc.c | 22 +++- src/bin/pg_dump/pg_dump.c | 10 +- src/bin/scripts/clusterdb.c | 2 +- src/bin/scripts/createuser.c | 2 +- src/bin/scripts/reindexdb.c | 6 +- src/bin/scripts/vacuumdb.c | 4 +- src/common/Makefile | 2 +- src/fe_utils/Makefile | 2 +- src/fe_utils/simple_list.c | 4 + src/include/fe_utils/simple_list.h | 1 + src/include/libpq/pqcomm.h | 2 +- src/include/postmaster/postmaster.h | 7 ++ src/interfaces/libpq/fe-connect.c | 218 ++++++++++++++++++++++++++++++++++-- src/interfaces/libpq/fe-protocol3.c | 42 ++++++- src/interfaces/libpq/libpq-fe.h | 1 + src/interfaces/libpq/libpq-int.h | 10 +- src/tools/msvc/Mkvcbuild.pm | 2 +- 20 files changed, 404 insertions(+), 49 deletions(-) diff --git a/src/Makefile b/src/Makefile index 380da92c75..048d2d7ca8 100644 --- a/src/Makefile +++ b/src/Makefile @@ -20,10 +20,10 @@ SUBDIRS = \ backend/utils/mb/conversion_procs \ backend/snowball \ include \ + fe_utils \ interfaces \ backend/replication/libpqwalreceiver \ backend/replication/pgoutput \ - fe_utils \ bin \ pl \ makefiles \ diff --git a/src/Makefile.global.in b/src/Makefile.global.in index e8b3a519cb..c3b45ce650 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -480,7 +480,7 @@ endif # This macro is for use by libraries linking to libpq. (Because libpgport # isn't created with the same link flags as libpq, it can't be used.) -libpq = -L$(libpq_builddir) -lpq +libpq = -L$(libpq_builddir) -lpq -L$(top_builddir)/src/common -lpgcommon -L$(top_builddir)/src/fe_utils -lpgfeutils # This macro is for use by client executables (not libraries) that use libpq. # We force clients to pull symbols from the non-shared libraries libpgport diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 95180b2ef5..21d77333be 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -103,6 +103,7 @@ #include "lib/ilist.h" #include "libpq/auth.h" #include "libpq/libpq.h" +#include "libpq/pqformat.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "pg_getopt.h" @@ -567,6 +568,77 @@ HANDLE PostmasterHandle; #endif /* + * Initiate protocol negotiation phase; + * protocol negotiation is only supported for pgwire version 3.x, x>0. + * + * Ensure that the packet write buffer is flushed. + */ +int +NegotiateServerProtocol(Port *port) +{ + if (whereToSendOutput != DestRemote || + PG_PROTOCOL_MAJOR(FrontendProtocol) < 3) + return -1; + + int sendStatus = 0; + + /* NegotiateServerProtocol packet structure + * + * [ 'Y' | msgLength | min_version | max_version | param_list_len | list of param names ] + */ + + sendStatus = SendServerProtocolVersionMessage(port); + + /* Ensure that the message buffer is flushed */ + pq_flush(); + + return sendStatus; +} + +/* + * Construct and send a ServerProtocolVersion message. + * + * Message contains: + * 1. minimum, maximum versions supported by the BE, + * 2. number of parameters that were honored by the BE from startup packet, + * 3. a list of strings consisting of the parameter names accepted by BE. + */ +int +SendServerProtocolVersionMessage(Port *port) +{ + StringInfoData buf; + + /* PG message type*/ + pq_beginmessage(&buf, 'Y'); + + /* Protocol version numbers */ + pq_sendint(&buf, PG_PROTOCOL_EARLIEST, sizeof(int32)); /* min */ + pq_sendint(&buf, PG_PROTOCOL_LATEST, sizeof(int32)); /* max */ + + /* Length of parameter list; parameter list consists of (key, value) pairs */ + pq_sendint(&buf, list_length(port->guc_options) / 2, sizeof(int32)); + + ListCell *gucopts = list_head(port->guc_options); + while (gucopts) + { + char *name; + + /* First comes key, which we need. */ + name = lfirst(gucopts); + gucopts = lnext(gucopts); + + /* Then comes value, which we don't need. */ + gucopts = lnext(gucopts); + + pq_sendstring(&buf, name); + } + + pq_endmessage(&buf); + + return 0; +} + +/* * Postmaster main entry point */ void @@ -2050,20 +2122,6 @@ retry1: */ FrontendProtocol = proto; - /* Check we can handle the protocol the frontend is using. */ - - if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) || - PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) || - (PG_PROTOCOL_MAJOR(proto) == PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) && - PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))) - ereport(FATAL, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u", - PG_PROTOCOL_MAJOR(proto), PG_PROTOCOL_MINOR(proto), - PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST), - PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST), - PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)))); - /* * Now fetch parameters out of startup packet and save them into the Port * structure. All data structures attached to the Port struct must be @@ -2145,9 +2203,35 @@ retry1: ereport(FATAL, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("invalid startup packet layout: expected terminator as last byte"))); + + /* + * Need to negotiate pgwire protocol if + * 1. FE version is not the same as BE version + * 2. FE version is not 3.0 + */ + if (FrontendProtocol != PG_PROTOCOL_LATEST + && FrontendProtocol != PG_PROTOCOL(3, 0)) + { + /* Negotiate parameters after all the error-checking is done */ + if (NegotiateServerProtocol(port)) + return STATUS_ERROR; + } } else { + /* Check we can handle the protocol the frontend is using. */ + if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) || + PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) || + (PG_PROTOCOL_MAJOR(proto) == PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) && + PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))) + ereport(FATAL, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u", + PG_PROTOCOL_MAJOR(proto), PG_PROTOCOL_MINOR(proto), + PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST), + PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST), + PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)))); + /* * Get the parameters from the old-style, fixed-width-fields startup * packet as C strings. The packet destination was cleared first so a diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 246fea8693..69441f3f86 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -3961,6 +3961,7 @@ static int GUCNestLevel = 0; /* 1 when in main transaction */ static int guc_var_compare(const void *a, const void *b); static int guc_name_compare(const char *namea, const char *nameb); +static bool is_optional(const char *guc_name); static void InitializeGUCOptionsFromEnvironment(void); static void InitializeOneGUCOption(struct config_generic *gconf); static void push_old_value(struct config_generic *gconf, GucAction action); @@ -4416,7 +4417,7 @@ find_option(const char *name, bool create_placeholders, int elevel) /* * Check if the name is qualified, and if so, add a placeholder. */ - if (strchr(name, GUC_QUALIFIER_SEPARATOR) != NULL) + if (strchr(name, GUC_QUALIFIER_SEPARATOR) != NULL || is_optional(name)) return add_placeholder_variable(name, elevel); } @@ -4467,6 +4468,25 @@ guc_name_compare(const char *namea, const char *nameb) return 0; } +/* + * A GUC variable is deemed optional if the name contains "_pq_" as a proper prefix. + * + * It takes the whole struct as input in case we want to move away from name-based + * tagging of optional variables. + */ +bool +is_optional(const char *guc_name) +{ + const char *optionalPrefix = "_pq_"; + bool isOptional = false; + + /* "_pq_" must be a proper prefix of the guc name in all encodings */ + if (guc_name_compare(guc_name, optionalPrefix) == 1 && + strstr(guc_name, optionalPrefix)) + isOptional = true; + + return isOptional; +} /* * Initialize GUC options during program startup. diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 75f08cd792..9d3feb0a0a 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -110,16 +110,16 @@ static int strict_names = 0; * The string lists record the patterns given by command-line switches, * which we then convert to lists of OIDs of matching objects. */ -static SimpleStringList schema_include_patterns = {NULL, NULL}; +static SimpleStringList schema_include_patterns = {NULL, NULL, NULL}; static SimpleOidList schema_include_oids = {NULL, NULL}; -static SimpleStringList schema_exclude_patterns = {NULL, NULL}; +static SimpleStringList schema_exclude_patterns = {NULL, NULL, NULL}; static SimpleOidList schema_exclude_oids = {NULL, NULL}; -static SimpleStringList table_include_patterns = {NULL, NULL}; +static SimpleStringList table_include_patterns = {NULL, NULL, NULL}; static SimpleOidList table_include_oids = {NULL, NULL}; -static SimpleStringList table_exclude_patterns = {NULL, NULL}; +static SimpleStringList table_exclude_patterns = {NULL, NULL, NULL}; static SimpleOidList table_exclude_oids = {NULL, NULL}; -static SimpleStringList tabledata_exclude_patterns = {NULL, NULL}; +static SimpleStringList tabledata_exclude_patterns = {NULL, NULL, NULL}; static SimpleOidList tabledata_exclude_oids = {NULL, NULL}; diff --git a/src/bin/scripts/clusterdb.c b/src/bin/scripts/clusterdb.c index a6640aa57b..79fa46afd2 100644 --- a/src/bin/scripts/clusterdb.c +++ b/src/bin/scripts/clusterdb.c @@ -60,7 +60,7 @@ main(int argc, char *argv[]) bool quiet = false; bool alldb = false; bool verbose = false; - SimpleStringList tables = {NULL, NULL}; + SimpleStringList tables = {NULL, NULL, NULL}; progname = get_progname(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts")); diff --git a/src/bin/scripts/createuser.c b/src/bin/scripts/createuser.c index 0e36edcc5d..ff33ec63c3 100644 --- a/src/bin/scripts/createuser.c +++ b/src/bin/scripts/createuser.c @@ -58,7 +58,7 @@ main(int argc, char *argv[]) char *host = NULL; char *port = NULL; char *username = NULL; - SimpleStringList roles = {NULL, NULL}; + SimpleStringList roles = {NULL, NULL, NULL}; enum trivalue prompt_password = TRI_DEFAULT; bool echo = false; bool interactive = false; diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c index ffd611e7bb..2aede05156 100644 --- a/src/bin/scripts/reindexdb.c +++ b/src/bin/scripts/reindexdb.c @@ -68,9 +68,9 @@ main(int argc, char *argv[]) bool echo = false; bool quiet = false; bool verbose = false; - SimpleStringList indexes = {NULL, NULL}; - SimpleStringList tables = {NULL, NULL}; - SimpleStringList schemas = {NULL, NULL}; + SimpleStringList indexes = {NULL, NULL, NULL}; + SimpleStringList tables = {NULL, NULL, NULL}; + SimpleStringList schemas = {NULL, NULL, NULL}; progname = get_progname(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts")); diff --git a/src/bin/scripts/vacuumdb.c b/src/bin/scripts/vacuumdb.c index 5d2869ea6b..df77eccf5f 100644 --- a/src/bin/scripts/vacuumdb.c +++ b/src/bin/scripts/vacuumdb.c @@ -123,7 +123,7 @@ main(int argc, char *argv[]) vacuumingOptions vacopts; bool analyze_in_stages = false; bool alldb = false; - SimpleStringList tables = {NULL, NULL}; + SimpleStringList tables = {NULL, NULL, NULL}; int concurrentCons = 1; int tbl_count = 0; @@ -342,7 +342,7 @@ vacuum_one_database(const char *dbname, vacuumingOptions *vacopts, PGconn *conn; SimpleStringListCell *cell; ParallelSlot *slots = NULL; - SimpleStringList dbtables = {NULL, NULL}; + SimpleStringList dbtables = {NULL, NULL, NULL}; int i; bool failed = false; bool parallel = concurrentCons > 1; diff --git a/src/common/Makefile b/src/common/Makefile index 80e78d72fe..fa29c3e0ac 100644 --- a/src/common/Makefile +++ b/src/common/Makefile @@ -24,7 +24,7 @@ subdir = src/common top_builddir = ../.. include $(top_builddir)/src/Makefile.global -override CPPFLAGS := -DFRONTEND $(CPPFLAGS) +override CPPFLAGS := -DFRONTEND $(CPPFLAGS) -fPIC LIBS += $(PTHREAD_LIBS) # don't include subdirectory-path-dependent -I and -L switches diff --git a/src/fe_utils/Makefile b/src/fe_utils/Makefile index ebce38ceb4..7706bc5fa9 100644 --- a/src/fe_utils/Makefile +++ b/src/fe_utils/Makefile @@ -17,7 +17,7 @@ subdir = src/fe_utils top_builddir = ../.. include $(top_builddir)/src/Makefile.global -override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS) +override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS) -fPIC OBJS = mbprint.o print.o psqlscan.o simple_list.o string_utils.o diff --git a/src/fe_utils/simple_list.c b/src/fe_utils/simple_list.c index 21a2e57297..ed67f254f3 100644 --- a/src/fe_utils/simple_list.c +++ b/src/fe_utils/simple_list.c @@ -64,6 +64,10 @@ simple_string_list_append(SimpleStringList *list, const char *val) { SimpleStringListCell *cell; + /* Cannot append to immutable list */ + if (list->is_immutable) + return; + cell = (SimpleStringListCell *) pg_malloc(offsetof(SimpleStringListCell, val) + strlen(val) + 1); diff --git a/src/include/fe_utils/simple_list.h b/src/include/fe_utils/simple_list.h index 97bb34f191..ea5e9af7b4 100644 --- a/src/include/fe_utils/simple_list.h +++ b/src/include/fe_utils/simple_list.h @@ -41,6 +41,7 @@ typedef struct SimpleStringList { SimpleStringListCell *head; SimpleStringListCell *tail; + bool is_immutable; } SimpleStringList; diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 10c7434c41..3a8ea400ac 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -108,7 +108,7 @@ typedef struct /* The earliest and latest frontend/backend protocol version supported. */ #define PG_PROTOCOL_EARLIEST PG_PROTOCOL(2,0) -#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,0) +#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,1) typedef uint32 ProtocolVersion; /* FE/BE protocol version number */ diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h index 0f85908b09..da0a3a79c3 100644 --- a/src/include/postmaster/postmaster.h +++ b/src/include/postmaster/postmaster.h @@ -13,6 +13,10 @@ #ifndef _POSTMASTER_H #define _POSTMASTER_H +#include "postgres.h" + +#include "libpq/libpq-be.h" + /* GUC options */ extern bool EnableSSL; extern int ReservedBackends; @@ -46,6 +50,9 @@ extern int postmaster_alive_fds[2]; extern const char *progname; +extern int NegotiateServerProtocol(Port *port); +extern int SendServerProtocolVersionMessage(Port *port); + extern void PostmasterMain(int argc, char *argv[]) pg_attribute_noreturn(); extern void ClosePostmasterPorts(bool am_syslogger); diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index d0e97ecdd4..db22fca57a 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -86,8 +86,11 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, * application_name in a startup packet. We hard-wire the value rather * than looking into errcodes.h since it reflects historical behavior * rather than that of the current code. + * + * Note that application_name is a startup parameter. + * The same error code is used for all unrecognized startup parameters. */ -#define ERRCODE_APPNAME_UNKNOWN "42704" +#define ERRCODE_PARAMETER_UNKNOWN "42704" /* This is part of the protocol so just define it */ #define ERRCODE_INVALID_PASSWORD "28P01" @@ -350,6 +353,11 @@ static const PQEnvironmentOption EnvironmentOptions[] = static const char uri_designator[] = "postgresql://"; static const char short_uri_designator[] = "postgres://"; +static const char *optional_parameter_prefix = "_pq_"; + +/* A list of string options to add as startup options */ +static SimpleStringList startup_parameters = {NULL, NULL, NULL}; + static bool connectOptions1(PGconn *conn, const char *conninfo); static bool connectOptions2(PGconn *conn); static int connectDBStart(PGconn *conn); @@ -1803,8 +1811,9 @@ connectDBStart(PGconn *conn) */ conn->whichhost = 0; conn->addr_cur = conn->connhost[0].addrlist; - conn->pversion = PG_PROTOCOL(3, 0); + conn->pversion = PG_PROTOCOL(3, 1); conn->send_appname = true; + conn->send_startup_params = true; conn->status = CONNECTION_NEEDED; /* @@ -2001,6 +2010,9 @@ PQconnectPoll(PGconn *conn) int optval; PQExpBufferData savedMessage; + /* Flag to check if newer FE is connecting to older BE. */ + bool server_is_older = false; + if (conn == NULL) return PGRES_POLLING_FAILED; @@ -2018,6 +2030,7 @@ PQconnectPoll(PGconn *conn) /* These are reading states */ case CONNECTION_AWAITING_RESPONSE: + case CONNECTION_NEGOTIATING: case CONNECTION_AUTH_OK: { /* Load waiting data */ @@ -2462,7 +2475,8 @@ keep_going: /* We will come back to here until there is */ if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) startpacket = pqBuildStartupPacket3(conn, &packetlen, - EnvironmentOptions); + EnvironmentOptions, + &startup_parameters); else startpacket = pqBuildStartupPacket2(conn, &packetlen, EnvironmentOptions); @@ -2649,6 +2663,12 @@ keep_going: /* We will come back to here until there is return PGRES_POLLING_READING; } + if (beresp == 'Y') + { + conn->status = CONNECTION_NEGOTIATING; + goto keep_going; + } + /* * Validate message type: we expect only an authentication * request or an error here. Anything else probably means @@ -2716,10 +2736,19 @@ keep_going: /* We will come back to here until there is appendPQExpBufferChar(&conn->errorMessage, '\n'); /* - * If we tried to open the connection in 3.0 protocol, - * fall back to 2.0 protocol. + * If we tried to open the connection in 3.1 protocol, + * fall back to 3.0 protocol; fall back to 2.0 from 3.0. */ - if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) + if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3 && + PG_PROTOCOL_MINOR(conn->pversion) >= 1) + { + conn->pversion = PG_PROTOCOL(3, 0); + /* Must drop the old connection */ + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } + else if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) { conn->pversion = PG_PROTOCOL(2, 0); /* Must drop the old connection */ @@ -2893,6 +2922,161 @@ keep_going: /* We will come back to here until there is goto keep_going; } + case CONNECTION_NEGOTIATING: + { + int minServerVersion; /* Min pgwire protocol supported by server */ + int maxServerVersion; /* Max pgwire protocol supported by server */ + int param_list_len; /* Length of list of parameters honored by server */ + int proper_prefix_len; /* Length of required prefix in optional parameter name */ + int i; /* Index for loop */ + PQExpBuffer buf; /* Buffer for data */ + char *rejected_param; /* Parameter that was rejected by the BE */ + int originalMsgLen; /* Length of message sans msg type */ + int runningMsgLength; /* Copy of originalMsgLen, will not be preserved */ + int available; /* Bytes available in message body */ + + /* Mark 'Y' as consumed: 1 byte for message type */ + conn->inCursor = conn->inStart + 1; + + /* + * Block until message length is read. + * + * No need to account for 2.x fixed-length message because this state cannot + * be reached by pre-3.0 server. + */ + if (pqGetInt(&originalMsgLen, sizeof(int32), conn)) + return PGRES_POLLING_READING; + + runningMsgLength = originalMsgLen; + + /* Block until each of the fields in the packet is read */ + if (pqGetInt(&minServerVersion, sizeof(int32), conn) || + pqGetInt(&maxServerVersion, sizeof(int32), conn) || + pqGetInt(¶m_list_len, sizeof(int32), conn)) + { + return PGRES_POLLING_READING; + } + + /* 4 bytes each for msgLength, min, max, and length of list of param names */ + runningMsgLength -= sizeof(int32) * 4; + + /* Create empty buffer */ + buf = createPQExpBuffer(); + for (i = 0; i < param_list_len; ++i) + { + if (pqGets(buf, conn)) + { + available = conn->inEnd - conn->inCursor; + if (available < runningMsgLength) + { + /* Enlarge buffer if required */ + if (pqCheckInBufferSpace(conn->inCursor + (size_t)runningMsgLength, conn)) + goto error_return; + + return PGRES_POLLING_READING; + } + else + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("encountered error while attempting " + "to read parameter name from connection\n")); + /* Free buffer */ + destroyPQExpBuffer(buf); + + goto error_return; + } + } + + /* Read string successfully, decrement message length to reflect this */ + runningMsgLength -= buf->len + 1; /* +1 for NULL */ + + simple_string_list_member(&startup_parameters, buf->data); + + /* pqGets() resets the buffer, so buffer is clean for next iteration */ + } + + /* Free buffer */ + destroyPQExpBuffer(buf); + + /* Check for extraneous data in packet, +1 to account for message type char */ + if (conn->inCursor != conn->inStart + 1 + originalMsgLen) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("Extraneous data in ServerProtocolMessage packet\n")); + goto error_return; + } + + /* Mark read data consumed */ + conn->inStart = conn->inCursor; + + /* + * Emit error if FE is older than min supported by BE. + * + * This check will be effective once the minimum BE version is >= 3.0; + * otherwise, older FEs will be turned away when parsing startup packet. + */ + if (PG_PROTOCOL_MAJOR(conn->pversion) < PG_PROTOCOL_MAJOR(minServerVersion) || + (PG_PROTOCOL_MAJOR(conn->pversion) == PG_PROTOCOL_MAJOR(minServerVersion) && + PG_PROTOCOL_MINOR(conn->pversion) < PG_PROTOCOL_MINOR(minServerVersion))) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("unsupported frontend protocol %u.%u: " + "server supports %u.%u to %u.%u"), + PG_PROTOCOL_MAJOR(conn->pversion), + PG_PROTOCOL_MINOR(conn->pversion), + PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST), + PG_PROTOCOL_MINOR(PG_PROTOCOL_EARLIEST), + PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST), + PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)); + goto error_return; + } + + /* Set flag to enable parameter check if newer FE is connecting to older BE + * In this case, check if the non-optional parameters sent + * in StartupMsg by FE were accepted by the BE. + * + * If the FE version is the same as BE or it falls in the [min, max] + * range of BE, all the parameters sent by FE should be accepted by BE, + * so skip the accepted parameter check. + */ + else if (PG_PROTOCOL_MAJOR(conn->pversion) > PG_PROTOCOL_MAJOR(maxServerVersion) || + (PG_PROTOCOL_MAJOR(conn->pversion) == PG_PROTOCOL_MAJOR(maxServerVersion) && + PG_PROTOCOL_MINOR(conn->pversion) > PG_PROTOCOL_MINOR(maxServerVersion))) + { + server_is_older = true; + } + + /* + * Check whether all required parameters sent by newer FE were accepted by older BE. + * Do not error out if optional parameters were rejected by the BE. + */ + while (server_is_older && (rejected_param = simple_string_list_not_touched(&startup_parameters)) != NULL) + { + /* + * Terminate connection if the rejected parameter is not optional. + * This is not encoding-aware, which is okay because it is all on client-side + * optional_parameter_prefix must be a proper prefix of the rejected paramteter's name. + */ + proper_prefix_len = strlen(optional_parameter_prefix); + if (strlen(rejected_param) <= proper_prefix_len || + strncmp(rejected_param, optional_parameter_prefix, proper_prefix_len) != 0) + { + appendPQExpBuffer(&conn->errorMessage, + libpq_gettext("Non-optional startup parameter \'%s\'" + "was rejected by server\n"), + rejected_param); + goto error_return; + + } + + /* Mark member as visited */ + simple_string_list_member(&startup_parameters, rejected_param); + } + + conn->status = CONNECTION_AWAITING_RESPONSE; + goto keep_going; + } + case CONNECTION_AUTH_OK: { /* @@ -2921,8 +3105,9 @@ keep_going: /* We will come back to here until there is if (res->resultStatus != PGRES_FATAL_ERROR) appendPQExpBufferStr(&conn->errorMessage, libpq_gettext("unexpected message from server during startup\n")); - else if (conn->send_appname && - (conn->appname || conn->fbappname)) + else if ((conn->send_appname && + (conn->appname || conn->fbappname)) || + (conn->send_startup_params && startup_parameters.head)) { /* * If we tried to send application_name, check to see @@ -2938,10 +3123,23 @@ keep_going: /* We will come back to here until there is sqlstate = PQresultErrorField(res, PG_DIAG_SQLSTATE); if (sqlstate && - strcmp(sqlstate, ERRCODE_APPNAME_UNKNOWN) == 0) + strcmp(sqlstate, ERRCODE_PARAMETER_UNKNOWN) == 0) { PQclear(res); - conn->send_appname = false; + + if (conn->send_startup_params && startup_parameters.head) + { + conn->send_startup_params = false; + /* + * Sending optional parameters is the only FE feature of pgwire v3.1; + * downgrade to v3.0 if this failed. + */ + conn->pversion = PG_PROTOCOL(3, 0); + } + + if (conn->send_appname && (conn->appname || conn->fbappname)) + conn->send_appname = false; + /* Must drop the old connection */ pqDropConnection(conn, true); conn->status = CONNECTION_NEEDED; diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index a484fe80a1..7f5c74b11f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -13,6 +13,7 @@ *------------------------------------------------------------------------- */ #include "postgres_fe.h" +#include "fe_utils/simple_list.h" #include #include @@ -54,7 +55,8 @@ static int getReadyForQuery(PGconn *conn); static void reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding); static int build_startup_packet(const PGconn *conn, char *packet, - const PQEnvironmentOption *options); + const PQEnvironmentOption *options, + SimpleStringList *startup_parameters); /* @@ -2116,15 +2118,16 @@ pqFunctionCall3(PGconn *conn, Oid fnid, */ char * pqBuildStartupPacket3(PGconn *conn, int *packetlen, - const PQEnvironmentOption *options) + const PQEnvironmentOption *options, + SimpleStringList *startup_parameters) { char *startpacket; - *packetlen = build_startup_packet(conn, NULL, options); + *packetlen = build_startup_packet(conn, NULL, options, startup_parameters); startpacket = (char *) malloc(*packetlen); if (!startpacket) return NULL; - *packetlen = build_startup_packet(conn, startpacket, options); + *packetlen = build_startup_packet(conn, startpacket, options, startup_parameters); return startpacket; } @@ -2139,11 +2142,15 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen, */ static int build_startup_packet(const PGconn *conn, char *packet, - const PQEnvironmentOption *options) + const PQEnvironmentOption *options, + SimpleStringList *startup_parameters) { int packet_len = 0; const PQEnvironmentOption *next_eo; const char *val; + int nCells = 0; + char *name, *value; + SimpleStringListCell *pCurr; /* Protocol version comes first. */ if (packet) @@ -2195,6 +2202,31 @@ build_startup_packet(const PGconn *conn, char *packet, } } + if (conn->send_startup_params && startup_parameters->head) + { + for (pCurr = startup_parameters->head; pCurr != NULL; pCurr = pCurr->next, ++nCells) + { + if ((nCells % 2) == 0) + name = pCurr->val; + else + { + value = pCurr->val; + ADD_STARTUP_OPTION(name, value); + + /* Mark value consumed */ + pCurr->touched = true; + } + } + + if ((nCells % 2) != 0) + { + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("at least one parameter specified in" + "StartupMessage does not have a value\n")); + pqSaveErrorResult(conn); + } + } + /* Add trailing terminator */ if (packet) packet[packet_len] = '\0'; diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 1d915e7915..d04f48544c 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -58,6 +58,7 @@ typedef enum CONNECTION_MADE, /* Connection OK; waiting to send. */ CONNECTION_AWAITING_RESPONSE, /* Waiting for a response from the * postmaster. */ + CONNECTION_NEGOTIATING, /* Negotiating pgwire protocol between FE/BE */ CONNECTION_AUTH_OK, /* Received authentication; waiting for * backend startup. */ CONNECTION_SETENV, /* Negotiating environment. */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 42913604e3..4a1839186f 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -43,6 +43,12 @@ /* include stuff found in fe only */ #include "pqexpbuffer.h" +/* + * Simple string list data structure to track + * startup parameters that were accepted. + */ +#include "fe_utils/simple_list.h" + #ifdef ENABLE_GSS #if defined(HAVE_GSSAPI_H) #include @@ -414,6 +420,7 @@ struct pg_conn PGSetenvStatusType setenv_state; /* for 2.0 protocol only */ const PQEnvironmentOption *next_eo; bool send_appname; /* okay to send application_name? */ + bool send_startup_params; /* okay to send startup parameters? */ /* Miscellaneous stuff */ int be_pid; /* PID of backend --- needed for cancels */ @@ -598,7 +605,8 @@ extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid, /* === in fe-protocol3.c === */ extern char *pqBuildStartupPacket3(PGconn *conn, int *packetlen, - const PQEnvironmentOption *options); + const PQEnvironmentOption *options, + SimpleStringList *parameters); extern void pqParseInput3(PGconn *conn); extern int pqGetErrorNotice3(PGconn *conn, bool isError); extern void pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res, diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm index 686c7369f6..a440ca303f 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -234,7 +234,7 @@ sub mkvcbuild $libpq->UseDef('src/interfaces/libpq/libpqdll.def'); $libpq->ReplaceFile('src/interfaces/libpq/libpqrc.c', 'src/interfaces/libpq/libpq.rc'); - $libpq->AddReference($libpgport); + $libpq->AddReference($libpgcommon, $libpgfeutils, $libpgport); # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if -- 2.13.2.windows.1