aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/interfaces/libpq/fe-auth-sasl.h11
-rw-r--r--src/interfaces/libpq/fe-auth-scram.c6
-rw-r--r--src/interfaces/libpq/fe-auth.c120
-rw-r--r--src/interfaces/libpq/fe-auth.h3
-rw-r--r--src/interfaces/libpq/fe-connect.c93
-rw-r--r--src/interfaces/libpq/fe-misc.c35
-rw-r--r--src/interfaces/libpq/libpq-fe.h2
-rw-r--r--src/interfaces/libpq/libpq-int.h6
8 files changed, 227 insertions, 49 deletions
diff --git a/src/interfaces/libpq/fe-auth-sasl.h b/src/interfaces/libpq/fe-auth-sasl.h
index f0c62139092..f06f547c07d 100644
--- a/src/interfaces/libpq/fe-auth-sasl.h
+++ b/src/interfaces/libpq/fe-auth-sasl.h
@@ -30,6 +30,7 @@ typedef enum
SASL_COMPLETE = 0,
SASL_FAILED,
SASL_CONTINUE,
+ SASL_ASYNC,
} SASLStatus;
/*
@@ -77,6 +78,8 @@ typedef struct pg_fe_sasl_mech
*
* state: The opaque mechanism state returned by init()
*
+ * final: true if the server has sent a final exchange outcome
+ *
* input: The challenge data sent by the server, or NULL when
* generating a client-first initial response (that is, when
* the server expects the client to send a message to start
@@ -101,12 +104,18 @@ typedef struct pg_fe_sasl_mech
*
* SASL_CONTINUE: The output buffer is filled with a client response.
* Additional server challenge is expected
+ * SASL_ASYNC: Some asynchronous processing external to the
+ * connection needs to be done before a response can be
+ * generated. The mechanism is responsible for setting up
+ * conn->async_auth/cleanup_async_auth appropriately
+ * before returning.
* SASL_COMPLETE: The SASL exchange has completed successfully.
* SASL_FAILED: The exchange has failed and the connection should be
* dropped.
*--------
*/
- SASLStatus (*exchange) (void *state, char *input, int inputlen,
+ SASLStatus (*exchange) (void *state, bool final,
+ char *input, int inputlen,
char **output, int *outputlen);
/*--------
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 557e9c568b6..fe18615197f 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -24,7 +24,8 @@
/* The exported SCRAM callback mechanism. */
static void *scram_init(PGconn *conn, const char *password,
const char *sasl_mechanism);
-static SASLStatus scram_exchange(void *opaq, char *input, int inputlen,
+static SASLStatus scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen);
static bool scram_channel_bound(void *opaq);
static void scram_free(void *opaq);
@@ -205,7 +206,8 @@ scram_free(void *opaq)
* Exchange a SCRAM message with backend.
*/
static SASLStatus
-scram_exchange(void *opaq, char *input, int inputlen,
+scram_exchange(void *opaq, bool final,
+ char *input, int inputlen,
char **output, int *outputlen)
{
fe_scram_state *state = (fe_scram_state *) opaq;
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 70753d8ec29..761ee8f88f7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -430,7 +430,7 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
* Initialize SASL authentication exchange.
*/
static int
-pg_SASL_init(PGconn *conn, int payloadlen)
+pg_SASL_init(PGconn *conn, int payloadlen, bool *async)
{
char *initialresponse = NULL;
int initialresponselen;
@@ -448,7 +448,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
goto error;
}
- if (conn->sasl_state)
+ if (conn->sasl_state && !conn->async_auth)
{
libpq_append_conn_error(conn, "duplicate SASL authentication request");
goto error;
@@ -607,26 +607,54 @@ pg_SASL_init(PGconn *conn, int payloadlen)
Assert(conn->sasl);
- /*
- * Initialize the SASL state information with all the information gathered
- * during the initial exchange.
- *
- * Note: Only tls-unique is supported for the moment.
- */
- conn->sasl_state = conn->sasl->init(conn,
- password,
- selected_mechanism);
if (!conn->sasl_state)
- goto oom_error;
+ {
+ /*
+ * Initialize the SASL state information with all the information
+ * gathered during the initial exchange.
+ *
+ * Note: Only tls-unique is supported for the moment.
+ */
+ conn->sasl_state = conn->sasl->init(conn,
+ password,
+ selected_mechanism);
+ if (!conn->sasl_state)
+ goto oom_error;
+ }
+ else
+ {
+ /*
+ * This is only possible if we're returning from an async loop.
+ * Disconnect it now.
+ */
+ Assert(conn->async_auth);
+ conn->async_auth = NULL;
+ }
/* Get the mechanism-specific Initial Client Response, if any */
- status = conn->sasl->exchange(conn->sasl_state,
+ status = conn->sasl->exchange(conn->sasl_state, false,
NULL, -1,
&initialresponse, &initialresponselen);
if (status == SASL_FAILED)
goto error;
+ if (status == SASL_ASYNC)
+ {
+ /*
+ * The mechanism should have set up the necessary callbacks; all we
+ * need to do is signal the caller.
+ *
+ * In non-assertion builds, this postcondition is enforced at time of
+ * use in PQconnectPoll().
+ */
+ Assert(conn->async_auth);
+ Assert(conn->cleanup_async_auth);
+
+ *async = true;
+ return STATUS_OK;
+ }
+
/*
* Build a SASLInitialResponse message, and send it.
*/
@@ -671,7 +699,7 @@ oom_error:
* the protocol.
*/
static int
-pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
+pg_SASL_continue(PGconn *conn, int payloadlen, bool final, bool *async)
{
char *output;
int outputlen;
@@ -701,11 +729,25 @@ pg_SASL_continue(PGconn *conn, int payloadlen, bool final)
/* For safety and convenience, ensure the buffer is NULL-terminated. */
challenge[payloadlen] = '\0';
- status = conn->sasl->exchange(conn->sasl_state,
+ status = conn->sasl->exchange(conn->sasl_state, final,
challenge, payloadlen,
&output, &outputlen);
free(challenge); /* don't need the input anymore */
+ if (status == SASL_ASYNC)
+ {
+ /*
+ * The mechanism should have set up the necessary callbacks; all we
+ * need to do is signal the caller.
+ */
+ *async = true;
+
+ /*
+ * The mechanism may optionally generate some output to send before
+ * switching over to async auth, so continue onwards.
+ */
+ }
+
if (final && status == SASL_CONTINUE)
{
if (outputlen != 0)
@@ -1013,12 +1055,18 @@ check_expected_areq(AuthRequest areq, PGconn *conn)
* it. We are responsible for reading any remaining extra data, specific
* to the authentication method. 'payloadlen' is the remaining length in
* the message.
+ *
+ * If *async is set to true on return, the client doesn't yet have enough
+ * information to respond, and the caller must temporarily switch to
+ * conn->async_auth() to continue driving the exchange.
*/
int
-pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
+pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn, bool *async)
{
int oldmsglen;
+ *async = false;
+
if (!check_expected_areq(areq, conn))
return STATUS_ERROR;
@@ -1176,7 +1224,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
* The request contains the name (as assigned by IANA) of the
* authentication mechanism.
*/
- if (pg_SASL_init(conn, payloadlen) != STATUS_OK)
+ if (pg_SASL_init(conn, payloadlen, async) != STATUS_OK)
{
/* pg_SASL_init already set the error message */
return STATUS_ERROR;
@@ -1185,23 +1233,33 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
- if (conn->sasl_state == NULL)
{
- appendPQExpBufferStr(&conn->errorMessage,
- "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
- return STATUS_ERROR;
- }
- oldmsglen = conn->errorMessage.len;
- if (pg_SASL_continue(conn, payloadlen,
- (areq == AUTH_REQ_SASL_FIN)) != STATUS_OK)
- {
- /* Use this message if pg_SASL_continue didn't supply one */
- if (conn->errorMessage.len == oldmsglen)
+ bool final = false;
+
+ if (conn->sasl_state == NULL)
+ {
appendPQExpBufferStr(&conn->errorMessage,
- "fe_sendauth: error in SASL authentication\n");
- return STATUS_ERROR;
+ "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n");
+ return STATUS_ERROR;
+ }
+ oldmsglen = conn->errorMessage.len;
+
+ if (areq == AUTH_REQ_SASL_FIN)
+ final = true;
+
+ if (pg_SASL_continue(conn, payloadlen, final, async) != STATUS_OK)
+ {
+ /*
+ * Append a generic error message unless pg_SASL_continue
+ * did set a more specific one already.
+ */
+ if (conn->errorMessage.len == oldmsglen)
+ appendPQExpBufferStr(&conn->errorMessage,
+ "fe_sendauth: error in SASL authentication\n");
+ return STATUS_ERROR;
+ }
+ break;
}
- break;
default:
libpq_append_conn_error(conn, "authentication method %u not supported", areq);
diff --git a/src/interfaces/libpq/fe-auth.h b/src/interfaces/libpq/fe-auth.h
index df0a68b0b21..1d4991f8996 100644
--- a/src/interfaces/libpq/fe-auth.h
+++ b/src/interfaces/libpq/fe-auth.h
@@ -19,7 +19,8 @@
/* Prototypes for functions in fe-auth.c */
-extern int pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn);
+extern int pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn,
+ bool *async);
extern char *pg_fe_getusername(uid_t user_id, PQExpBuffer errorMessage);
extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
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;
}
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index 2c60eb5b569..d78445c70af 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -1049,34 +1049,43 @@ pqWriteReady(PGconn *conn)
* or both. Returns >0 if one or more conditions are met, 0 if it timed
* out, -1 if an error occurred.
*
- * If SSL is in use, the SSL buffer is checked prior to checking the socket
- * for read data directly.
+ * If an altsock is set for asynchronous authentication, that will be used in
+ * preference to the "server" socket. Otherwise, if SSL is in use, the SSL
+ * buffer is checked prior to checking the socket for read data directly.
*/
static int
pqSocketCheck(PGconn *conn, int forRead, int forWrite, pg_usec_time_t end_time)
{
int result;
+ pgsocket sock;
if (!conn)
return -1;
- if (conn->sock == PGINVALID_SOCKET)
+
+ if (conn->altsock != PGINVALID_SOCKET)
+ sock = conn->altsock;
+ else
{
- libpq_append_conn_error(conn, "invalid socket");
- return -1;
- }
+ sock = conn->sock;
+ if (sock == PGINVALID_SOCKET)
+ {
+ libpq_append_conn_error(conn, "invalid socket");
+ return -1;
+ }
#ifdef USE_SSL
- /* Check for SSL library buffering read bytes */
- if (forRead && conn->ssl_in_use && pgtls_read_pending(conn))
- {
- /* short-circuit the select */
- return 1;
- }
+ /* Check for SSL library buffering read bytes */
+ if (forRead && conn->ssl_in_use && pgtls_read_pending(conn))
+ {
+ /* short-circuit the select */
+ return 1;
+ }
#endif
+ }
/* We will retry as long as we get EINTR */
do
- result = PQsocketPoll(conn->sock, forRead, forWrite, end_time);
+ result = PQsocketPoll(sock, forRead, forWrite, end_time);
while (result < 0 && SOCK_ERRNO == EINTR);
if (result < 0)
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index cce9ce60c55..a3491faf0c3 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -103,6 +103,8 @@ typedef enum
CONNECTION_CHECK_STANDBY, /* Checking if server is in standby mode. */
CONNECTION_ALLOCATED, /* Waiting for connection attempt to be
* started. */
+ CONNECTION_AUTHENTICATING, /* Authentication is in progress with some
+ * external system. */
} ConnStatusType;
typedef enum
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index e0d5b5fe0be..2546f9f8a50 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -513,6 +513,12 @@ struct pg_conn
* know which auth response we're
* sending */
+ /* Callbacks for external async authentication */
+ PostgresPollingStatusType (*async_auth) (PGconn *conn);
+ void (*cleanup_async_auth) (PGconn *conn);
+ pgsocket altsock; /* alternative socket for client to poll */
+
+
/* Transient state needed while establishing connection */
PGTargetServerType target_server_type; /* desired session properties */
PGLoadBalanceType load_balance_type; /* desired load balancing