diff --git a/configure b/configure index 30d949c3c466..1e18a5a22b08 100755 --- a/configure +++ b/configure @@ -15927,6 +15927,16 @@ fi cat >>confdefs.h <<_ACEOF #define HAVE_DECL_STRSEP $ac_have_decl _ACEOF +ac_fn_c_check_decl "$LINENO" "timingsafe_bcmp" "ac_cv_have_decl_timingsafe_bcmp" "$ac_includes_default" +if test "x$ac_cv_have_decl_timingsafe_bcmp" = xyes; then : + ac_have_decl=1 +else + ac_have_decl=0 +fi + +cat >>confdefs.h <<_ACEOF +#define HAVE_DECL_TIMINGSAFE_BCMP $ac_have_decl +_ACEOF # We can't use AC_CHECK_FUNCS to detect these functions, because it @@ -16087,6 +16097,19 @@ esac fi +ac_fn_c_check_func "$LINENO" "timingsafe_bcmp" "ac_cv_func_timingsafe_bcmp" +if test "x$ac_cv_func_timingsafe_bcmp" = xyes; then : + $as_echo "#define HAVE_TIMINGSAFE_BCMP 1" >>confdefs.h + +else + case " $LIBOBJS " in + *" timingsafe_bcmp.$ac_objext "* ) ;; + *) LIBOBJS="$LIBOBJS timingsafe_bcmp.$ac_objext" + ;; +esac + +fi + ac_fn_c_check_func "$LINENO" "pthread_barrier_wait" "ac_cv_func_pthread_barrier_wait" diff --git a/configure.ac b/configure.ac index 25cdfcf65af7..440640febcc2 100644 --- a/configure.ac +++ b/configure.ac @@ -1806,7 +1806,7 @@ AC_CHECK_DECLS(posix_fadvise, [], [], [#include ]) ]) # fi AC_CHECK_DECLS(fdatasync, [], [], [#include ]) -AC_CHECK_DECLS([strlcat, strlcpy, strnlen, strsep]) +AC_CHECK_DECLS([strlcat, strlcpy, strnlen, strsep, timingsafe_bcmp]) # We can't use AC_CHECK_FUNCS to detect these functions, because it # won't handle deployment target restrictions on macOS @@ -1826,6 +1826,7 @@ AC_REPLACE_FUNCS(m4_normalize([ strlcpy strnlen strsep + timingsafe_bcmp ])) AC_REPLACE_FUNCS(pthread_barrier_wait) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index b359fbff295b..41e898e7a388 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2144,6 +2144,56 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname + + min_protocol_version + + + Specifies the minimum protocol version to allow for the connection. + The default is to allow any version of the + PostgreSQL protocol supported by libpq, + which currently means 3.0. If the server + does not support at least this protocol version the connection will be + closed. + + + + The current supported values are + 3.0, + 3.2, + and latest. The latest value is + equivalent to the latest protocol version that is supported by the used + libpq version, which currently is 3.2. + + + + + + max_protocol_version + + + Specifies the protocol version to request from the server. + The default is to use version 3.0 of the + PostgreSQL protocol, unless the connection + string specifies a feature that relies on a higher protocol version, + in which case the latest version supported by libpq is used. If the + server does not support the protocol version requested by the client, + the connection is automatically downgraded to a lower minor protocol + version that the server supports. After the connection attempt has + completed you can use to + find out which exact protocol version was negotiated. + + + + The current supported values are + 3.0, + 3.2, + and latest. The latest value is + equivalent to the latest protocol version that is supported by the used + libpq version, which currently is 3.2. + + + + ssl_max_protocol_version @@ -2482,7 +2532,6 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname - @@ -9329,6 +9378,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough) linkend="libpq-connect-load-balance-hosts"/> connection parameter. + + + + + PGMINPROTOCOLVERSION + + PGMINPROTOCOLVERSION behaves the same as the connection parameter. + + + + + + + PGMAXPROTOCOLVERSION + + PGMAXPROTOCOLVERSION behaves the same as the connection parameter. + + diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 04d8e7d21af9..4c80c456e418 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -18,22 +18,10 @@ - This document describes version 3.0 of the protocol, implemented in - PostgreSQL 7.4 and later. For descriptions - of the earlier protocol versions, see previous releases of the - PostgreSQL documentation. A single server - can support multiple protocol versions. The initial startup-request - message tells the server which protocol version the client is attempting to - use. If the major version requested by the client is not supported by - the server, the connection will be rejected (for example, this would occur - if the client requested protocol version 4.0, which does not exist as of - this writing). If the minor version requested by the client is not - supported by the server (e.g., the client requests version 3.1, but the - server supports only 3.0), the server may either reject the connection or - may respond with a NegotiateProtocolVersion message containing the highest - minor protocol version which it supports. The client may then choose either - to continue with the connection using the specified protocol version or - to abort the connection. + This document describes version 3.2 of the protocol, introduced in + PostgreSQL version 17. The server and the libpq + client library is compatible with protocol version 3.0, implemented in + PostgreSQL 7.4 and later. @@ -199,6 +187,85 @@ server versions; the text format is usually the more portable choice. + + + Protocol versions + + + The current, latest version of the protocol is version 3.2. For backwards + compatibility with old server versions and middleware that don't support + the version negotiation yet, the libpq library still uses protocol version + 3.0 by default however. + + + + A single server can support multiple protocol versions. The initial + startup-request message tells the server which protocol version the client + is attempting to use. If the major version requested by the client is not + supported by the server, the connection will be rejected (for example, + this would occur if the client requested protocol version 4.0, which does + not exist as of this writing). If the minor version requested by the + client is not supported by the server (e.g., the client requests version + 3.2, but the server supports only 3.0), the server may either reject the + connection or may respond with a NegotiateProtocolVersion message + containing the highest minor protocol version which it supports. The + client may then choose either to continue with the connection using the + specified protocol version or to abort the connection. + + + + The protocol negotiation was introduced in + PostgreSQL version 9.3.21. Earlier versions + would reject the connection if the client requested a minor version that + was not supported by the server. + + + + Protocol versions + + + + + Version + Supported by + Description + + + + + + 3.2 + PostgreSQL 18 and later + Current latest version. The secret key used in query + cancellation was enlarged from 4 bytes to a variable length field. The + BackendKeyData message was changed to accomodate that, and the CancelRequest + message was redefined to have a variable length payload. + + + + 3.1 + - + Reserved. Version 3.1 has not been used by any PostgreSQL + version, but it was skipped because old versions of the popular + pgbouncer application had a bug in the protocol negotiation which made + it incorrectly claim that it supported version 3.1. + + + + 3.0 + PostgreSQL 7.4 and later + + + 2.0 + up to PostgreSQL 13 + See previous releases of + the PostgreSQL documentation for + details + + + +
+
@@ -413,8 +480,14 @@ this message indicates the highest supported minor version. This message will also be sent if the client requested unsupported protocol options (i.e., beginning with _pq_.) in the - startup packet. This message will be followed by an ErrorResponse or - a message indicating the success or failure of authentication. + startup packet. + + + After this message, the authentication will continue using the version + indicated by the server. If the client does not support the older + version, it should immediately close the connection. If the server + does not send this message, it supports the client's requested + protocol version and all the protocol options. @@ -3624,10 +3697,10 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" indicate that it can be sent by a frontend (F), a backend (B), or both (F & B). Notice that although each message includes a byte count at the beginning, - the message format is defined so that the message end can be found without - reference to the byte count. This aids validity checking. (The CopyData - message is an exception, because it forms part of a data stream; the contents - of any individual CopyData message cannot be interpretable on their own.) + most messages are defined so that the message end can be found without + reference to the byte count. This is mostly for historical reasons, as + the original, now-obsolete protocol version 2 did not have an explicit length + field, but it also aids validity checking. @@ -4043,7 +4116,7 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Int32(12) + Int32 Length of message contents in bytes, including self. @@ -4061,14 +4134,18 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Int32 + Byten - The secret key of this backend. + The secret key of this backend. This field extends to the end of the + message, indicated by the length field. The maximum key length is 256 bytes. + + Before protocol version 3.2, the secret key was always 4 bytes long. + @@ -4274,14 +4351,18 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" - Int32 + Byten - The secret key for the target backend. + The secret key for the target backend. This field extends to the end of the + message, indicated by the length field. The maximum key length is 256 bytes. + + Before protocol version 3.2, the secret key was always 4 bytes long. + diff --git a/meson.build b/meson.build index b8da4966297d..eee744b3abe8 100644 --- a/meson.build +++ b/meson.build @@ -2569,6 +2569,7 @@ decl_checks = [ ['strlcpy', 'string.h'], ['strnlen', 'string.h'], ['strsep', 'string.h'], + ['timingsafe_bcmp', 'string.h'], ] # Need to check for function declarations for these functions, because @@ -2811,6 +2812,7 @@ func_checks = [ ['strsignal'], ['sync_file_range'], ['syncfs'], + ['timingsafe_bcmp'], ['uselocale'], ['wcstombs_l'], ] diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index 7d201965503c..b7c39a4c5f0c 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -63,8 +63,8 @@ typedef struct { pg_atomic_uint32 pss_pid; - bool pss_cancel_key_valid; - int32 pss_cancel_key; + int pss_cancel_key_len; /* 0 means no cancellation is possible */ + char pss_cancel_key[MAX_CANCEL_KEY_LENGTH]; volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS]; slock_t pss_mutex; /* protects the above fields */ @@ -148,8 +148,7 @@ ProcSignalShmemInit(void) SpinLockInit(&slot->pss_mutex); pg_atomic_init_u32(&slot->pss_pid, 0); - slot->pss_cancel_key_valid = false; - slot->pss_cancel_key = 0; + slot->pss_cancel_key_len = 0; MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags)); pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX); pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0); @@ -163,12 +162,13 @@ ProcSignalShmemInit(void) * Register the current process in the ProcSignal array */ void -ProcSignalInit(bool cancel_key_valid, int32 cancel_key) +ProcSignalInit(char *cancel_key, int cancel_key_len) { ProcSignalSlot *slot; uint64 barrier_generation; uint32 old_pss_pid; + Assert(cancel_key_len >= 0 && cancel_key_len <= MAX_CANCEL_KEY_LENGTH); if (MyProcNumber < 0) elog(ERROR, "MyProcNumber not set"); if (MyProcNumber >= NumProcSignalSlots) @@ -199,8 +199,9 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key) pg_atomic_read_u64(&ProcSignal->psh_barrierGeneration); pg_atomic_write_u64(&slot->pss_barrierGeneration, barrier_generation); - slot->pss_cancel_key_valid = cancel_key_valid; - slot->pss_cancel_key = cancel_key; + if (cancel_key_len > 0) + memcpy(slot->pss_cancel_key, cancel_key, cancel_key_len); + slot->pss_cancel_key_len = cancel_key_len; pg_atomic_write_u32(&slot->pss_pid, MyProcPid); SpinLockRelease(&slot->pss_mutex); @@ -254,8 +255,7 @@ CleanupProcSignalState(int status, Datum arg) /* Mark the slot as unused */ pg_atomic_write_u32(&slot->pss_pid, 0); - slot->pss_cancel_key_valid = false; - slot->pss_cancel_key = 0; + slot->pss_cancel_key_len = 0; /* * Make this slot look like it's absorbed all possible barriers, so that @@ -725,7 +725,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS) * fields in the ProcSignal slots. */ void -SendCancelRequest(int backendPID, int32 cancelAuthCode) +SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len) { Assert(backendPID != 0); @@ -754,7 +754,8 @@ SendCancelRequest(int backendPID, int32 cancelAuthCode) } else { - match = slot->pss_cancel_key_valid && slot->pss_cancel_key == cancelAuthCode; + match = slot->pss_cancel_key_len == cancel_key_len && + timingsafe_bcmp(slot->pss_cancel_key, cancel_key, cancel_key_len) == 0; SpinLockRelease(&slot->pss_mutex); diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c index a07c59ece013..cbdc903b4561 100644 --- a/src/backend/tcop/backend_startup.c +++ b/src/backend/tcop/backend_startup.c @@ -59,6 +59,7 @@ ConnectionTiming conn_timing = {.ready_for_use = TIMESTAMP_MINUS_INFINITY}; static void BackendInitialize(ClientSocket *client_sock, CAC_state cac); static int ProcessSSLStartup(Port *port); static int ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done); +static void ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen); static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options); static void process_startup_packet_die(SIGNAL_ARGS); static void StartupPacketTimeoutHandler(void); @@ -557,28 +558,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) if (proto == CANCEL_REQUEST_CODE) { - /* - * The client has sent a cancel request packet, not a normal - * start-a-new-connection packet. Perform the necessary processing. - * Nothing is sent back to the client. - */ - CancelRequestPacket *canc; - int backendPID; - int32 cancelAuthCode; - - if (len != sizeof(CancelRequestPacket)) - { - ereport(COMMERROR, - (errcode(ERRCODE_PROTOCOL_VIOLATION), - errmsg("invalid length of startup packet"))); - return STATUS_ERROR; - } - canc = (CancelRequestPacket *) buf; - backendPID = (int) pg_ntoh32(canc->backendPID); - cancelAuthCode = (int32) pg_ntoh32(canc->cancelAuthCode); - - if (backendPID != 0) - SendCancelRequest(backendPID, cancelAuthCode); + ProcessCancelRequestPacket(port, buf, len); /* Not really an error, but we don't want to proceed further */ return STATUS_ERROR; } @@ -878,6 +858,37 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) return STATUS_OK; } +/* + * The client has sent a cancel request packet, not a normal + * start-a-new-connection packet. Perform the necessary processing. Nothing + * is sent back to the client. + */ +static void +ProcessCancelRequestPacket(Port *port, void *pkt, int pktlen) +{ + CancelRequestPacket *canc; + int len; + + if (pktlen < offsetof(CancelRequestPacket, cancelAuthCode)) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("invalid length of query cancel packet"))); + return; + } + len = pktlen - offsetof(CancelRequestPacket, cancelAuthCode); + if (len == 0 || len > 256) + { + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("invalid length of query cancel key"))); + return; + } + + canc = (CancelRequestPacket *) pkt; + SendCancelRequest(pg_ntoh32(canc->backendPID), canc->cancelAuthCode, len); +} + /* * Send a NegotiateProtocolVersion to the client. This lets the client know * that they have either requested a newer minor protocol version than we are diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index aec65007bb6a..c26a0511989e 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -4259,16 +4259,20 @@ PostgresMain(const char *dbname, const char *username) * Generate a random cancel key, if this is a backend serving a * connection. InitPostgres() will advertise it in shared memory. */ - Assert(!MyCancelKeyValid); + Assert(MyCancelKeyLength == 0); if (whereToSendOutput == DestRemote) { - if (!pg_strong_random(&MyCancelKey, sizeof(int32))) + int len; + + len = (MyProcPort == NULL || MyProcPort->proto >= PG_PROTOCOL(3, 1)) + ? MAX_CANCEL_KEY_LENGTH : 4; + if (!pg_strong_random(&MyCancelKey, len)) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("could not generate random cancel key"))); } - MyCancelKeyValid = true; + MyCancelKeyLength = len; } /* @@ -4323,10 +4327,11 @@ PostgresMain(const char *dbname, const char *username) { StringInfoData buf; - Assert(MyCancelKeyValid); + Assert(MyCancelKeyLength > 0); pq_beginmessage(&buf, PqMsg_BackendKeyData); pq_sendint32(&buf, (int32) MyProcPid); - pq_sendint32(&buf, (int32) MyCancelKey); + + pq_sendbytes(&buf, MyCancelKey, MyCancelKeyLength); pq_endmessage(&buf); /* Need not flush since ReadyForQuery will do it. */ } diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index b844f9fdaef5..2152aad97d99 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -24,6 +24,7 @@ #include "miscadmin.h" #include "postmaster/postmaster.h" #include "storage/procnumber.h" +#include "storage/procsignal.h" ProtocolVersion FrontendProtocol; @@ -48,8 +49,8 @@ pg_time_t MyStartTime; TimestampTz MyStartTimestamp; struct ClientSocket *MyClientSocket; struct Port *MyProcPort; -bool MyCancelKeyValid = false; -int32 MyCancelKey = 0; +char MyCancelKey[MAX_CANCEL_KEY_LENGTH]; +uint8 MyCancelKeyLength = 0; int MyPMChildSlot; /* diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 7958ea11b735..c09c4d404ba7 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -753,7 +753,7 @@ InitPostgres(const char *in_dbname, Oid dboid, */ SharedInvalBackendInit(false); - ProcSignalInit(MyCancelKeyValid, MyCancelKey); + ProcSignalInit(MyCancelKey, MyCancelKeyLength); /* * Also set up timeout handlers needed for backend operation. We need diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 46b37e0e4ebb..d0f2c1fb03a4 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -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 */ @@ -129,7 +128,11 @@ typedef uint32 AuthRequest; * * The cancel request code must not match any protocol version number * we're ever likely to use. This random choice should do. + * + * Before PostgreSQL v17 and the protocol version bump from 3.0 to 3.1, the + * cancel key was always 4 bytes. Starting with v17, it's variable length. */ + #define CANCEL_REQUEST_CODE PG_PROTOCOL(1234,5678) typedef struct CancelRequestPacket @@ -137,7 +140,8 @@ typedef struct CancelRequestPacket /* Note that each field is stored in network byte order! */ MsgType cancelRequestCode; /* code to identify a cancel request */ uint32 backendPID; /* PID of client's backend */ - uint32 cancelAuthCode; /* secret key to authorize cancel */ + char cancelAuthCode[FLEXIBLE_ARRAY_MEMBER]; /* secret key to + * authorize cancel */ } CancelRequestPacket; /* Application-Layer Protocol Negotiation is required for direct connections diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 603d0424354d..0d8528b28751 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -191,8 +191,8 @@ extern PGDLLIMPORT pg_time_t MyStartTime; extern PGDLLIMPORT TimestampTz MyStartTimestamp; extern PGDLLIMPORT struct Port *MyProcPort; extern PGDLLIMPORT struct Latch *MyLatch; -extern PGDLLIMPORT bool MyCancelKeyValid; -extern PGDLLIMPORT int32 MyCancelKey; +extern PGDLLIMPORT char MyCancelKey[]; +extern PGDLLIMPORT uint8 MyCancelKeyLength; extern PGDLLIMPORT int MyPMChildSlot; extern PGDLLIMPORT char OutputFileName[]; diff --git a/src/include/port.h b/src/include/port.h index 3faae03d246b..3964d3b12936 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -464,6 +464,10 @@ extern size_t strnlen(const char *str, size_t maxlen); extern char *strsep(char **stringp, const char *delim); #endif +#if !HAVE_DECL_TIMINGSAFE_BCMP +extern int timingsafe_bcmp(const void *b1, const void *b2, size_t len); +#endif + /* * Callers should use the qsort() macro defined below instead of calling * pg_qsort() directly. diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h index 022fd8ed9338..b14c92525821 100644 --- a/src/include/storage/procsignal.h +++ b/src/include/storage/procsignal.h @@ -56,16 +56,26 @@ typedef enum PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */ } ProcSignalBarrierType; +/* + * Length of query cancel keys generated. + * + * Note that the protocol allows for longer keys, or shorter, but this is the + * length we actually generate. Client code, and the server code that handles + * incoming cancellation packets from clients, cannot use this hardcoded + * length. + */ +#define MAX_CANCEL_KEY_LENGTH 32 + /* * prototypes for functions in procsignal.c */ extern Size ProcSignalShmemSize(void); extern void ProcSignalShmemInit(void); -extern void ProcSignalInit(bool cancel_key_valid, int32 cancel_key); +extern void ProcSignalInit(char *cancel_key, int cancel_key_len); extern int SendProcSignal(pid_t pid, ProcSignalReason reason, ProcNumber procNumber); -extern void SendCancelRequest(int backendPID, int32 cancelAuthCode); +extern void SendCancelRequest(int backendPID, char *cancel_key, int cancel_key_len); extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type); extern void WaitForProcSignalBarrier(uint64 generation); diff --git a/src/interfaces/libpq/fe-cancel.c b/src/interfaces/libpq/fe-cancel.c index 7ebaa335bbae..e84e64bf2a70 100644 --- a/src/interfaces/libpq/fe-cancel.c +++ b/src/interfaces/libpq/fe-cancel.c @@ -1,7 +1,7 @@ /*------------------------------------------------------------------------- * * fe-cancel.c - * functions related to setting up a connection to the backend + * functions related to query cancellation * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -41,7 +41,6 @@ struct pg_cancel { SockAddr raddr; /* Remote address */ int be_pid; /* PID of to-be-canceled backend */ - int be_key; /* cancel key of to-be-canceled backend */ int pgtcp_user_timeout; /* tcp user timeout */ int keepalives; /* use TCP keepalives? */ int keepalives_idle; /* time between TCP keepalives */ @@ -49,6 +48,10 @@ struct pg_cancel * retransmits */ int keepalives_count; /* maximum number of TCP keepalive * retransmits */ + + /* Pre-constructed cancel request packet starts here */ + int32 cancel_pkt_len; /* in network-byte-order */ + char cancel_req[FLEXIBLE_ARRAY_MEMBER]; /* CancelRequestPacket */ }; @@ -83,6 +86,13 @@ PQcancelCreate(PGconn *conn) return (PGcancelConn *) cancelConn; } + /* Check that we have received a cancellation key */ + if (conn->be_cancel_key_len == 0) + { + libpq_append_conn_error(cancelConn, "no cancellation key received"); + return (PGcancelConn *) cancelConn; + } + /* * Indicate that this connection is used to send a cancellation */ @@ -101,7 +111,15 @@ PQcancelCreate(PGconn *conn) * Copy cancellation token data from the original connection */ cancelConn->be_pid = conn->be_pid; - cancelConn->be_key = conn->be_key; + if (conn->be_cancel_key != NULL) + { + cancelConn->be_cancel_key = malloc(conn->be_cancel_key_len); + if (!conn->be_cancel_key) + goto oom_error; + memcpy(cancelConn->be_cancel_key, conn->be_cancel_key, conn->be_cancel_key_len); + } + cancelConn->be_cancel_key_len = conn->be_cancel_key_len; + cancelConn->pversion = conn->pversion; /* * Cancel requests should not iterate over all possible hosts. The request @@ -349,6 +367,8 @@ PGcancel * PQgetCancel(PGconn *conn) { PGcancel *cancel; + int cancel_req_len; + CancelRequestPacket *req; if (!conn) return NULL; @@ -356,13 +376,17 @@ PQgetCancel(PGconn *conn) if (conn->sock == PGINVALID_SOCKET) return NULL; - cancel = malloc(sizeof(PGcancel)); + /* Check that we have received a cancellation key */ + if (conn->be_cancel_key_len == 0) + return NULL; + + cancel_req_len = offsetof(CancelRequestPacket, cancelAuthCode) + conn->be_cancel_key_len; + cancel = malloc(offsetof(PGcancel, cancel_req) + cancel_req_len); if (cancel == NULL) return NULL; memcpy(&cancel->raddr, &conn->raddr, sizeof(SockAddr)); - cancel->be_pid = conn->be_pid; - cancel->be_key = conn->be_key; + /* We use -1 to indicate an unset connection option */ cancel->pgtcp_user_timeout = -1; cancel->keepalives = -1; @@ -405,6 +429,13 @@ PQgetCancel(PGconn *conn) goto fail; } + req = (CancelRequestPacket *) &cancel->cancel_req; + req->cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE); + req->backendPID = pg_hton32(conn->be_pid); + memcpy(req->cancelAuthCode, conn->be_cancel_key, conn->be_cancel_key_len); + /* include the length field itself in the length */ + cancel->cancel_pkt_len = pg_hton32(cancel_req_len + 4); + return cancel; fail: @@ -412,6 +443,42 @@ PQgetCancel(PGconn *conn) return NULL; } +/* + * PQsendCancelRequest + * Submit a CancelRequest message, but don't wait for it to finish + * + * Returns: 1 if successfully submitted + * 0 if error (conn->errorMessage is set) + */ +int +PQsendCancelRequest(PGconn *cancelConn) +{ + CancelRequestPacket req; + + /* Start the message. */ + if (pqPutMsgStart(0, cancelConn)) + return STATUS_ERROR; + + /* Send the message body. */ + memset(&req, 0, offsetof(CancelRequestPacket, cancelAuthCode)); + req.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE); + req.backendPID = pg_hton32(cancelConn->be_pid); + if (pqPutnchar((char *) &req, offsetof(CancelRequestPacket, cancelAuthCode), cancelConn)) + return STATUS_ERROR; + if (pqPutnchar(cancelConn->be_cancel_key, cancelConn->be_cancel_key_len, cancelConn)) + return STATUS_ERROR; + + /* Finish the message. */ + if (pqPutMsgEnd(cancelConn)) + return STATUS_ERROR; + + /* Flush to ensure backend gets it. */ + if (pqFlush(cancelConn)) + return STATUS_ERROR; + + return STATUS_OK; +} + /* PQfreeCancel: free a cancel structure */ void PQfreeCancel(PGcancel *cancel) @@ -465,11 +532,8 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize) int save_errno = SOCK_ERRNO; pgsocket tmpsock = PGINVALID_SOCKET; int maxlen; - struct - { - uint32 packetlen; - CancelRequestPacket cp; - } crp; + char recvbuf; + int cancel_pkt_len; if (!cancel) { @@ -571,15 +635,15 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize) goto cancel_errReturn; } - /* Create and send the cancel request packet. */ - - crp.packetlen = pg_hton32((uint32) sizeof(crp)); - crp.cp.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE); - crp.cp.backendPID = pg_hton32(cancel->be_pid); - crp.cp.cancelAuthCode = pg_hton32(cancel->be_key); + cancel_pkt_len = pg_ntoh32(cancel->cancel_pkt_len); retry4: - if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp)) + + /* + * Send the cancel request packet. It starts with the message length at + * cancel_pkt_len, followed by the actual packet. + */ + if (send(tmpsock, (char *) &cancel->cancel_pkt_len, cancel_pkt_len, 0) != cancel_pkt_len) { if (SOCK_ERRNO == EINTR) /* Interrupted system call - we'll just try again */ @@ -596,7 +660,7 @@ PQcancel(PGcancel *cancel, char *errbuf, int errbufsize) * read to obtain any data, we are just waiting for EOF to be signaled. */ retry5: - if (recv(tmpsock, (char *) &crp, 1, 0) < 0) + if (recv(tmpsock, &recvbuf, 1, 0) < 0) { if (SOCK_ERRNO == EINTR) /* Interrupted system call - we'll just try again */ diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index d5051f5e820f..58328e829af4 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -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 */ @@ -667,6 +678,7 @@ pqDropServerData(PGconn *conn) /* Reset assorted other per-connection state */ conn->last_sqlstate[0] = '\0'; + conn->negotiate_pversion_received = false; conn->auth_req_received = false; conn->client_finished_auth = false; conn->password_needed = false; @@ -684,7 +696,12 @@ pqDropServerData(PGconn *conn) if (!conn->cancelRequest) { conn->be_pid = 0; - conn->be_key = 0; + if (conn->be_cancel_key != NULL) + { + free(conn->be_cancel_key); + conn->be_cancel_key = NULL; + } + conn->be_cancel_key_len = 0; } } @@ -2080,6 +2097,42 @@ pqConnectOptions2(PGconn *conn) } } + if (conn->min_protocol_version) + { + if (!pqParseProtocolVersion(conn->min_protocol_version, &conn->min_pversion, conn, "min_protocol_version")) + 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")) + 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 */ @@ -3083,7 +3136,7 @@ PQconnectPoll(PGconn *conn) * 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; @@ -3638,13 +3691,7 @@ PQconnectPoll(PGconn *conn) */ if (conn->cancelRequest) { - CancelRequestPacket cancelpacket; - - packetlen = sizeof(cancelpacket); - cancelpacket.cancelRequestCode = (MsgType) pg_hton32(CANCEL_REQUEST_CODE); - cancelpacket.backendPID = pg_hton32(conn->be_pid); - cancelpacket.cancelAuthCode = pg_hton32(conn->be_key); - if (pqPacketSend(conn, 0, &cancelpacket, packetlen) != STATUS_OK) + if (PQsendCancelRequest(conn) != STATUS_OK) { libpq_append_conn_error(conn, "could not send cancel packet: %s", SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); @@ -4084,16 +4131,29 @@ PQconnectPoll(PGconn *conn) CONNECTION_FAILED(); } + /* Handle NegotiateProtocolVersion */ else if (beresp == PqMsg_NegotiateProtocolVersion) { + if (conn->negotiate_pversion_received) + { + libpq_append_conn_error(conn, "received duplicate protocol negotiation message"); + goto error_return; + } + conn->negotiate_pversion_received = true; + if (pqGetNegotiateProtocolVersion3(conn)) { - libpq_append_conn_error(conn, "received invalid protocol negotiation message"); + /* + * Negotiation failed. The error message was filled + * in already. + */ goto error_return; } + /* OK, we read the message; mark data consumed */ pqParseDone(conn, conn->inCursor); - goto error_return; + + goto keep_going; } /* It is an authentication request. */ @@ -8148,6 +8208,70 @@ pqParseIntParam(const char *value, int *result, PGconn *conn, 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) +{ + char *end; + int major; + int minor; + ProtocolVersion version; + + if (strcmp(value, "latest") == 0) + { + *result = PG_PROTOCOL_LATEST; + return true; + } + + /* 3.1 never existed, we went straight from 3.0 to 3.2 */ + if (strcmp(value, "3.1") == 0) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + context, + value); + return false; + } + + major = strtol(value, &end, 10); + if (*end != '.') + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + context, + value); + return false; + } + + minor = strtol(&end[1], &end, 10); + if (*end != '\0') + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + context, + value); + return false; + } + + version = PG_PROTOCOL(major, minor); + if (version > PG_PROTOCOL_LATEST || + version < PG_PROTOCOL_EARLIEST) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + context, + value); + return false; + } + + *result = version; + return true; +} + /* * To keep the API consistent, the locking stubs are always provided, even * if they are not required. diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 95dd456f0764..d85910f41fc7 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -48,6 +48,7 @@ static int getRowDescriptions(PGconn *conn, int msgLength); static int getParamDescriptions(PGconn *conn, int msgLength); static int getAnotherTuple(PGconn *conn, int msgLength); static int getParameterStatus(PGconn *conn); +static int getBackendKeyData(PGconn *conn, int msgLength); static int getNotify(PGconn *conn); static int getCopyStart(PGconn *conn, ExecStatusType copytype); static int getReadyForQuery(PGconn *conn); @@ -308,9 +309,7 @@ pqParseInput3(PGconn *conn) * just as easy to handle it as part of the main loop. * Save the data and continue processing. */ - if (pqGetInt(&(conn->be_pid), 4, conn)) - return; - if (pqGetInt(&(conn->be_key), 4, conn)) + if (getBackendKeyData(conn, msgLength)) return; break; case PqMsg_RowDescription: @@ -870,6 +869,7 @@ getAnotherTuple(PGconn *conn, int msgLength) /* * Attempt to read an Error or Notice response message. * This is possible in several places, so we break it out as a subroutine. + * * Entry: 'E' or 'N' message type and length have already been consumed. * Exit: returns 0 if successfully consumed message. * returns EOF if not enough data. @@ -1399,64 +1399,105 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding) /* - * Attempt to read a NegotiateProtocolVersion message. + * Attempt to read a NegotiateProtocolVersion message. Sets conn->pversion + * to the version that's negotiated by the server. + * * Entry: 'v' message type and length have already been consumed. * Exit: returns 0 if successfully consumed message. - * returns EOF if not enough data. + * returns 1 on failure. The error message is filled in. */ int pqGetNegotiateProtocolVersion3(PGconn *conn) { - int tmp; - ProtocolVersion their_version; + int their_version; int num; - PQExpBufferData buf; - if (pqGetInt(&tmp, 4, conn) != 0) - return EOF; - their_version = tmp; + if (pqGetInt(&their_version, 4, conn) != 0) + goto eof; if (pqGetInt(&num, 4, conn) != 0) - return EOF; + goto eof; - initPQExpBuffer(&buf); - for (int i = 0; i < num; i++) + /* Check the protocol version */ + if (their_version > conn->pversion) { - if (pqGets(&conn->workBuffer, conn)) - { - termPQExpBuffer(&buf); - return EOF; - } - if (buf.len > 0) - appendPQExpBufferChar(&buf, ' '); - appendPQExpBufferStr(&buf, conn->workBuffer.data); + libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to a higher-numbered version"); + goto failure; + } + + if (their_version < PG_PROTOCOL(3, 0)) + { + libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requested downgrade to pre-3.0 protocol version"); + 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"); + goto failure; + } + + if (their_version == conn->pversion && num == 0) + { + libpq_append_conn_error(conn, "received invalid protocol negotiation message: server negotiated but asks for no changes"); + goto failure; } - 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) + if (their_version < conn->min_pversion) { - 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'); + 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; } - /* neither -- server shouldn't have sent it */ - if (!(their_version < conn->pversion) && !(num > 0)) - libpq_append_conn_error(conn, "invalid %s message", "NegotiateProtocolVersion"); + /* the version is acceptable */ + conn->pversion = their_version; + + /* + * We don't currently request any protocol extensions, so we don't expect + * the server to reply with any either. + */ + for (int i = 0; i < num; i++) + { + if (pqGets(&conn->workBuffer, conn)) + { + goto eof; + } + if (strncmp(conn->workBuffer.data, "_pq_.", 5) != 0) + { + libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported unsupported parameter name without a _pq_. prefix (\"%s\")", conn->workBuffer.data); + goto failure; + } + libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data); + goto failure; + } - termPQExpBuffer(&buf); return 0; + +eof: + libpq_append_conn_error(conn, "received invalid protocol negotation message: message too short"); +failure: + conn->asyncStatus = PGASYNC_READY; + pqSaveErrorResult(conn); + return 1; } /* * Attempt to read a ParameterStatus message. * This is possible in several places, so we break it out as a subroutine. + * * Entry: 'S' message type and length have already been consumed. * Exit: returns 0 if successfully consumed message. * returns EOF if not enough data. @@ -1482,10 +1523,51 @@ getParameterStatus(PGconn *conn) return 0; } +/* + * parseInput subroutine to read a BackendKeyData message. + * Entry: 'v' message type and length have already been consumed. + * Exit: returns 0 if successfully consumed message. + * returns EOF if not enough data. + */ +static int +getBackendKeyData(PGconn *conn, int msgLength) +{ + uint8 cancel_key_len; + + if (conn->be_cancel_key) + { + free(conn->be_cancel_key); + conn->be_cancel_key = NULL; + conn->be_cancel_key_len = 0; + } + + if (pqGetInt(&(conn->be_pid), 4, conn)) + return EOF; + + cancel_key_len = 5 + msgLength - (conn->inCursor - conn->inStart); + + conn->be_cancel_key = malloc(cancel_key_len); + if (conn->be_cancel_key == NULL) + { + libpq_append_conn_error(conn, "out of memory"); + /* discard the message */ + return EOF; + } + if (pqGetnchar(conn->be_cancel_key, cancel_key_len, conn)) + { + free(conn->be_cancel_key); + conn->be_cancel_key = NULL; + return EOF; + } + conn->be_cancel_key_len = cancel_key_len; + return 0; +} + /* * Attempt to read a Notify response message. * This is possible in several places, so we break it out as a subroutine. + * * Entry: 'A' message type and length have already been consumed. * Exit: returns 0 if successfully consumed Notify message. * returns EOF if not enough data. diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index ade5ad82f07c..16b6d96df33c 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -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 */ @@ -496,6 +498,7 @@ struct pg_conn SockAddr raddr; /* Remote address */ ProtocolVersion pversion; /* FE/BE protocol version in use */ int sversion; /* server version, e.g. 70401 for 7.4.1 */ + bool negotiate_pversion_received; /* true if NegotiateProtocolVersion received */ bool auth_req_received; /* true if any type of auth req received */ bool password_needed; /* true if server demanded a password */ bool gssapi_used; /* true if authenticated via gssapi */ @@ -537,10 +540,13 @@ 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 */ - int be_key; /* key of backend --- needed for cancels */ + char *be_cancel_key; /* query cancellation key and its length */ + uint16 be_cancel_key_len; pgParameterStatus *pstatus; /* ParameterStatus data */ int client_encoding; /* encoding id */ bool std_strings; /* standard_conforming_strings */ @@ -760,6 +766,10 @@ extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid, int result_is_int, const PQArgBlock *args, int nargs); +/* === in fe-cancel.c === */ + +extern int PQsendCancelRequest(PGconn *cancelConn); + /* === in fe-misc.c === */ /* diff --git a/src/port/meson.build b/src/port/meson.build index cf7f07644b90..51041e756099 100644 --- a/src/port/meson.build +++ b/src/port/meson.build @@ -73,6 +73,7 @@ replace_funcs_neg = [ ['strlcpy'], ['strnlen'], ['strsep'], + ['timingsafe_bcmp'], ] if host_system != 'windows' diff --git a/src/port/timingsafe_bcmp.c b/src/port/timingsafe_bcmp.c new file mode 100644 index 000000000000..288865f50d17 --- /dev/null +++ b/src/port/timingsafe_bcmp.c @@ -0,0 +1,43 @@ +/* + * src/port/timingsafe_bcmp.c + * + * $OpenBSD: timingsafe_bcmp.c,v 1.3 2015/08/31 02:53:57 guenther Exp $ + */ + +/* + * Copyright (c) 2010 Damien Miller. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "c.h" + +#ifdef USE_SSL +#include +#endif + +int +timingsafe_bcmp(const void *b1, const void *b2, size_t n) +{ +#ifdef USE_SSL + return CRYPTO_memcmp(b1, b2, n); +#else + const unsigned char *p1 = b1, + *p2 = b2; + int ret = 0; + + for (; n > 0; n--) + ret |= *p1++ ^ *p2++; + return (ret != 0); +#endif +} diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c index ac9ac95135f3..92c547581966 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -197,24 +197,15 @@ send_cancellable_query_impl(int line, PGconn *conn, PGconn *monitorConn) } /* - * Create a new connection with the same conninfo as the given one. + * Fills keywords and vals, with the same options as the ones in the opts + * linked-list. Returns the length of the filled in list. */ -static PGconn * -copy_connection(PGconn *conn) +static +int +copy_connection_options(PQconninfoOption *opts, const char **keywords, const char **vals) { - PGconn *copyConn; - PQconninfoOption *opts = PQconninfo(conn); - const char **keywords; - const char **vals; - int nopts = 1; int i = 0; - for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt) - nopts++; - - keywords = pg_malloc(sizeof(char *) * nopts); - vals = pg_malloc(sizeof(char *) * nopts); - for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt) { if (opt->val) @@ -224,8 +215,28 @@ copy_connection(PGconn *conn) i++; } } - keywords[i] = vals[i] = NULL; + return i; +} + +/* + * Create a new connection with the same conninfo as the given one. + */ +static PGconn * +copy_connection(PGconn *conn) +{ + const char **keywords; + const char **vals; + PGconn *copyConn; + PQconninfoOption *opts = PQconninfo(conn); + int nopts = 1; /* 1 for the NULL terminator */ + for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt) + nopts++; + + keywords = pg_malloc0(sizeof(char *) * nopts); + vals = pg_malloc0(sizeof(char *) * nopts); + + copy_connection_options(opts, keywords, vals); copyConn = PQconnectdbParams(keywords, vals, false); if (PQstatus(copyConn) != CONNECTION_OK) @@ -1405,6 +1416,72 @@ test_prepared(PGconn *conn) fprintf(stderr, "ok\n"); } +static void +test_protocol_version(PGconn *conn) +{ + const char **keywords; + const char **vals; + int nopts = 2; /* NULL terminator + max_protocol_version */ + PQconninfoOption *opts = PQconninfo(conn); + int protocol_version; + int max_protocol_version_index; + + for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt) + nopts++; + + keywords = pg_malloc0(sizeof(char *) * nopts); + vals = pg_malloc0(sizeof(char *) * nopts); + + max_protocol_version_index = copy_connection_options(opts, keywords, vals); + + keywords[max_protocol_version_index] = "max_protocol_version"; + 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); + + 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); + + 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); + + 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 +2230,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 +2341,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) diff --git a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl index 9691b8504189..61524bdbd8f2 100644 --- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl +++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl @@ -49,11 +49,11 @@ BEGIN push @extraargs, "-t" => $traceout; } - # Execute the test + # Execute the test using the latest protocol version. $node->command_ok( [ 'libpq_pipeline', @extraargs, - $testname, $node->connstr('postgres') + $testname, $node->connstr('postgres') . " max_protocol_version=latest" ], "libpq_pipeline $testname"); @@ -72,6 +72,14 @@ BEGIN } } +# There were changes to query cancellation in protocol version 3.2, so +# test separately that it still works the old protocol version too. +$node->command_ok( + [ + 'libpq_pipeline', 'cancel', $node->connstr('postgres') . " max_protocol_version=3.0" + ], + "libpq_pipeline cancel with protocol 3.0"); + $node->stop('fast'); done_testing(); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b66cecd87991..aa01983337eb 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -386,6 +386,7 @@ CachedPlanSource CallContext CallStmt CancelRequestPacket +CancelRequestExtendedPacket Cardinality CaseExpr CaseKind