/*-------------------------------------------------------------------------
*
* fe - auth . c
* The front - end ( client ) authorization routines
*
* Portions Copyright ( c ) 1996 - 2025 , PostgreSQL Global Development Group
* Portions Copyright ( c ) 1994 , Regents of the University of California
*
* IDENTIFICATION
* src / interfaces / libpq / fe - auth . c
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*/
/*
* INTERFACE ROUTINES
* frontend ( client ) routines :
* pg_fe_sendauth send authentication information
* pg_fe_getauthname get user ' s name according to the client side
* of the authentication system
*/
#include "postgres_fe.h"
#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <sys/param.h> /* for MAXHOSTNAMELEN on most */
#include <sys/socket.h>
#ifdef HAVE_SYS_UCRED_H
#include <sys/ucred.h>
#endif
#ifndef MAXHOSTNAMELEN
#include <netdb.h> /* for MAXHOSTNAMELEN on some */
#endif
#endif
#include "common/md5.h"
#include "common/oauth-common.h"
#include "common/scram-common.h"
#include "fe-auth.h"
#include "fe-auth-sasl.h"
#include "fe-auth-oauth.h"
#include "libpq-fe.h"
#ifdef ENABLE_GSS
/*
* GSSAPI authentication system .
*/
#include "fe-gssapi-common.h"
/*
* Continue GSS authentication with next token as needed .
*/
static int
pg_GSS_continue(PGconn *conn, int payloadlen)
{
OM_uint32 maj_stat,
min_stat,
lmin_s,
gss_flags = GSS_C_MUTUAL_FLAG;
gss_buffer_desc ginbuf;
gss_buffer_desc goutbuf;
/*
* On first call , there ' s no input token . On subsequent calls , read the
* input token into a GSS buffer .
*/
if (conn->gctx != GSS_C_NO_CONTEXT)
{
ginbuf.length = payloadlen;
ginbuf.value = malloc(payloadlen);
if (!ginbuf.value)
{
libpq_append_conn_error(conn, "out of memory allocating GSSAPI buffer (%d)" ,
payloadlen);
return STATUS_ERROR;
}
if (pqGetnchar(ginbuf.value, payloadlen, conn))
{
/*
* Shouldn ' t happen , because the caller should ' ve ensured that the
* whole message is already in the input buffer .
*/
free(ginbuf.value);
return STATUS_ERROR;
}
}
else
{
ginbuf.length = 0 ;
ginbuf.value = NULL;
}
/* finished parsing, trace server-to-client message */
if (conn->Pfdebug)
pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false );
/* Only try to acquire credentials if GSS delegation isn't disabled. */
if (!pg_GSS_have_cred_cache(&conn->gcred))
conn->gcred = GSS_C_NO_CREDENTIAL;
if (conn->gssdelegation && conn->gssdelegation[0 ] == '1' )
gss_flags |= GSS_C_DELEG_FLAG;
maj_stat = gss_init_sec_context(&min_stat,
conn->gcred,
&conn->gctx,
conn->gtarg_nam,
GSS_C_NO_OID,
gss_flags,
0 ,
GSS_C_NO_CHANNEL_BINDINGS,
(ginbuf.value == NULL) ? GSS_C_NO_BUFFER : &ginbuf,
NULL,
&goutbuf,
NULL,
NULL);
free(ginbuf.value);
if (goutbuf.length != 0 )
{
/*
* GSS generated data to send to the server . We don ' t care if it ' s the
* first or subsequent packet , just send the same kind of password
* packet .
*/
conn->current_auth_response = AUTH_RESPONSE_GSS;
if (pqPacketSend(conn, PqMsg_GSSResponse,
goutbuf.value, goutbuf.length) != STATUS_OK)
{
gss_release_buffer(&lmin_s, &goutbuf);
return STATUS_ERROR;
}
}
gss_release_buffer(&lmin_s, &goutbuf);
if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED)
{
pg_GSS_error(libpq_gettext("GSSAPI continuation error" ),
conn,
maj_stat, min_stat);
gss_release_name(&lmin_s, &conn->gtarg_nam);
if (conn->gctx)
gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER);
return STATUS_ERROR;
}
if (maj_stat == GSS_S_COMPLETE)
{
conn->client_finished_auth = true ;
gss_release_name(&lmin_s, &conn->gtarg_nam);
conn->gssapi_used = true ;
}
return STATUS_OK;
}
/*
* Send initial GSS authentication token
*/
static int
pg_GSS_startup(PGconn *conn, int payloadlen)
{
int ret;
char *host = conn->connhost[conn->whichhost].host;
if (!(host && host[0 ] != '\0' ))
{
libpq_append_conn_error(conn, "host name must be specified" );
return STATUS_ERROR;
}
if (conn->gctx)
{
libpq_append_conn_error(conn, "duplicate GSS authentication request" );
return STATUS_ERROR;
}
ret = pg_GSS_load_servicename(conn);
if (ret != STATUS_OK)
return ret;
/*
* Initial packet is the same as a continuation packet with no initial
* context .
*/
conn->gctx = GSS_C_NO_CONTEXT;
return pg_GSS_continue(conn, payloadlen);
}
#endif /* ENABLE_GSS */
#ifdef ENABLE_SSPI
/*
* SSPI authentication system ( Windows only )
*/
static void
pg_SSPI_error(PGconn *conn, const char *mprefix, SECURITY_STATUS r)
{
char sysmsg[256 ];
if (FormatMessage(FORMAT_MESSAGE_IGNORE_INSERTS |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL, r, 0 ,
sysmsg, sizeof (sysmsg), NULL) == 0 )
appendPQExpBuffer(&conn->errorMessage, "%s: SSPI error %x\n" ,
mprefix, (unsigned int ) r);
else
appendPQExpBuffer(&conn->errorMessage, "%s: %s (%x)\n" ,
mprefix, sysmsg, (unsigned int ) r);
}
/*
* Continue SSPI authentication with next token as needed .
*/
static int
pg_SSPI_continue(PGconn *conn, int payloadlen)
{
SECURITY_STATUS r;
CtxtHandle newContext;
ULONG contextAttr;
SecBufferDesc inbuf;
SecBufferDesc outbuf;
SecBuffer OutBuffers[1 ];
SecBuffer InBuffers[1 ];
char *inputbuf = NULL;
if (conn->sspictx != NULL)
{
/*
* On runs other than the first we have some data to send . Put this
* data in a SecBuffer type structure .
*/
inputbuf = malloc(payloadlen);
if (!inputbuf)
{
libpq_append_conn_error(conn, "out of memory allocating SSPI buffer (%d)" ,
payloadlen);
return STATUS_ERROR;
}
if (pqGetnchar(inputbuf, payloadlen, conn))
{
/*
* Shouldn ' t happen , because the caller should ' ve ensured that the
* whole message is already in the input buffer .
*/
free(inputbuf);
return STATUS_ERROR;
}
inbuf.ulVersion = SECBUFFER_VERSION;
inbuf.cBuffers = 1 ;
inbuf.pBuffers = InBuffers;
InBuffers[0 ].pvBuffer = inputbuf;
InBuffers[0 ].cbBuffer = payloadlen;
InBuffers[0 ].BufferType = SECBUFFER_TOKEN;
}
/* finished parsing, trace server-to-client message */
if (conn->Pfdebug)
pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false );
OutBuffers[0 ].pvBuffer = NULL;
OutBuffers[0 ].BufferType = SECBUFFER_TOKEN;
OutBuffers[0 ].cbBuffer = 0 ;
outbuf.cBuffers = 1 ;
outbuf.pBuffers = OutBuffers;
outbuf.ulVersion = SECBUFFER_VERSION;
r = InitializeSecurityContext(conn->sspicred,
conn->sspictx,
conn->sspitarget,
ISC_REQ_ALLOCATE_MEMORY,
0 ,
SECURITY_NETWORK_DREP,
(conn->sspictx == NULL) ? NULL : &inbuf,
0 ,
&newContext,
&outbuf,
&contextAttr,
NULL);
/* we don't need the input anymore */
free(inputbuf);
if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED)
{
pg_SSPI_error(conn, libpq_gettext("SSPI continuation error" ), r);
return STATUS_ERROR;
}
if (conn->sspictx == NULL)
{
/* On first run, transfer retrieved context handle */
conn->sspictx = malloc(sizeof (CtxtHandle));
if (conn->sspictx == NULL)
{
libpq_append_conn_error(conn, "out of memory" );
return STATUS_ERROR;
}
memcpy(conn->sspictx, &newContext, sizeof (CtxtHandle));
}
/*
* If SSPI returned any data to be sent to the server ( as it normally
* would ) , send this data as a password packet .
*/
if (outbuf.cBuffers > 0 )
{
if (outbuf.cBuffers != 1 )
{
/*
* This should never happen , at least not for Kerberos
* authentication . Keep check in case it shows up with other
* authentication methods later .
*/
appendPQExpBufferStr(&conn->errorMessage,
"SSPI returned invalid number of output buffers\n" );
return STATUS_ERROR;
}
/*
* If the negotiation is complete , there may be zero bytes to send .
* The server is at this point not expecting any more data , so don ' t
* send it .
*/
if (outbuf.pBuffers[0 ].cbBuffer > 0 )
{
conn->current_auth_response = AUTH_RESPONSE_GSS;
if (pqPacketSend(conn, PqMsg_GSSResponse,
outbuf.pBuffers[0 ].pvBuffer, outbuf.pBuffers[0 ].cbBuffer))
{
FreeContextBuffer(outbuf.pBuffers[0 ].pvBuffer);
return STATUS_ERROR;
}
}
FreeContextBuffer(outbuf.pBuffers[0 ].pvBuffer);
}
if (r == SEC_E_OK)
conn->client_finished_auth = true ;
/* Cleanup is handled by the code in freePGconn() */
return STATUS_OK;
}
/*
* Send initial SSPI authentication token .
* If use_negotiate is 0 , use kerberos authentication package which is
* compatible with Unix . If use_negotiate is 1 , use the negotiate package
* which supports both kerberos and NTLM , but is not compatible with Unix .
*/
static int
pg_SSPI_startup(PGconn *conn, int use_negotiate, int payloadlen)
{
SECURITY_STATUS r;
TimeStamp expire;
char *host = conn->connhost[conn->whichhost].host;
if (conn->sspictx)
{
libpq_append_conn_error(conn, "duplicate SSPI authentication request" );
return STATUS_ERROR;
}
/*
* Retrieve credentials handle
*/
conn->sspicred = malloc(sizeof (CredHandle));
if (conn->sspicred == NULL)
{
libpq_append_conn_error(conn, "out of memory" );
return STATUS_ERROR;
}
r = AcquireCredentialsHandle(NULL,
use_negotiate ? "negotiate" : "kerberos" ,
SECPKG_CRED_OUTBOUND,
NULL,
NULL,
NULL,
NULL,
conn->sspicred,
&expire);
if (r != SEC_E_OK)
{
pg_SSPI_error(conn, libpq_gettext("could not acquire SSPI credentials" ), r);
free(conn->sspicred);
conn->sspicred = NULL;
return STATUS_ERROR;
}
/*
* Compute target principal name . SSPI has a different format from GSSAPI ,
* but not more complex . We can skip the @ REALM part , because Windows will
* fill that in for us automatically .
*/
if (!(host && host[0 ] != '\0' ))
{
libpq_append_conn_error(conn, "host name must be specified" );
return STATUS_ERROR;
}
conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(host) + 2 );
if (!conn->sspitarget)
{
libpq_append_conn_error(conn, "out of memory" );
return STATUS_ERROR;
}
sprintf(conn->sspitarget, "%s/%s" , conn->krbsrvname, host);
/*
* Indicate that we ' re in SSPI authentication mode to make sure that
* pg_SSPI_continue is called next time in the negotiation .
*/
conn->usesspi = 1 ;
return pg_SSPI_continue(conn, payloadlen);
}
#endif /* ENABLE_SSPI */
/*
* Initialize SASL authentication exchange .
*/
static int
pg_SASL_init(PGconn *conn, int payloadlen, bool *async)
{
char *initialresponse = NULL;
int initialresponselen;
const char *selected_mechanism;
PQExpBufferData mechanism_buf;
char *password = NULL;
SASLStatus status;
initPQExpBuffer(&mechanism_buf);
if (conn->channel_binding[0 ] == 'r' && /* require */
!conn->ssl_in_use)
{
libpq_append_conn_error(conn, "channel binding required, but SSL not in use" );
goto error;
}
if (conn->sasl_state && !conn->async_auth)
{
libpq_append_conn_error(conn, "duplicate SASL authentication request" );
goto error;
}
/*
* Parse the list of SASL authentication mechanisms in the
* AuthenticationSASL message , and select the best mechanism that we
* support . Mechanisms are listed by order of decreasing importance .
*/
selected_mechanism = NULL;
for (;;)
{
if (pqGets(&mechanism_buf, conn))
{
appendPQExpBufferStr(&conn->errorMessage,
"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 ;
/*
* Select the mechanism to use . Pick SCRAM - SHA - 256 - PLUS over anything
* else if a channel binding type is set and if the client supports it
* ( and did not set channel_binding = disable ) . Pick SCRAM - SHA - 256 if
* nothing else has already been picked . If we add more mechanisms , a
* more refined priority mechanism might become necessary .
*/
if (strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0 )
{
if (conn->ssl_in_use)
{
/* The server has offered SCRAM-SHA-256-PLUS. */
#ifdef USE_SSL
/*
* The client supports channel binding , which is chosen if
* channel_binding is not disabled .
*/
if (conn->channel_binding[0 ] != 'd' ) /* disable */
{
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
conn->sasl = &pg_scram_mech;
conn->password_needed = true ;
}
#else
/*
* The client does not support channel binding . If it is
* required , complain immediately instead of the error below
* which would be confusing as the server is publishing
* SCRAM - SHA - 256 - PLUS .
*/
if (conn->channel_binding[0 ] == 'r' ) /* require */
{
libpq_append_conn_error(conn, "channel binding is required, but client does not support it" );
goto error;
}
#endif
}
else
{
/*
* The server offered SCRAM - SHA - 256 - PLUS , but the connection
* is not SSL - encrypted . That ' s not sane . Perhaps SSL was
* stripped by a proxy ? There ' s no point in continuing ,
* because the server will reject the connection anyway if we
* try authenticate without channel binding even though both
* the client and server supported it . The SCRAM exchange
* checks for that , to prevent downgrade attacks .
*/
libpq_append_conn_error(conn, "server offered SCRAM-SHA-256-PLUS authentication over a non-SSL connection" );
goto error;
}
}
else if (strcmp(mechanism_buf.data, SCRAM_SHA_256_NAME) == 0 &&
!selected_mechanism)
{
selected_mechanism = SCRAM_SHA_256_NAME;
conn->sasl = &pg_scram_mech;
conn->password_needed = true ;
}
else if (strcmp(mechanism_buf.data, OAUTHBEARER_NAME) == 0 &&
!selected_mechanism)
{
selected_mechanism = OAUTHBEARER_NAME;
conn->sasl = &pg_oauth_mech;
conn->password_needed = false ;
}
}
if (!selected_mechanism)
{
libpq_append_conn_error(conn, "none of the server's SASL authentication mechanisms are supported" );
goto error;
}
/* Make sure require_auth is satisfied. */
if (conn->require_auth)
{
bool allowed = false ;
for (int i = 0 ; i < lengthof(conn->allowed_sasl_mechs); i++)
{
if (conn->sasl == conn->allowed_sasl_mechs[i])
{
allowed = true ;
break ;
}
}
if (!allowed)
{
libpq_append_conn_error(conn, "authentication method requirement \" %s\" failed: server requested %s authentication" ,
conn->require_auth, selected_mechanism);
goto error;
}
}
if (conn->channel_binding[0 ] == 'r' && /* require */
strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0 )
{
libpq_append_conn_error(conn, "channel binding is required, but server did not offer an authentication method that supports channel binding" );
goto error;
}
/*
* Now that the SASL mechanism has been chosen for the exchange ,
* initialize its state information .
*/
/*
* First , select the password to use for the exchange , complaining if
* there isn ' t one and the selected SASL mechanism needs it .
*/
if (conn->password_needed && !conn->scram_client_key_binary)
{
password = conn->connhost[conn->whichhost].password;
if (password == NULL)
password = conn->pgpass;
if (password == NULL || password[0 ] == '\0' )
{
appendPQExpBufferStr(&conn->errorMessage,
PQnoPasswordSupplied);
goto error;
}
}
/* finished parsing, trace server-to-client message */
if (conn->Pfdebug)
pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false );
Assert(conn->sasl);
if (!conn->sasl_state)
{
/*
* 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, 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 .
*/
if (pqPutMsgStart(PqMsg_SASLInitialResponse, conn))
goto error;
if (pqPuts(selected_mechanism, conn))
goto error;
if (initialresponse)
{
if (pqPutInt(initialresponselen, 4 , conn))
goto error;
if (pqPutnchar(initialresponse, initialresponselen, conn))
goto error;
}
conn->current_auth_response = AUTH_RESPONSE_SASL_INITIAL;
if (pqPutMsgEnd(conn))
goto error;
if (pqFlush(conn))
goto error;
termPQExpBuffer(&mechanism_buf);
free(initialresponse);
return STATUS_OK;
error:
termPQExpBuffer(&mechanism_buf);
free(initialresponse);
return STATUS_ERROR;
oom_error:
termPQExpBuffer(&mechanism_buf);
free(initialresponse);
libpq_append_conn_error(conn, "out of memory" );
return STATUS_ERROR;
}
/*
* Exchange a message for SASL communication protocol with the backend .
* This should be used after calling pg_SASL_init to set up the status of
* the protocol .
*/
static int
pg_SASL_continue(PGconn *conn, int payloadlen, bool final, bool *async)
{
char *output;
int outputlen;
int res;
char *challenge;
SASLStatus status;
/* Read the SASL challenge from the AuthenticationSASLContinue message. */
challenge = malloc(payloadlen + 1 );
if (!challenge)
{
libpq_append_conn_error(conn, "out of memory allocating SASL buffer (%d)" ,
payloadlen);
return STATUS_ERROR;
}
if (pqGetnchar(challenge, payloadlen, conn))
{
free(challenge);
return STATUS_ERROR;
}
/* finished parsing, trace server-to-client message */
if (conn->Pfdebug)
pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false );
/* For safety and convenience, ensure the buffer is NULL-terminated. */
challenge[payloadlen] = '\0' ;
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 )
free(output);
libpq_append_conn_error(conn, "AuthenticationSASLFinal received from server, but SASL authentication was not completed" );
return STATUS_ERROR;
}
/*
* If the exchange is not completed yet , we need to make sure that the
* SASL mechanism has generated a message to send back .
*/
if (output == NULL && status == SASL_CONTINUE)
{
libpq_append_conn_error(conn, "no client response found after SASL exchange success" );
return STATUS_ERROR;
}
/*
* SASL allows zero - length responses , so this check uses " output " and not
* " outputlen " to allow the case of an empty message .
*/
if (output)
{
/*
* Send the SASL response to the server .
*/
conn->current_auth_response = AUTH_RESPONSE_SASL;
res = pqPacketSend(conn, PqMsg_SASLResponse, output, outputlen);
free(output);
if (res != STATUS_OK)
return STATUS_ERROR;
}
if (status == SASL_FAILED)
return STATUS_ERROR;
return STATUS_OK;
}
static int
pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
{
int ret;
char *crypt_pwd = NULL;
const char *pwd_to_send;
uint8 md5Salt[4 ];
/* Read the salt from the AuthenticationMD5Password message. */
if (areq == AUTH_REQ_MD5)
{
if (pqGetnchar(md5Salt, 4 , conn))
return STATUS_ERROR; /* shouldn't happen */
}
/* finished parsing, trace server-to-client message */
if (conn->Pfdebug)
pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false );
/* Encrypt the password if needed. */
switch (areq)
{
case AUTH_REQ_MD5:
{
char *crypt_pwd2;
const char *errstr = NULL;
/* Allocate enough space for two MD5 hashes */
crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1 ));
if (!crypt_pwd)
{
libpq_append_conn_error(conn, "out of memory" );
return STATUS_ERROR;
}
crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1 ;
if (!pg_md5_encrypt(password, (uint8 *) conn->pguser,
strlen(conn->pguser), crypt_pwd2,
&errstr))
{
libpq_append_conn_error(conn, "could not encrypt password: %s" , errstr);
free(crypt_pwd);
return STATUS_ERROR;
}
if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5" ), md5Salt,
4 , crypt_pwd, &errstr))
{
libpq_append_conn_error(conn, "could not encrypt password: %s" , errstr);
free(crypt_pwd);
return STATUS_ERROR;
}
pwd_to_send = crypt_pwd;
break ;
}
case AUTH_REQ_PASSWORD:
pwd_to_send = password;
break ;
default :
return STATUS_ERROR;
}
conn->current_auth_response = AUTH_RESPONSE_PASSWORD;
ret = pqPacketSend(conn, PqMsg_PasswordMessage,
pwd_to_send, strlen(pwd_to_send) + 1 );
free(crypt_pwd);
return ret;
}
/*
* Translate a disallowed AuthRequest code into an error message .
*/
static const char *
auth_method_description(AuthRequest areq)
{
switch (areq)
{
case AUTH_REQ_PASSWORD:
return libpq_gettext("server requested a cleartext password" );
case AUTH_REQ_MD5:
return libpq_gettext("server requested a hashed password" );
case AUTH_REQ_GSS:
case AUTH_REQ_GSS_CONT:
return libpq_gettext("server requested GSSAPI authentication" );
case AUTH_REQ_SSPI:
return libpq_gettext("server requested SSPI authentication" );
case AUTH_REQ_SASL:
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
return libpq_gettext("server requested SASL authentication" );
}
return libpq_gettext("server requested an unknown authentication type" );
}
/*
* Convenience macro for checking the allowed_auth_methods bitmask . Caller
* must ensure that type is not greater than 31 ( high bit of the bitmask ) .
*/
#define auth_method_allowed(conn, type) \
(((conn)->allowed_auth_methods & (1 << (type))) != 0 )
/*
* Verify that the authentication request is expected , given the connection
* parameters . This is especially important when the client wishes to
* authenticate the server before any sensitive information is exchanged .
*/
static bool
check_expected_areq(AuthRequest areq, PGconn *conn)
{
bool result = true ;
const char *reason = NULL;
StaticAssertDecl((sizeof (conn->allowed_auth_methods) * CHAR_BIT) > AUTH_REQ_MAX,
"AUTH_REQ_MAX overflows the allowed_auth_methods bitmask" );
if (conn->sslcertmode[0 ] == 'r' /* require */
&& areq == AUTH_REQ_OK)
{
/*
* Trade off a little bit of complexity to try to get these error
* messages as precise as possible .
*/
if (!conn->ssl_cert_requested)
{
libpq_append_conn_error(conn, "server did not request an SSL certificate" );
return false ;
}
else if (!conn->ssl_cert_sent)
{
libpq_append_conn_error(conn, "server accepted connection without a valid SSL certificate" );
return false ;
}
}
/*
* If the user required a specific auth method , or specified an allowed
* set , then reject all others here , and make sure the server actually
* completes an authentication exchange .
*/
if (conn->require_auth)
{
switch (areq)
{
case AUTH_REQ_OK:
/*
* Check to make sure we ' ve actually finished our exchange ( or
* else that the user has allowed an authentication - less
* connection ) .
*
* If the user has allowed both SCRAM and unauthenticated
* ( trust ) connections , then this check will silently accept
* partial SCRAM exchanges , where a misbehaving server does
* not provide its verifier before sending an OK . This is
* consistent with historical behavior , but it may be a point
* to revisit in the future , since it could allow a server
* that doesn ' t know the user ' s password to silently harvest
* material for a brute force attack .
*/
if (!conn->auth_required || conn->client_finished_auth)
break ;
/*
* No explicit authentication request was made by the server
* - - or perhaps it was made and not completed , in the case of
* SCRAM - - but there is one special case to check . If the
* user allowed " gss " , then a GSS - encrypted channel also
* satisfies the check .
*/
#ifdef ENABLE_GSS
if (auth_method_allowed(conn, AUTH_REQ_GSS) && conn->gssenc)
{
/*
* If implicit GSS auth has already been performed via GSS
* encryption , we don ' t need to have performed an
* AUTH_REQ_GSS exchange . This allows require_auth = gss to
* be combined with gssencmode , since there won ' t be an
* explicit authentication request in that case .
*/
}
else
#endif
{
reason = libpq_gettext("server did not complete authentication" );
result = false ;
}
break ;
case AUTH_REQ_PASSWORD:
case AUTH_REQ_MD5:
case AUTH_REQ_GSS:
case AUTH_REQ_GSS_CONT:
case AUTH_REQ_SSPI:
case AUTH_REQ_SASL:
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
/*
* We don ' t handle these with the default case , to avoid
* bit - shifting past the end of the allowed_auth_methods mask
* if the server sends an unexpected AuthRequest .
*/
result = auth_method_allowed(conn, areq);
break ;
default :
result = false ;
break ;
}
}
if (!result)
{
if (!reason)
reason = auth_method_description(areq);
libpq_append_conn_error(conn, "authentication method requirement \" %s\" failed: %s" ,
conn->require_auth, reason);
return result;
}
/*
* When channel_binding = require , we must protect against two cases : ( 1 ) we
* must not respond to non - SASL authentication requests , which might leak
* information such as the client ' s password ; and ( 2 ) even if we receive
* AUTH_REQ_OK , we still must ensure that channel binding has happened in
* order to authenticate the server .
*/
if (conn->channel_binding[0 ] == 'r' /* require */ )
{
switch (areq)
{
case AUTH_REQ_SASL:
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
break ;
case AUTH_REQ_OK:
if (!conn->sasl || !conn->sasl->channel_bound(conn->sasl_state))
{
libpq_append_conn_error(conn, "channel binding required, but server authenticated client without channel binding" );
result = false ;
}
break ;
default :
libpq_append_conn_error(conn, "channel binding required but not supported by server's authentication request" );
result = false ;
break ;
}
}
return result;
}
/*
* pg_fe_sendauth
* client demux routine for processing an authentication request
*
* The server has sent us an authentication challenge ( or OK ) . Send an
* appropriate response . The caller has ensured that the whole message is
* now in the input buffer , and has already read the type and length of
* 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, bool *async)
{
int oldmsglen;
*async = false ;
if (!check_expected_areq(areq, conn))
return STATUS_ERROR;
switch (areq)
{
case AUTH_REQ_OK:
break ;
case AUTH_REQ_KRB4:
libpq_append_conn_error(conn, "Kerberos 4 authentication not supported" );
return STATUS_ERROR;
case AUTH_REQ_KRB5:
libpq_append_conn_error(conn, "Kerberos 5 authentication not supported" );
return STATUS_ERROR;
#if defined (ENABLE_GSS) || defined (ENABLE_SSPI)
case AUTH_REQ_GSS:
#if !defined (ENABLE_SSPI)
/* no native SSPI, so use GSSAPI library for it */
case AUTH_REQ_SSPI:
#endif
{
int r;
pglock_thread();
/*
* If we have both GSS and SSPI support compiled in , use SSPI
* support by default . This is overridable by a connection
* string parameter . Note that when using SSPI we still leave
* the negotiate parameter off , since we want SSPI to use the
* GSSAPI kerberos protocol . For actual SSPI negotiate
* protocol , we use AUTH_REQ_SSPI .
*/
#if defined (ENABLE_GSS) && defined (ENABLE_SSPI)
if (conn->gsslib && (pg_strcasecmp(conn->gsslib, "gssapi" ) == 0 ))
r = pg_GSS_startup(conn, payloadlen);
else
r = pg_SSPI_startup(conn, 0 , payloadlen);
#elif defined (ENABLE_GSS) && !defined (ENABLE_SSPI)
r = pg_GSS_startup(conn, payloadlen);
#elif !defined (ENABLE_GSS) && defined (ENABLE_SSPI)
r = pg_SSPI_startup(conn, 0 , payloadlen);
#endif
if (r != STATUS_OK)
{
/* Error message already filled in. */
pgunlock_thread();
return STATUS_ERROR;
}
pgunlock_thread();
}
break ;
case AUTH_REQ_GSS_CONT:
{
int r;
pglock_thread();
#if defined (ENABLE_GSS) && defined (ENABLE_SSPI)
if (conn->usesspi)
r = pg_SSPI_continue(conn, payloadlen);
else
r = pg_GSS_continue(conn, payloadlen);
#elif defined (ENABLE_GSS) && !defined (ENABLE_SSPI)
r = pg_GSS_continue(conn, payloadlen);
#elif !defined (ENABLE_GSS) && defined (ENABLE_SSPI)
r = pg_SSPI_continue(conn, payloadlen);
#endif
if (r != STATUS_OK)
{
/* Error message already filled in. */
pgunlock_thread();
return STATUS_ERROR;
}
pgunlock_thread();
}
break ;
#else /* defined(ENABLE_GSS) || defined(ENABLE_SSPI) */
/* No GSSAPI *or* SSPI support */
case AUTH_REQ_GSS:
case AUTH_REQ_GSS_CONT:
libpq_append_conn_error(conn, "GSSAPI authentication not supported" );
return STATUS_ERROR;
#endif /* defined(ENABLE_GSS) || defined(ENABLE_SSPI) */
#ifdef ENABLE_SSPI
case AUTH_REQ_SSPI:
/*
* SSPI has its own startup message so libpq can decide which
* method to use . Indicate to pg_SSPI_startup that we want SSPI
* negotiation instead of Kerberos .
*/
pglock_thread();
if (pg_SSPI_startup(conn, 1 , payloadlen) != STATUS_OK)
{
/* Error message already filled in. */
pgunlock_thread();
return STATUS_ERROR;
}
pgunlock_thread();
break ;
#else
/*
* No SSPI support . However , if we have GSSAPI but not SSPI
* support , AUTH_REQ_SSPI will have been handled in the codepath
* for AUTH_REQ_GSS above , so don ' t duplicate the case label in
* that case .
*/
#if !defined (ENABLE_GSS)
case AUTH_REQ_SSPI:
libpq_append_conn_error(conn, "SSPI authentication not supported" );
return STATUS_ERROR;
#endif /* !define(ENABLE_GSS) */
#endif /* ENABLE_SSPI */
case AUTH_REQ_CRYPT:
libpq_append_conn_error(conn, "Crypt authentication not supported" );
return STATUS_ERROR;
case AUTH_REQ_MD5:
case AUTH_REQ_PASSWORD:
{
char *password;
conn->password_needed = true ;
password = conn->connhost[conn->whichhost].password;
if (password == NULL)
password = conn->pgpass;
if (password == NULL || password[0 ] == '\0' )
{
appendPQExpBufferStr(&conn->errorMessage,
PQnoPasswordSupplied);
return STATUS_ERROR;
}
if (pg_password_sendauth(conn, password, areq) != STATUS_OK)
{
appendPQExpBufferStr(&conn->errorMessage,
"fe_sendauth: error sending password authentication\n" );
return STATUS_ERROR;
}
/* We expect no further authentication requests. */
conn->client_finished_auth = true ;
break ;
}
case AUTH_REQ_SASL:
/*
* The request contains the name ( as assigned by IANA ) of the
* authentication mechanism .
*/
if (pg_SASL_init(conn, payloadlen, async) != STATUS_OK)
{
/* pg_SASL_init already set the error message */
return STATUS_ERROR;
}
break ;
case AUTH_REQ_SASL_CONT:
case AUTH_REQ_SASL_FIN:
{
bool final = false ;
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 (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 ;
}
default :
libpq_append_conn_error(conn, "authentication method %u not supported" , areq);
return STATUS_ERROR;
}
return STATUS_OK;
}
/*
* pg_fe_getusername
*
* Returns a pointer to malloc ' d space containing the name of the
* specified user_id . If there is an error , return NULL , and append
* a suitable error message to * errorMessage if that ' s not NULL .
*
* Caution : on Windows , the user_id argument is ignored , and we always
* fetch the current user ' s name .
*/
char *
pg_fe_getusername(uid_t user_id, PQExpBuffer errorMessage)
{
char *result = NULL;
const char *name = NULL;
#ifdef WIN32
/* Microsoft recommends buffer size of UNLEN+1, where UNLEN = 256 */
char username[256 + 1 ];
DWORD namesize = sizeof (username);
#else
struct passwd pwbuf;
struct passwd *pw = NULL;
char buf[1024 ];
int rc;
#endif
#ifdef WIN32
if (GetUserName(username, &namesize))
name = username;
else if (errorMessage)
libpq_append_error(errorMessage,
"user name lookup failure: error code %lu" ,
GetLastError());
#else
rc = getpwuid_r(user_id, &pwbuf, buf, sizeof buf, &pw);
if (rc != 0 )
{
errno = rc;
if (errorMessage)
libpq_append_error(errorMessage, "could not look up local user ID %ld: %m" , (long ) user_id);
}
else if (!pw)
{
if (errorMessage)
libpq_append_error(errorMessage, "local user with ID %ld does not exist" , (long ) user_id);
}
else
name = pw->pw_name;
#endif
if (name)
{
result = strdup(name);
if (result == NULL && errorMessage)
libpq_append_error(errorMessage, "out of memory" );
}
return result;
}
/*
* pg_fe_getauthname
*
* Returns a pointer to malloc ' d space containing whatever name the user
* has authenticated to the system . If there is an error , return NULL ,
* and append a suitable error message to * errorMessage if that ' s not NULL .
*/
char *
pg_fe_getauthname(PQExpBuffer errorMessage)
{
#ifdef WIN32
return pg_fe_getusername(0 , errorMessage);
#else
return pg_fe_getusername(geteuid(), errorMessage);
#endif
}
/*
* PQencryptPassword - - exported routine to encrypt a password with MD5
*
* This function is equivalent to calling PQencryptPasswordConn with
* " md5 " as the encryption method , except that this doesn ' t require
* a connection object . This function is deprecated , use
* PQencryptPasswordConn instead .
*/
char *
PQencryptPassword(const char *passwd, const char *user)
{
char *crypt_pwd;
const char *errstr = NULL;
crypt_pwd = malloc(MD5_PASSWD_LEN + 1 );
if (!crypt_pwd)
return NULL;
if (!pg_md5_encrypt(passwd, (uint8 *) user, strlen(user), crypt_pwd, &errstr))
{
free(crypt_pwd);
return NULL;
}
return crypt_pwd;
}
/*
* PQencryptPasswordConn - - exported routine to encrypt a password
*
* This is intended to be used by client applications that wish to send
* commands like ALTER USER joe PASSWORD ' pwd ' . The password need not
* be sent in cleartext if it is encrypted on the client side . This is
* good because it ensures the cleartext password won ' t end up in logs ,
* pg_stat displays , etc . We export the function so that clients won ' t
* be dependent on low - level details like whether the encryption is MD5
* or something else .
*
* Arguments are a connection object , the cleartext password , the SQL
* name of the user it is for , and a string indicating the algorithm to
* use for encrypting the password . If algorithm is NULL , this queries
* the server for the current ' password_encryption ' value . If you wish
* to avoid that , e . g . to avoid blocking , you can execute
* ' show password_encryption ' yourself before calling this function , and
* pass it as the algorithm .
*
* Return value is a malloc ' d string . The client may assume the string
* doesn ' t contain any special characters that would require escaping .
* On error , an error message is stored in the connection object , and
* returns NULL .
*/
char *
PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user,
const char *algorithm)
{
#define MAX_ALGORITHM_NAME_LEN 50
char algobuf[MAX_ALGORITHM_NAME_LEN + 1 ];
char *crypt_pwd = NULL;
if (!conn)
return NULL;
pqClearConnErrorState(conn);
/* If no algorithm was given, ask the server. */
if (algorithm == NULL)
{
PGresult *res;
char *val;
res = PQexec(conn, "show password_encryption" );
if (res == NULL)
{
/* PQexec() should've set conn->errorMessage already */
return NULL;
}
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
/* PQexec() should've set conn->errorMessage already */
PQclear(res);
return NULL;
}
if (PQntuples(res) != 1 || PQnfields(res) != 1 )
{
PQclear(res);
libpq_append_conn_error(conn, "unexpected shape of result set returned for SHOW" );
return NULL;
}
val = PQgetvalue(res, 0 , 0 );
if (strlen(val) > MAX_ALGORITHM_NAME_LEN)
{
PQclear(res);
libpq_append_conn_error(conn, "\" password_encryption\" value too long" );
return NULL;
}
strcpy(algobuf, val);
PQclear(res);
algorithm = algobuf;
}
/*
* Also accept " on " and " off " as aliases for " md5 " , because
* password_encryption was a boolean before PostgreSQL 10 . We refuse to
* send the password in plaintext even if it was " off " .
*/
if (strcmp(algorithm, "on" ) == 0 ||
strcmp(algorithm, "off" ) == 0 )
algorithm = "md5" ;
/*
* Ok , now we know what algorithm to use
*/
if (strcmp(algorithm, "scram-sha-256" ) == 0 )
{
const char *errstr = NULL;
crypt_pwd = pg_fe_scram_build_secret(passwd,
conn->scram_sha_256_iterations,
&errstr);
if (!crypt_pwd)
libpq_append_conn_error(conn, "could not encrypt password: %s" , errstr);
}
else if (strcmp(algorithm, "md5" ) == 0 )
{
crypt_pwd = malloc(MD5_PASSWD_LEN + 1 );
if (crypt_pwd)
{
const char *errstr = NULL;
if (!pg_md5_encrypt(passwd, (uint8 *) user, strlen(user), crypt_pwd, &errstr))
{
libpq_append_conn_error(conn, "could not encrypt password: %s" , errstr);
free(crypt_pwd);
crypt_pwd = NULL;
}
}
else
libpq_append_conn_error(conn, "out of memory" );
}
else
{
libpq_append_conn_error(conn, "unrecognized password encryption algorithm \" %s\"" ,
algorithm);
return NULL;
}
return crypt_pwd;
}
/*
* PQchangePassword - - exported routine to change a password
*
* This is intended to be used by client applications that wish to
* change the password for a user . The password is not sent in
* cleartext because it is encrypted on the client side . This is
* good because it ensures the cleartext password is never known by
* the server , and therefore won ' t end up in logs , pg_stat displays ,
* etc . The password encryption is performed by PQencryptPasswordConn ( ) ,
* which is passed a NULL for the algorithm argument . Hence encryption
* is done according to the server ' s password_encryption
* setting . We export the function so that clients won ' t be dependent
* on the implementation specific details with respect to how the
* server changes passwords .
*
* Arguments are a connection object , the SQL name of the target user ,
* and the cleartext password .
*
* Return value is the PGresult of the executed ALTER USER statement
* or NULL if we never get there . The caller is responsible to PQclear ( )
* the returned PGresult .
*
* PQresultStatus ( ) should be called to check the return value for errors ,
* and PQerrorMessage ( ) used to get more information about such errors .
*/
PGresult *
PQchangePassword(PGconn *conn, const char *user, const char *passwd)
{
char *encrypted_password = PQencryptPasswordConn(conn, passwd,
user, NULL);
if (!encrypted_password)
{
/* PQencryptPasswordConn() already registered the error */
return NULL;
}
else
{
char *fmtpw = PQescapeLiteral(conn, encrypted_password,
strlen(encrypted_password));
/* no longer needed, so clean up now */
PQfreemem(encrypted_password);
if (!fmtpw)
{
/* PQescapeLiteral() already registered the error */
return NULL;
}
else
{
char *fmtuser = PQescapeIdentifier(conn, user, strlen(user));
if (!fmtuser)
{
/* PQescapeIdentifier() already registered the error */
PQfreemem(fmtpw);
return NULL;
}
else
{
PQExpBufferData buf;
PGresult *res;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD %s" ,
fmtuser, fmtpw);
res = PQexec(conn, buf.data);
/* clean up */
termPQExpBuffer(&buf);
PQfreemem(fmtuser);
PQfreemem(fmtpw);
return res;
}
}
}
}
PQauthDataHook_type PQauthDataHook = PQdefaultAuthDataHook;
PQauthDataHook_type
PQgetAuthDataHook(void )
{
return PQauthDataHook;
}
void
PQsetAuthDataHook(PQauthDataHook_type hook)
{
PQauthDataHook = hook ? hook : PQdefaultAuthDataHook;
}
int
PQdefaultAuthDataHook(PGauthData type, PGconn *conn, void *data)
{
return 0 ; /* handle nothing */
}
Messung V0.5 in Prozent C=94 H=92 G=92
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.13Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-08-08)
¤
*Eine klare Vorstellung vom Zielzustand