diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/libpq/auth-scram.c | 27 | ||||
-rw-r--r-- | src/backend/libpq/auth.c | 68 | ||||
-rw-r--r-- | src/include/libpq/pqcomm.h | 5 | ||||
-rw-r--r-- | src/interfaces/libpq/fe-auth.c | 158 |
4 files changed, 194 insertions, 64 deletions
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c index a47c48d9805..338afede9de 100644 --- a/src/backend/libpq/auth-scram.c +++ b/src/backend/libpq/auth-scram.c @@ -254,8 +254,16 @@ pg_be_scram_init(const char *username, const char *shadow_pass) /* * Continue a SCRAM authentication exchange. * - * The next message to send to client is saved in "output", for a length - * of "outputlen". In the case of an error, optionally store a palloc'd + * 'input' is the SCRAM payload sent by the client. On the first call, + * 'input' contains the "Initial Client Response" that the client sent as + * part of the SASLInitialResponse message, or NULL if no Initial Client + * Response was given. (The SASL specification distinguishes between an + * empty response and non-existing one.) On subsequent calls, 'input' + * cannot be NULL. For convenience in this function, the caller must + * ensure that there is a null terminator at input[inputlen]. + * + * The next message to send to client is saved in 'output', for a length + * of 'outputlen'. In the case of an error, optionally store a palloc'd * string at *logdetail that will be sent to the postmaster log (but not * the client). */ @@ -269,6 +277,21 @@ pg_be_scram_exchange(void *opaq, char *input, int inputlen, *output = NULL; /* + * If the client didn't include an "Initial Client Response" in the + * SASLInitialResponse message, send an empty challenge, to which the + * client will respond with the same data that usually comes in the + * Initial Client Response. + */ + if (input == NULL) + { + Assert(state->state == SCRAM_AUTH_INIT); + + *output = pstrdup(""); + *outputlen = 0; + return SASL_EXCHANGE_CONTINUE; + } + + /* * Check that the input length agrees with the string length of the input. * We can ignore inputlen after this. */ diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index b4c98c45c9f..848561e188e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -620,10 +620,11 @@ sendAuthRequest(Port *port, AuthRequest areq, char *extradata, int extralen) pq_endmessage(&buf); /* - * Flush message so client will see it, except for AUTH_REQ_OK, which need - * not be sent until we are ready for queries. + * Flush message so client will see it, except for AUTH_REQ_OK and + * AUTH_REQ_SASL_FIN, which need not be sent until we are ready for + * queries. */ - if (areq != AUTH_REQ_OK) + if (areq != AUTH_REQ_OK && areq != AUTH_REQ_SASL_FIN) pq_flush(); CHECK_FOR_INTERRUPTS(); @@ -850,7 +851,10 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) void *scram_opaq; char *output = NULL; int outputlen = 0; + char *input; + int inputlen; int result; + bool initial; /* * SASL auth is not supported for protocol versions before 3, because it @@ -866,10 +870,13 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) errmsg("SASL authentication is not supported in protocol version 2"))); /* - * Send first the authentication request to user. + * Send the SASL authentication request to user. It includes the list of + * authentication mechanisms (which is trivial, because we only support + * SCRAM-SHA-256 at the moment). The extra "\0" is for an empty string to + * terminate the list. */ - sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME, - strlen(SCRAM_SHA256_NAME) + 1); + sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME "\0", + strlen(SCRAM_SHA256_NAME) + 2); /* * Initialize the status tracker for message exchanges. @@ -890,6 +897,7 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) * from the client. All messages from client to server are password * packets (type 'p'). */ + initial = true; do { pq_startmsgread(); @@ -921,10 +929,51 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) elog(DEBUG4, "Processing received SASL response of length %d", buf.len); /* + * The first SASLInitialResponse message is different from the others. + * It indicates which SASL mechanism the client selected, and contains + * an optional Initial Client Response payload. The subsequent + * SASLResponse messages contain just the SASL payload. + */ + if (initial) + { + const char *selected_mech; + + /* + * We only support SCRAM-SHA-256 at the moment, so anything else + * is an error. + */ + selected_mech = pq_getmsgrawstring(&buf); + if (strcmp(selected_mech, SCRAM_SHA256_NAME) != 0) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("client selected an invalid SASL authentication mechanism"))); + + inputlen = pq_getmsgint(&buf, 4); + if (inputlen == -1) + input = NULL; + else + input = (char *) pq_getmsgbytes(&buf, inputlen); + + initial = false; + } + else + { + inputlen = buf.len; + input = (char *) pq_getmsgbytes(&buf, buf.len); + } + pq_getmsgend(&buf); + + /* + * The StringInfo guarantees that there's a \0 byte after the + * response. + */ + Assert(input == NULL || input[inputlen] == '\0'); + + /* * we pass 'logdetail' as NULL when doing a mock authentication, * because we should already have a better error message in that case */ - result = pg_be_scram_exchange(scram_opaq, buf.data, buf.len, + result = pg_be_scram_exchange(scram_opaq, input, inputlen, &output, &outputlen, logdetail); @@ -938,7 +987,10 @@ CheckSCRAMAuth(Port *port, char *shadow_pass, char **logdetail) */ elog(DEBUG4, "sending SASL challenge of length %u", outputlen); - sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen); + if (result == SASL_EXCHANGE_SUCCESS) + sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen); + else + sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen); pfree(output); } diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 5441aaa93ac..b6de569c5cb 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -172,8 +172,9 @@ extern bool Db_user_namespace; #define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ #define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ #define AUTH_REQ_SSPI 9 /* SSPI negotiate without wrap() */ -#define AUTH_REQ_SASL 10 /* SASL */ -#define AUTH_REQ_SASL_CONT 11 /* continue SASL exchange */ +#define AUTH_REQ_SASL 10 /* Begin SASL authentication */ +#define AUTH_REQ_SASL_CONT 11 /* Continue SASL authentication */ +#define AUTH_REQ_SASL_FIN 12 /* Final SASL message */ typedef uint32 AuthRequest; diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c index 14e00a69e2a..d81ee4f9447 100644 --- a/src/interfaces/libpq/fe-auth.c +++ b/src/interfaces/libpq/fe-auth.c @@ -475,88 +475,129 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen) static int pg_SASL_init(PGconn *conn, int payloadlen) { - char auth_mechanism[21]; - char *initialresponse; + char *initialresponse = NULL; int initialresponselen; bool done; bool success; - int res; + const char *selected_mechanism; + PQExpBufferData mechanism_buf; - /* - * Read the authentication mechanism the server told us to use. - */ - if (payloadlen > sizeof(auth_mechanism) - 1) - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SASL authentication mechanism not supported\n")); - if (pqGetnchar(auth_mechanism, payloadlen, conn)) + initPQExpBuffer(&mechanism_buf); + + if (conn->sasl_state) { printfPQExpBuffer(&conn->errorMessage, - "fe_sendauth: invalid authentication request from server: invalid authentication mechanism\n"); - - return STATUS_ERROR; + libpq_gettext("duplicate SASL authentication request\n")); + goto error; } - auth_mechanism[payloadlen] = '\0'; /* - * Check the authentication mechanism (only SCRAM-SHA-256 is supported at - * the moment.) + * Parse the list of SASL authentication mechanisms in the + * AuthenticationSASL message, and select the best mechanism that we + * support. (Only SCRAM-SHA-256 is supported at the moment.) */ - if (strcmp(auth_mechanism, SCRAM_SHA256_NAME) == 0) + selected_mechanism = NULL; + for (;;) { - char *password; - - conn->password_needed = true; - password = conn->connhost[conn->whichhost].password; - if (password == NULL) - password = conn->pgpass; - if (password == NULL || password[0] == '\0') + if (pqGets(&mechanism_buf, conn)) { printfPQExpBuffer(&conn->errorMessage, - PQnoPasswordSupplied); - return STATUS_ERROR; + "fe_sendauth: invalid authentication request from server: invalid list of authentication mechanisms\n"); + goto error; } + if (PQExpBufferDataBroken(mechanism_buf)) + goto oom_error; + + /* An empty string indicates end of list */ + if (mechanism_buf.data[0] == '\0') + break; - conn->sasl_state = pg_fe_scram_init(conn->pguser, password); - if (!conn->sasl_state) + /* + * If we have already selected a mechanism, just skip through the rest + * of the list. + */ + if (selected_mechanism) + continue; + + /* + * Do we support this mechanism? + */ + if (strcmp(mechanism_buf.data, SCRAM_SHA256_NAME) == 0) { - printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("out of memory\n")); - return STATUS_ERROR; + char *password; + + conn->password_needed = true; + password = conn->connhost[conn->whichhost].password; + if (password == NULL) + password = conn->pgpass; + if (password == NULL || password[0] == '\0') + { + printfPQExpBuffer(&conn->errorMessage, + PQnoPasswordSupplied); + goto error; + } + + conn->sasl_state = pg_fe_scram_init(conn->pguser, password); + if (!conn->sasl_state) + goto oom_error; + selected_mechanism = SCRAM_SHA256_NAME; } } - else + + if (!selected_mechanism) { printfPQExpBuffer(&conn->errorMessage, - libpq_gettext("SASL authentication mechanism %s not supported\n"), - auth_mechanism); - return STATUS_ERROR; + libpq_gettext("none of the server's SASL authentication mechanisms are supported\n")); + goto error; } - /* Send the initial client response */ + /* Get the mechanism-specific Initial Client Response, if any */ pg_fe_scram_exchange(conn->sasl_state, NULL, -1, &initialresponse, &initialresponselen, &done, &success, &conn->errorMessage); + if (done && !success) + goto error; + + /* + * Build a SASLInitialResponse message, and send it. + */ + if (pqPutMsgStart('p', true, conn)) + goto error; + if (pqPuts(selected_mechanism, conn)) + goto error; if (initialresponse) { - res = pqPacketSend(conn, 'p', initialresponse, initialresponselen); - free(initialresponse); - - if (res != STATUS_OK) - return STATUS_ERROR; + if (pqPutInt(initialresponselen, 4, conn)) + goto error; + if (pqPutnchar(initialresponse, initialresponselen, conn)) + goto error; } + if (pqPutMsgEnd(conn)) + goto error; + if (pqFlush(conn)) + goto error; - if (done && !success) - { - /* Use error message, if set already */ - if (conn->errorMessage.len == 0) - printfPQExpBuffer(&conn->errorMessage, - "fe_sendauth: error in SASL authentication\n"); - return STATUS_ERROR; - } + termPQExpBuffer(&mechanism_buf); + if (initialresponse) + free(initialresponse); return STATUS_OK; + +error: + termPQExpBuffer(&mechanism_buf); + if (initialresponse) + free(initialresponse); + return STATUS_ERROR; + +oom_error: + termPQExpBuffer(&mechanism_buf); + if (initialresponse) + free(initialresponse); + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("out of memory\n")); + return STATUS_ERROR; } /* @@ -565,7 +606,7 @@ pg_SASL_init(PGconn *conn, int payloadlen) * the protocol. */ static int -pg_SASL_continue(PGconn *conn, int payloadlen) +pg_SASL_continue(PGconn *conn, int payloadlen, bool final) { char *output; int outputlen; @@ -598,9 +639,20 @@ pg_SASL_continue(PGconn *conn, int payloadlen) &done, &success, &conn->errorMessage); free(challenge); /* don't need the input anymore */ - /* Send the SASL response to the server, if any. */ + if (final && !done) + { + if (outputlen != 0) + free(output); + + printfPQExpBuffer(&conn->errorMessage, + libpq_gettext("AuthenticationSASLFinal received from server, but SASL authentication was not completed\n")); + return STATUS_ERROR; + } if (outputlen != 0) { + /* + * Send the SASL response to the server. + */ res = pqPacketSend(conn, 'p', output, outputlen); free(output); @@ -918,13 +970,15 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn) break; case AUTH_REQ_SASL_CONT: + case AUTH_REQ_SASL_FIN: if (conn->sasl_state == NULL) { printfPQExpBuffer(&conn->errorMessage, "fe_sendauth: invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL\n"); return STATUS_ERROR; } - if (pg_SASL_continue(conn, payloadlen) != STATUS_OK) + if (pg_SASL_continue(conn, payloadlen, + (areq == AUTH_REQ_SASL_FIN)) != STATUS_OK) { /* Use error message, if set already */ if (conn->errorMessage.len == 0) |