From b60a18d2bb7e9a45d33155099459b41938f06323 Mon Sep 17 00:00:00 2001 From: Jakob Egger Date: Fri, 6 Dec 2019 12:34:23 +0100 Subject: [PATCH 2/2] libpq: Retry after failed ssl/gss negotiation When attempting to negotiate both GSS and SSL, detect errors from old servers that don't support multiple negotiation attempts. --- src/interfaces/libpq/fe-connect.c | 86 +++++++++++++++++++++---------- src/interfaces/libpq/libpq-int.h | 3 ++ 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 5c786360a9..e88771b7e6 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -1965,11 +1965,6 @@ connectDBStart(PGconn *conn) */ resetPQExpBuffer(&conn->errorMessage); -#ifdef ENABLE_GSS - if (conn->gssencmode[0] == 'd') /* "disable" */ - conn->try_gss = false; -#endif - /* * Set up to try to connect to the first host. (Setting whichhost = -1 is * a bit of a cheat, but PQconnectPoll will advance it to 0 before @@ -2409,6 +2404,13 @@ keep_going: /* We will come back to here until there is conn->allow_ssl_try = (conn->sslmode[0] != 'd'); /* "disable" */ conn->wait_ssl_try = (conn->sslmode[0] == 'a'); /* "allow" */ #endif + +#ifdef ENABLE_GSS + if (conn->gssencmode[0] == 'd') /* "disable" */ + conn->try_gss = false; +#endif + + conn->did_negotiate = false; reset_connection_state_machine = false; need_new_connection = true; @@ -2431,6 +2433,8 @@ keep_going: /* We will come back to here until there is /* Reset conn->status to put the state machine in the right state */ conn->status = CONNECTION_NEEDED; + conn->did_negotiate = false; + need_new_connection = false; } @@ -2971,24 +2975,37 @@ keep_going: /* We will come back to here until there is goto error_return; } /* Otherwise, proceed with normal startup */ + conn->did_negotiate = true; conn->allow_ssl_try = false; conn->status = CONNECTION_MADE; return PGRES_POLLING_WRITING; } else if (SSLok == 'E') { - /* - * Server failure of some sort, such as failure to - * fork a backend process. We need to process and - * report the error message, which might be formatted - * according to either protocol 2 or protocol 3. - * Rather than duplicate the code for that, we flip - * into AWAITING_RESPONSE state and let the code there - * deal with it. Note we have *not* consumed the "E" - * byte here. - */ - conn->status = CONNECTION_AWAITING_RESPONSE; - goto keep_going; + if (conn->did_negotiate) { + /* + * Postmaster used to allow only a single negotiation attempt. + * If we get an error on the second negotiation attempt, + * we reconnect and try again. + */ + need_new_connection = true; + goto keep_going; + } + else + { + /* + * Server failure of some sort, such as failure to + * fork a backend process. We need to process and + * report the error message, which might be formatted + * according to either protocol 2 or protocol 3. + * Rather than duplicate the code for that, we flip + * into AWAITING_RESPONSE state and let the code there + * deal with it. Note we have *not* consumed the "E" + * byte here. + */ + conn->status = CONNECTION_AWAITING_RESPONSE; + goto keep_going; + } } else { @@ -3061,17 +3078,29 @@ keep_going: /* We will come back to here until there is if (gss_ok == 'E') { - /* - * Server failure of some sort. Assume it's a - * protocol version support failure, and let's see if - * we can't recover (if it's not, we'll get a better - * error message on retry). Server gets fussy if we - * don't hang up the socket, though. - */ - conn->try_gss = false; - pqDropConnection(conn, true); - conn->status = CONNECTION_NEEDED; - goto keep_going; + if (conn->did_negotiate) { + /* + * Postmaster used to allow only a single negotiation attempt. + * If we get an error on the second negotiation attempt, + * we reconnect and try again. + */ + need_new_connection = true; + goto keep_going; + } + else + { + /* + * Server failure of some sort. Assume it's a + * protocol version support failure, and let's see if + * we can't recover (if it's not, we'll get a better + * error message on retry). Server gets fussy if we + * don't hang up the socket, though. + */ + conn->try_gss = false; + pqDropConnection(conn, true); + conn->status = CONNECTION_NEEDED; + goto keep_going; + } } /* mark byte consumed */ @@ -3087,6 +3116,7 @@ keep_going: /* We will come back to here until there is goto error_return; } + conn->did_negotiate = true; conn->try_gss = false; conn->status = CONNECTION_MADE; return PGRES_POLLING_WRITING; diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 7f5be7db7a..e787ab82da 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -507,6 +507,9 @@ struct pg_conn * connection */ #endif + /* Did we try negotiating SSL or GSS? Postmaster used to allow only a single attempt */ + bool did_negotiate; + /* Buffer for current error message */ PQExpBufferData errorMessage; /* expansible string */ -- 2.23.0