1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-03 22:24:49 +03:00
postgres/src/backend/libpq/auth-sasl.c
Michael Paquier 9fd85570d1 Refactor SASL code with a generic interface for its mechanisms
The code of SCRAM and SASL have been tightly linked together since SCRAM
exists in the core code, making hard to apprehend the addition of new
SASL mechanisms, but these are by design different facilities, with
SCRAM being an option for SASL.  This refactors the code related to both
so as the backend and the frontend use a set of callbacks for SASL
mechanisms, documenting while on it what is expected by anybody adding a
new SASL mechanism.

The separation between both layers is neat, using two sets of callbacks
for the frontend and the backend to mark the frontier between both
facilities.  The shape of the callbacks is now directly inspired from
the routines used by SCRAM, so the code change is straight-forward, and
the SASL code is moved into its own set of files.  These will likely
change depending on how and if new SASL mechanisms get added in the
future.

Author: Jacob Champion
Reviewed-by: Michael Paquier
Discussion: https://postgr.es/m/3d2a6f5d50e741117d6baf83eb67ebf1a8a35a11.camel@vmware.com
2021-07-07 10:55:15 +09:00

196 lines
5.3 KiB
C

/*-------------------------------------------------------------------------
*
* auth-sasl.c
* Routines to handle authentication via SASL
*
* Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/libpq/auth-sasl.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "libpq/sasl.h"
/*
* Maximum accepted size of SASL messages.
*
* The messages that the server or libpq generate are much smaller than this,
* but have some headroom.
*/
#define PG_MAX_SASL_MESSAGE_LENGTH 1024
/*
* Perform a SASL exchange with a libpq client, using a specific mechanism
* implementation.
*
* shadow_pass is an optional pointer to the stored secret of the role
* authenticated, from pg_authid.rolpassword. For mechanisms that use
* shadowed passwords, a NULL pointer here means that an entry could not
* be found for the role (or the user does not exist), and the mechanism
* should fail the authentication exchange.
*
* Mechanisms must take care not to reveal to the client that a user entry
* does not exist; ideally, the external failure mode is identical to that
* of an incorrect password. Mechanisms may instead use the logdetail
* output parameter to internally differentiate between failure cases and
* assist debugging by the server admin.
*
* A mechanism is not required to utilize a shadow entry, or even a password
* system at all; for these cases, shadow_pass may be ignored and the caller
* should just pass NULL.
*/
int
CheckSASLAuth(const pg_be_sasl_mech *mech, Port *port, char *shadow_pass,
char **logdetail)
{
StringInfoData sasl_mechs;
int mtype;
StringInfoData buf;
void *opaq = NULL;
char *output = NULL;
int outputlen = 0;
const char *input;
int inputlen;
int result;
bool initial;
/*
* Send the SASL authentication request to user. It includes the list of
* authentication mechanisms that are supported.
*/
initStringInfo(&sasl_mechs);
mech->get_mechanisms(port, &sasl_mechs);
/* Put another '\0' to mark that list is finished. */
appendStringInfoChar(&sasl_mechs, '\0');
sendAuthRequest(port, AUTH_REQ_SASL, sasl_mechs.data, sasl_mechs.len);
pfree(sasl_mechs.data);
/*
* Loop through SASL message exchange. This exchange can consist of
* multiple messages sent in both directions. First message is always
* from the client. All messages from client to server are password
* packets (type 'p').
*/
initial = true;
do
{
pq_startmsgread();
mtype = pq_getbyte();
if (mtype != 'p')
{
/* Only log error if client didn't disconnect. */
if (mtype != EOF)
{
ereport(ERROR,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
errmsg("expected SASL response, got message type %d",
mtype)));
}
else
return STATUS_EOF;
}
/* Get the actual SASL message */
initStringInfo(&buf);
if (pq_getmessage(&buf, PG_MAX_SASL_MESSAGE_LENGTH))
{
/* EOF - pq_getmessage already logged error */
pfree(buf.data);
return STATUS_ERROR;
}
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;
selected_mech = pq_getmsgrawstring(&buf);
/*
* Initialize the status tracker for message exchanges.
*
* If the user doesn't exist, or doesn't have a valid password, or
* it's expired, we still go through the motions of SASL
* authentication, but tell the authentication method that the
* authentication is "doomed". That is, it's going to fail, no
* matter what.
*
* This is because we don't want to reveal to an attacker what
* usernames are valid, nor which users have a valid password.
*/
opaq = mech->init(port, selected_mech, shadow_pass);
inputlen = pq_getmsgint(&buf, 4);
if (inputlen == -1)
input = NULL;
else
input = pq_getmsgbytes(&buf, inputlen);
initial = false;
}
else
{
inputlen = buf.len;
input = 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');
/*
* Hand the incoming message to the mechanism implementation.
*/
result = mech->exchange(opaq, input, inputlen,
&output, &outputlen,
logdetail);
/* input buffer no longer used */
pfree(buf.data);
if (output)
{
/*
* Negotiation generated data to be sent to the client.
*/
elog(DEBUG4, "sending SASL challenge of length %u", outputlen);
if (result == PG_SASL_EXCHANGE_SUCCESS)
sendAuthRequest(port, AUTH_REQ_SASL_FIN, output, outputlen);
else
sendAuthRequest(port, AUTH_REQ_SASL_CONT, output, outputlen);
pfree(output);
}
} while (result == PG_SASL_EXCHANGE_CONTINUE);
/* Oops, Something bad happened */
if (result != PG_SASL_EXCHANGE_SUCCESS)
{
return STATUS_ERROR;
}
return STATUS_OK;
}