From 058e215f801b3e7ade5cacfc7e1889966430624c Mon Sep 17 00:00:00 2001 From: Greg Stark Date: Thu, 16 Mar 2023 11:55:16 -0400 Subject: [PATCH v3 2/4] Direct SSL connections client support --- src/interfaces/libpq/fe-connect.c | 91 +++++++++++++++++++++++++++++-- src/interfaces/libpq/libpq-fe.h | 1 + src/interfaces/libpq/libpq-int.h | 3 + 3 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index b9f899c552..80d3e9169b 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -271,6 +271,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "SSL-Mode", "", 12, /* sizeof("verify-full") == 12 */ offsetof(struct pg_conn, sslmode)}, + {"sslnegotiation", "PGSSLNEGOTIATION", "postgres", NULL, + "SSL-Negotiation", "", 14, /* strlen("requiredirect") == 14 */ + offsetof(struct pg_conn, sslnegotiation)}, + {"sslcompression", "PGSSLCOMPRESSION", "0", NULL, "SSL-Compression", "", 1, offsetof(struct pg_conn, sslcompression)}, @@ -1463,11 +1467,36 @@ connectOptions2(PGconn *conn) } #endif } + + /* + * validate sslnegotiation option, default is "postgres" for the postgres + * style negotiated connection with an extra round trip but more options. + */ + if (conn->sslnegotiation) + { + if (strcmp(conn->sslnegotiation, "postgres") != 0 + && strcmp(conn->sslnegotiation, "direct") != 0 + && strcmp(conn->sslnegotiation, "requiredirect") != 0) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "invalid %s value: \"%s\"", + "sslnegotiation", conn->sslnegotiation); + return false; + } + +#ifndef USE_SSL + if (conn->negotiation[0] != 'p') { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "sslnegotiation value \"%s\" invalid when SSL support is not compiled in", + conn->sslnegotiation); + return false; + } +#endif + } else { - conn->sslmode = strdup(DefaultSSLMode); - if (!conn->sslmode) - goto oom_error; + libpq_append_conn_error(conn, "sslnegotiation missing?"); + return false; } /* @@ -1527,6 +1556,18 @@ connectOptions2(PGconn *conn) conn->gssencmode); return false; } +#endif +#ifdef USE_SSL + /* GSS is incompatible with direct SSL connections so it requires the + * default postgres style connection ssl negotiation */ + if (strcmp(conn->gssencmode, "require") == 0 && + strcmp(conn->sslnegotiation, "postgres") != 0) + { + conn->status = CONNECTION_BAD; + libpq_append_conn_error(conn, "gssencmode value \"%s\" invalid when Direct SSL negotiation is enabled", + conn->gssencmode); + return false; + } #endif } else @@ -2583,11 +2624,12 @@ keep_going: /* We will come back to here until there is /* initialize these values based on SSL mode */ conn->allow_ssl_try = (conn->sslmode[0] != 'd'); /* "disable" */ conn->wait_ssl_try = (conn->sslmode[0] == 'a'); /* "allow" */ + /* direct ssl is incompatible with "allow" or "disabled" ssl */ + conn->allow_direct_ssl_try = conn->allow_ssl_try && !conn->wait_ssl_try && (conn->sslnegotiation[0] != 'p'); #endif #ifdef ENABLE_GSS conn->try_gss = (conn->gssencmode[0] != 'd'); /* "disable" */ #endif - reset_connection_state_machine = false; need_new_connection = true; } @@ -3046,6 +3088,28 @@ keep_going: /* We will come back to here until there is if (pqsecure_initialize(conn, false, true) < 0) goto error_return; + /* If SSL is enabled and direct SSL connections are enabled + * and we haven't already established an SSL connection (or + * already tried a direct connection and failed or succeeded) + * then try just enabling SSL directly. + * + * If we fail then we'll either fail the connection (if + * sslnegotiation is set to requiredirect or turn + * allow_direct_ssl_try to false + */ + if (conn->allow_ssl_try + && !conn->wait_ssl_try + && conn->allow_direct_ssl_try + && !conn->ssl_in_use +#ifdef ENABLE_GSS + && !conn->gssenc +#endif + ) + { + conn->status = CONNECTION_SSL_STARTUP; + return PGRES_POLLING_WRITING; + } + /* * If SSL is enabled and we haven't already got encryption of * some sort running, request SSL instead of sending the @@ -3122,9 +3186,11 @@ keep_going: /* We will come back to here until there is /* * On first time through, get the postmaster's response to our - * SSL negotiation packet. + * SSL negotiation packet. If we are trying a direct ssl + * connection skip reading the negotiation packet and go + * straight to initiating an ssl connection. */ - if (!conn->ssl_in_use) + if (!conn->ssl_in_use && !conn->allow_direct_ssl_try) { /* * We use pqReadData here since it has the logic to @@ -3230,6 +3296,18 @@ keep_going: /* We will come back to here until there is } if (pollres == PGRES_POLLING_FAILED) { + /* Failed direct ssl connection, possibly try a new connection with postgres negotiation */ + if (conn->allow_direct_ssl_try) + { + /* if it's requiredirect then it's a hard failure */ + if (conn->sslnegotiation[0] == 'r') + goto error_return; + /* otherwise only retry using postgres connection */ + conn->allow_direct_ssl_try = false; + need_new_connection = true; + goto keep_going; + } + /* * Failed ... if sslmode is "prefer" then do a non-SSL * retry @@ -4231,6 +4309,7 @@ freePGconn(PGconn *conn) free(conn->keepalives_interval); free(conn->keepalives_count); free(conn->sslmode); + free(conn->sslnegotiation); free(conn->sslcert); free(conn->sslkey); if (conn->sslpassword) diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index f3d9220496..05821b8473 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -72,6 +72,7 @@ typedef enum CONNECTION_AUTH_OK, /* Received authentication; waiting for * backend startup. */ CONNECTION_SETENV, /* This state is no longer used. */ + CONNECTION_DIRECT_SSL_STARTUP, /* Starting SSL without PG Negotiation. */ CONNECTION_SSL_STARTUP, /* Negotiating SSL. */ CONNECTION_NEEDED, /* Internal state: connect() needed */ CONNECTION_CHECK_WRITABLE, /* Checking if session is read-write. */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 1dc264fe54..8d8964d835 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -380,6 +380,7 @@ struct pg_conn char *keepalives_count; /* maximum number of TCP keepalive * retransmits */ char *sslmode; /* SSL mode (require,prefer,allow,disable) */ + char *sslnegotiation; /* SSL initiation style (postgres,direct,requiredirect) */ char *sslcompression; /* SSL compression (0 or 1) */ char *sslkey; /* client key filename */ char *sslcert; /* client certificate filename */ @@ -529,6 +530,8 @@ struct pg_conn bool ssl_in_use; #ifdef USE_SSL + bool allow_direct_ssl_try; /* Try to make a direct SSL connection + * without an "SSL negotiation packet" */ bool allow_ssl_try; /* Allowed to try SSL negotiation */ bool wait_ssl_try; /* Delay SSL negotiation until after * attempting normal connection */ -- 2.39.2