diff options
Diffstat (limited to 'src/backend/libpq')
| -rw-r--r-- | src/backend/libpq/auth-scram.c | 27 | ||||
| -rw-r--r-- | src/backend/libpq/auth.c | 68 |
2 files changed, 85 insertions, 10 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); } |
