aboutsummaryrefslogtreecommitdiff
path: root/src/interfaces/libpq/fe-connect.c
diff options
context:
space:
mode:
authorDaniel Gustafsson <dgustafsson@postgresql.org>2025-02-06 22:19:21 +0100
committerDaniel Gustafsson <dgustafsson@postgresql.org>2025-02-06 22:19:21 +0100
commita99a32e43ed72bd4fdb0950d2359fa4aa50fab76 (patch)
tree0239205eb286521400354bec2baf7b800f62473e /src/interfaces/libpq/fe-connect.c
parent44ec09575145e908be8130de701e5c83995d1abe (diff)
downloadpostgresql-a99a32e43ed72bd4fdb0950d2359fa4aa50fab76.tar.gz
postgresql-a99a32e43ed72bd4fdb0950d2359fa4aa50fab76.zip
libpq: Handle asynchronous actions during SASL
This adds the ability for a SASL mechanism to signal PQconnectPoll() that some arbitrary work, external to the Postgres connection, is required for authentication to continue. There is no consumer for this capability as part of this commit, it is infrastructure which is required for future work on supporting the OAUTHBEARER mechanism. To ensure that threads are not blocked waiting for the SASL mechanism to make long-running calls, the mechanism communicates with the top- level client via the "altsock": a file or socket descriptor, opaque to this layer of libpq, which is signaled when work is ready to be done again. The altsock temporarily replaces the regular connection descriptor, so existing PQsocket() clients should continue to operate correctly using their existing polling implementations. For a mechanism to use this it should set an authentication callback, conn->async_auth(), and a cleanup callback, conn->cleanup_async_auth(), and return SASL_ASYNC during the exchange. It should then assign conn->altsock during the first call to async_auth(). When the cleanup callback is called, either because authentication has succeeded or because the connection is being dropped, the altsock must be released and disconnected from the PGconn object. This was extracted from the larger OAUTHBEARER patchset which has been developed, and reviewed by many, over several years and it is thus likely that some reviewer credit of much earlier versions has been accidentally omitted. Author: Jacob Champion <jacob.champion@enterprisedb.com> Reviewed-by: Daniel Gustafsson <daniel@yesql.se> Reviewed-by: Peter Eisentraut <peter@eisentraut.org> Reviewed-by: Antonin Houska <ah@cybertec.at> Discussion: https://postgr.es/m/CAOYmi+kJqzo6XsR9TEhvVfeVNQ-TyFM5LATypm9yoQVYk=4Wrw@mail.gmail.com
Diffstat (limited to 'src/interfaces/libpq/fe-connect.c')
-rw-r--r--src/interfaces/libpq/fe-connect.c93
1 files changed, 92 insertions, 1 deletions
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index e1cea790f9e..85d1ca2864f 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -507,6 +507,19 @@ pqDropConnection(PGconn *conn, bool flushInput)
conn->cmd_queue_recycle = NULL;
/* Free authentication/encryption state */
+ if (conn->cleanup_async_auth)
+ {
+ /*
+ * Any in-progress async authentication should be torn down first so
+ * that cleanup_async_auth() can depend on the other authentication
+ * state if necessary.
+ */
+ conn->cleanup_async_auth(conn);
+ conn->cleanup_async_auth = NULL;
+ }
+ conn->async_auth = NULL;
+ /* cleanup_async_auth() should have done this, but make sure */
+ conn->altsock = PGINVALID_SOCKET;
#ifdef ENABLE_GSS
{
OM_uint32 min_s;
@@ -2853,6 +2866,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_GSS_STARTUP:
case CONNECTION_CHECK_TARGET:
+ case CONNECTION_AUTHENTICATING:
break;
default:
@@ -3888,6 +3902,7 @@ keep_going: /* We will come back to here until there is
int avail;
AuthRequest areq;
int res;
+ bool async;
/*
* Scan the message from current point (note that if we find
@@ -4076,7 +4091,17 @@ keep_going: /* We will come back to here until there is
* Note that conn->pghost must be non-NULL if we are going to
* avoid the Kerberos code doing a hostname look-up.
*/
- res = pg_fe_sendauth(areq, msgLength, conn);
+ res = pg_fe_sendauth(areq, msgLength, conn, &async);
+
+ if (async && (res == STATUS_OK))
+ {
+ /*
+ * We'll come back later once we're ready to respond.
+ * Don't consume the request yet.
+ */
+ conn->status = CONNECTION_AUTHENTICATING;
+ goto keep_going;
+ }
/*
* OK, we have processed the message; mark data consumed. We
@@ -4113,6 +4138,69 @@ keep_going: /* We will come back to here until there is
goto keep_going;
}
+ case CONNECTION_AUTHENTICATING:
+ {
+ PostgresPollingStatusType status;
+
+ if (!conn->async_auth || !conn->cleanup_async_auth)
+ {
+ /* programmer error; should not happen */
+ libpq_append_conn_error(conn,
+ "internal error: async authentication has no handler");
+ goto error_return;
+ }
+
+ /* Drive some external authentication work. */
+ status = conn->async_auth(conn);
+
+ if (status == PGRES_POLLING_FAILED)
+ goto error_return;
+
+ if (status == PGRES_POLLING_OK)
+ {
+ /* Done. Tear down the async implementation. */
+ conn->cleanup_async_auth(conn);
+ conn->cleanup_async_auth = NULL;
+
+ /*
+ * Cleanup must unset altsock, both as an indication that
+ * it's been released, and to stop pqSocketCheck from
+ * looking at the wrong socket after async auth is done.
+ */
+ if (conn->altsock != PGINVALID_SOCKET)
+ {
+ Assert(false);
+ libpq_append_conn_error(conn,
+ "internal error: async cleanup did not release polling socket");
+ goto error_return;
+ }
+
+ /*
+ * Reenter the authentication exchange with the server. We
+ * didn't consume the message that started external
+ * authentication, so it'll be reprocessed as if we just
+ * received it.
+ */
+ conn->status = CONNECTION_AWAITING_RESPONSE;
+
+ goto keep_going;
+ }
+
+ /*
+ * Caller needs to poll some more. conn->async_auth() should
+ * have assigned an altsock to poll on.
+ */
+ if (conn->altsock == PGINVALID_SOCKET)
+ {
+ Assert(false);
+ libpq_append_conn_error(conn,
+ "internal error: async authentication did not set a socket for polling");
+ goto error_return;
+ }
+
+ return status;
+ }
+
case CONNECTION_AUTH_OK:
{
/*
@@ -4794,6 +4882,7 @@ pqMakeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
+ conn->altsock = PGINVALID_SOCKET;
conn->Pfdebug = NULL;
/*
@@ -7445,6 +7534,8 @@ PQsocket(const PGconn *conn)
{
if (!conn)
return -1;
+ if (conn->altsock != PGINVALID_SOCKET)
+ return conn->altsock;
return (conn->sock != PGINVALID_SOCKET) ? conn->sock : -1;
}