mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-09-06 16:39:22 +02:00
now always use the system username as the default, and not try to pick it up from the kerberos ticket. This fixes the spurious error messages that show up on kerberos-enabled builds when not actually using kerberos, and puts it in line with how other authentication methods work.
1029 lines
26 KiB
C
1029 lines
26 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* fe-auth.c
|
|
* The front-end (client) authorization routines
|
|
*
|
|
* Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/interfaces/libpq/fe-auth.c,v 1.140 2009/01/13 10:43:21 mha Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
/*
|
|
* 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 <sys/types.h>
|
|
#include <sys/param.h> /* for MAXHOSTNAMELEN on most */
|
|
#include <sys/socket.h>
|
|
#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
|
|
#include <sys/uio.h>
|
|
#include <sys/ucred.h>
|
|
#endif
|
|
#ifndef MAXHOSTNAMELEN
|
|
#include <netdb.h> /* for MAXHOSTNAMELEN on some */
|
|
#endif
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
#include "libpq-fe.h"
|
|
#include "fe-auth.h"
|
|
#include "libpq/md5.h"
|
|
|
|
|
|
#ifdef KRB5
|
|
/*
|
|
* MIT Kerberos authentication system - protocol version 5
|
|
*/
|
|
|
|
#include <krb5.h>
|
|
/* Some old versions of Kerberos do not include <com_err.h> in <krb5.h> */
|
|
#if !defined(__COM_ERR_H) && !defined(__COM_ERR_H__)
|
|
#include <com_err.h>
|
|
#endif
|
|
|
|
/*
|
|
* Heimdal doesn't have a free function for unparsed names. Just pass it to
|
|
* standard free() which should work in these cases.
|
|
*/
|
|
#ifndef HAVE_KRB5_FREE_UNPARSED_NAME
|
|
static void
|
|
krb5_free_unparsed_name(krb5_context context, char *val)
|
|
{
|
|
free(val);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* pg_an_to_ln -- return the local name corresponding to an authentication
|
|
* name
|
|
*
|
|
* XXX Assumes that the first aname component is the user name. This is NOT
|
|
* necessarily so, since an aname can actually be something out of your
|
|
* worst X.400 nightmare, like
|
|
* ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
|
|
* Note that the MIT an_to_ln code does the same thing if you don't
|
|
* provide an aname mapping database...it may be a better idea to use
|
|
* krb5_an_to_ln, except that it punts if multiple components are found,
|
|
* and we can't afford to punt.
|
|
*
|
|
* For WIN32, convert username to lowercase because the Win32 kerberos library
|
|
* generates tickets with the username as the user entered it instead of as
|
|
* it is entered in the directory.
|
|
*/
|
|
static char *
|
|
pg_an_to_ln(char *aname)
|
|
{
|
|
char *p;
|
|
|
|
if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
|
|
*p = '\0';
|
|
#ifdef WIN32
|
|
for (p = aname; *p; p++)
|
|
*p = pg_tolower((unsigned char) *p);
|
|
#endif
|
|
|
|
return aname;
|
|
}
|
|
|
|
|
|
/*
|
|
* Various krb5 state which is not connection specific, and a flag to
|
|
* indicate whether we have initialised it yet.
|
|
*/
|
|
/*
|
|
static int pg_krb5_initialised;
|
|
static krb5_context pg_krb5_context;
|
|
static krb5_ccache pg_krb5_ccache;
|
|
static krb5_principal pg_krb5_client;
|
|
static char *pg_krb5_name;
|
|
*/
|
|
|
|
struct krb5_info
|
|
{
|
|
int pg_krb5_initialised;
|
|
krb5_context pg_krb5_context;
|
|
krb5_ccache pg_krb5_ccache;
|
|
krb5_principal pg_krb5_client;
|
|
char *pg_krb5_name;
|
|
};
|
|
|
|
|
|
static int
|
|
pg_krb5_init(PQExpBuffer errorMessage, struct krb5_info * info)
|
|
{
|
|
krb5_error_code retval;
|
|
|
|
if (info->pg_krb5_initialised)
|
|
return STATUS_OK;
|
|
|
|
retval = krb5_init_context(&(info->pg_krb5_context));
|
|
if (retval)
|
|
{
|
|
printfPQExpBuffer(errorMessage,
|
|
"pg_krb5_init: krb5_init_context: %s\n",
|
|
error_message(retval));
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
retval = krb5_cc_default(info->pg_krb5_context, &(info->pg_krb5_ccache));
|
|
if (retval)
|
|
{
|
|
printfPQExpBuffer(errorMessage,
|
|
"pg_krb5_init: krb5_cc_default: %s\n",
|
|
error_message(retval));
|
|
krb5_free_context(info->pg_krb5_context);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
retval = krb5_cc_get_principal(info->pg_krb5_context, info->pg_krb5_ccache,
|
|
&(info->pg_krb5_client));
|
|
if (retval)
|
|
{
|
|
printfPQExpBuffer(errorMessage,
|
|
"pg_krb5_init: krb5_cc_get_principal: %s\n",
|
|
error_message(retval));
|
|
krb5_cc_close(info->pg_krb5_context, info->pg_krb5_ccache);
|
|
krb5_free_context(info->pg_krb5_context);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
retval = krb5_unparse_name(info->pg_krb5_context, info->pg_krb5_client, &(info->pg_krb5_name));
|
|
if (retval)
|
|
{
|
|
printfPQExpBuffer(errorMessage,
|
|
"pg_krb5_init: krb5_unparse_name: %s\n",
|
|
error_message(retval));
|
|
krb5_free_principal(info->pg_krb5_context, info->pg_krb5_client);
|
|
krb5_cc_close(info->pg_krb5_context, info->pg_krb5_ccache);
|
|
krb5_free_context(info->pg_krb5_context);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
info->pg_krb5_name = pg_an_to_ln(info->pg_krb5_name);
|
|
|
|
info->pg_krb5_initialised = 1;
|
|
return STATUS_OK;
|
|
}
|
|
|
|
static void
|
|
pg_krb5_destroy(struct krb5_info * info)
|
|
{
|
|
krb5_free_principal(info->pg_krb5_context, info->pg_krb5_client);
|
|
krb5_cc_close(info->pg_krb5_context, info->pg_krb5_ccache);
|
|
krb5_free_unparsed_name(info->pg_krb5_context, info->pg_krb5_name);
|
|
krb5_free_context(info->pg_krb5_context);
|
|
}
|
|
|
|
|
|
/*
|
|
* pg_krb5_sendauth -- client routine to send authentication information to
|
|
* the server
|
|
*/
|
|
static int
|
|
pg_krb5_sendauth(PGconn *conn)
|
|
{
|
|
krb5_error_code retval;
|
|
int ret;
|
|
krb5_principal server;
|
|
krb5_auth_context auth_context = NULL;
|
|
krb5_error *err_ret = NULL;
|
|
struct krb5_info info;
|
|
|
|
info.pg_krb5_initialised = 0;
|
|
|
|
if (!conn->pghost)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
"pg_krb5_sendauth: hostname must be specified for Kerberos authentication\n");
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
ret = pg_krb5_init(&conn->errorMessage, &info);
|
|
if (ret != STATUS_OK)
|
|
return ret;
|
|
|
|
retval = krb5_sname_to_principal(info.pg_krb5_context, conn->pghost,
|
|
conn->krbsrvname,
|
|
KRB5_NT_SRV_HST, &server);
|
|
if (retval)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
"pg_krb5_sendauth: krb5_sname_to_principal: %s\n",
|
|
error_message(retval));
|
|
pg_krb5_destroy(&info);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/*
|
|
* libpq uses a non-blocking socket. But kerberos needs a blocking socket,
|
|
* and we have to block somehow to do mutual authentication anyway. So we
|
|
* temporarily make it blocking.
|
|
*/
|
|
if (!pg_set_block(conn->sock))
|
|
{
|
|
char sebuf[256];
|
|
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("could not set socket to blocking mode: %s\n"), pqStrerror(errno, sebuf, sizeof(sebuf)));
|
|
krb5_free_principal(info.pg_krb5_context, server);
|
|
pg_krb5_destroy(&info);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
retval = krb5_sendauth(info.pg_krb5_context, &auth_context,
|
|
(krb5_pointer) & conn->sock, (char *) conn->krbsrvname,
|
|
info.pg_krb5_client, server,
|
|
AP_OPTS_MUTUAL_REQUIRED,
|
|
NULL, 0, /* no creds, use ccache instead */
|
|
info.pg_krb5_ccache, &err_ret, NULL, NULL);
|
|
if (retval)
|
|
{
|
|
if (retval == KRB5_SENDAUTH_REJECTED && err_ret)
|
|
{
|
|
#if defined(HAVE_KRB5_ERROR_TEXT_DATA)
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("Kerberos 5 authentication rejected: %*s\n"),
|
|
(int) err_ret->text.length, err_ret->text.data);
|
|
#elif defined(HAVE_KRB5_ERROR_E_DATA)
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("Kerberos 5 authentication rejected: %*s\n"),
|
|
(int) err_ret->e_data->length,
|
|
(const char *) err_ret->e_data->data);
|
|
#else
|
|
#error "bogus configuration"
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
"krb5_sendauth: %s\n", error_message(retval));
|
|
}
|
|
|
|
if (err_ret)
|
|
krb5_free_error(info.pg_krb5_context, err_ret);
|
|
|
|
ret = STATUS_ERROR;
|
|
}
|
|
|
|
krb5_free_principal(info.pg_krb5_context, server);
|
|
|
|
if (!pg_set_noblock(conn->sock))
|
|
{
|
|
char sebuf[256];
|
|
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("could not restore non-blocking mode on socket: %s\n"),
|
|
pqStrerror(errno, sebuf, sizeof(sebuf)));
|
|
ret = STATUS_ERROR;
|
|
}
|
|
pg_krb5_destroy(&info);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* KRB5 */
|
|
|
|
#ifdef ENABLE_GSS
|
|
/*
|
|
* GSSAPI authentication system.
|
|
*/
|
|
|
|
#if defined(WIN32) && !defined(WIN32_ONLY_COMPILER)
|
|
/*
|
|
* MIT Kerberos GSSAPI DLL doesn't properly export the symbols for MingW
|
|
* that contain the OIDs required. Redefine here, values copied
|
|
* from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c
|
|
*/
|
|
static const gss_OID_desc GSS_C_NT_HOSTBASED_SERVICE_desc =
|
|
{10, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"};
|
|
static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc;
|
|
#endif
|
|
|
|
/*
|
|
* Fetch all errors of a specific type and append to "str".
|
|
*/
|
|
static void
|
|
pg_GSS_error_int(PQExpBuffer str, const char *mprefix,
|
|
OM_uint32 stat, int type)
|
|
{
|
|
OM_uint32 lmaj_s,
|
|
lmin_s;
|
|
gss_buffer_desc lmsg;
|
|
OM_uint32 msg_ctx = 0;
|
|
|
|
do
|
|
{
|
|
lmaj_s = gss_display_status(&lmin_s, stat, type,
|
|
GSS_C_NO_OID, &msg_ctx, &lmsg);
|
|
appendPQExpBuffer(str, "%s: %s\n", mprefix, (char *) lmsg.value);
|
|
gss_release_buffer(&lmin_s, &lmsg);
|
|
} while (msg_ctx);
|
|
}
|
|
|
|
/*
|
|
* GSSAPI errors contain two parts; put both into conn->errorMessage.
|
|
*/
|
|
static void
|
|
pg_GSS_error(const char *mprefix, PGconn *conn,
|
|
OM_uint32 maj_stat, OM_uint32 min_stat)
|
|
{
|
|
resetPQExpBuffer(&conn->errorMessage);
|
|
|
|
/* Fetch major error codes */
|
|
pg_GSS_error_int(&conn->errorMessage, mprefix, maj_stat, GSS_C_GSS_CODE);
|
|
|
|
/* Add the minor codes as well */
|
|
pg_GSS_error_int(&conn->errorMessage, mprefix, min_stat, GSS_C_MECH_CODE);
|
|
}
|
|
|
|
/*
|
|
* Continue GSS authentication with next token as needed.
|
|
*/
|
|
static int
|
|
pg_GSS_continue(PGconn *conn)
|
|
{
|
|
OM_uint32 maj_stat,
|
|
min_stat,
|
|
lmin_s;
|
|
|
|
maj_stat = gss_init_sec_context(&min_stat,
|
|
GSS_C_NO_CREDENTIAL,
|
|
&conn->gctx,
|
|
conn->gtarg_nam,
|
|
GSS_C_NO_OID,
|
|
GSS_C_MUTUAL_FLAG,
|
|
0,
|
|
GSS_C_NO_CHANNEL_BINDINGS,
|
|
(conn->gctx == GSS_C_NO_CONTEXT) ? GSS_C_NO_BUFFER : &conn->ginbuf,
|
|
NULL,
|
|
&conn->goutbuf,
|
|
NULL,
|
|
NULL);
|
|
|
|
if (conn->gctx != GSS_C_NO_CONTEXT)
|
|
{
|
|
free(conn->ginbuf.value);
|
|
conn->ginbuf.value = NULL;
|
|
conn->ginbuf.length = 0;
|
|
}
|
|
|
|
if (conn->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.
|
|
*/
|
|
if (pqPacketSend(conn, 'p',
|
|
conn->goutbuf.value, conn->goutbuf.length)
|
|
!= STATUS_OK)
|
|
{
|
|
gss_release_buffer(&lmin_s, &conn->goutbuf);
|
|
return STATUS_ERROR;
|
|
}
|
|
}
|
|
gss_release_buffer(&lmin_s, &conn->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)
|
|
gss_release_name(&lmin_s, &conn->gtarg_nam);
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
/*
|
|
* Send initial GSS authentication token
|
|
*/
|
|
static int
|
|
pg_GSS_startup(PGconn *conn)
|
|
{
|
|
OM_uint32 maj_stat,
|
|
min_stat;
|
|
int maxlen;
|
|
gss_buffer_desc temp_gbuf;
|
|
|
|
if (conn->gctx)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("duplicate GSS authentication request\n"));
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Import service principal name so the proper ticket can be acquired by
|
|
* the GSSAPI system.
|
|
*/
|
|
maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2;
|
|
temp_gbuf.value = (char *) malloc(maxlen);
|
|
snprintf(temp_gbuf.value, maxlen, "%s@%s",
|
|
conn->krbsrvname, conn->pghost);
|
|
temp_gbuf.length = strlen(temp_gbuf.value);
|
|
|
|
maj_stat = gss_import_name(&min_stat, &temp_gbuf,
|
|
GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam);
|
|
free(temp_gbuf.value);
|
|
|
|
if (maj_stat != GSS_S_COMPLETE)
|
|
{
|
|
pg_GSS_error(libpq_gettext("GSSAPI name import error"),
|
|
conn,
|
|
maj_stat, min_stat);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Initial packet is the same as a continuation packet with no initial
|
|
* context.
|
|
*/
|
|
conn->gctx = GSS_C_NO_CONTEXT;
|
|
|
|
return pg_GSS_continue(conn);
|
|
}
|
|
#endif /* ENABLE_GSS */
|
|
|
|
|
|
#ifdef ENABLE_SSPI
|
|
/*
|
|
* SSPI authentication system (Windows only)
|
|
*/
|
|
|
|
static void
|
|
pg_SSPI_error(PGconn *conn, char *mprefix, SECURITY_STATUS r)
|
|
{
|
|
char sysmsg[256];
|
|
|
|
if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, r, 0,
|
|
sysmsg, sizeof(sysmsg), NULL) == 0)
|
|
printfPQExpBuffer(&conn->errorMessage, "%s: sspi error %x",
|
|
mprefix, (unsigned int) r);
|
|
else
|
|
printfPQExpBuffer(&conn->errorMessage, "%s: %s (%x)",
|
|
mprefix, sysmsg, (unsigned int) r);
|
|
}
|
|
|
|
/*
|
|
* Continue SSPI authentication with next token as needed.
|
|
*/
|
|
static int
|
|
pg_SSPI_continue(PGconn *conn)
|
|
{
|
|
SECURITY_STATUS r;
|
|
CtxtHandle newContext;
|
|
ULONG contextAttr;
|
|
SecBufferDesc inbuf;
|
|
SecBufferDesc outbuf;
|
|
SecBuffer OutBuffers[1];
|
|
SecBuffer InBuffers[1];
|
|
|
|
if (conn->sspictx != NULL)
|
|
{
|
|
/*
|
|
* On runs other than the first we have some data to send. Put this
|
|
* data in a SecBuffer type structure.
|
|
*/
|
|
inbuf.ulVersion = SECBUFFER_VERSION;
|
|
inbuf.cBuffers = 1;
|
|
inbuf.pBuffers = InBuffers;
|
|
InBuffers[0].pvBuffer = conn->ginbuf.value;
|
|
InBuffers[0].cbBuffer = conn->ginbuf.length;
|
|
InBuffers[0].BufferType = SECBUFFER_TOKEN;
|
|
}
|
|
|
|
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);
|
|
|
|
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 retreived context handle */
|
|
conn->sspictx = malloc(sizeof(CtxtHandle));
|
|
if (conn->sspictx == NULL)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n"));
|
|
return STATUS_ERROR;
|
|
}
|
|
memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle));
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* On subsequent runs when we had data to send, free buffers that
|
|
* contained this data.
|
|
*/
|
|
free(conn->ginbuf.value);
|
|
conn->ginbuf.value = NULL;
|
|
conn->ginbuf.length = 0;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
printfPQExpBuffer(&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)
|
|
{
|
|
if (pqPacketSend(conn, 'p',
|
|
outbuf.pBuffers[0].pvBuffer, outbuf.pBuffers[0].cbBuffer))
|
|
{
|
|
FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
|
|
return STATUS_ERROR;
|
|
}
|
|
}
|
|
FreeContextBuffer(outbuf.pBuffers[0].pvBuffer);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
SECURITY_STATUS r;
|
|
TimeStamp expire;
|
|
|
|
conn->sspictx = NULL;
|
|
|
|
/*
|
|
* Retreive credentials handle
|
|
*/
|
|
conn->sspicred = malloc(sizeof(CredHandle));
|
|
if (conn->sspicred == NULL)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n"));
|
|
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, "acquire credentials failed", 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 (conn->pghost == NULL)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage, libpq_gettext("host name must be specified\n"));
|
|
return STATUS_ERROR;
|
|
}
|
|
conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(conn->pghost) + 2);
|
|
if (!conn->sspitarget)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n"));
|
|
return STATUS_ERROR;
|
|
}
|
|
sprintf(conn->sspitarget, "%s/%s", conn->krbsrvname, conn->pghost);
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
#endif /* ENABLE_SSPI */
|
|
|
|
/*
|
|
* Respond to AUTH_REQ_SCM_CREDS challenge.
|
|
*
|
|
* Note: current backends will not use this challenge if HAVE_GETPEEREID
|
|
* or SO_PEERCRED is defined, but pre-7.4 backends might, so compile the
|
|
* code anyway.
|
|
*/
|
|
static int
|
|
pg_local_sendauth(PGconn *conn)
|
|
{
|
|
#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || \
|
|
(defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
|
|
char buf;
|
|
struct iovec iov;
|
|
struct msghdr msg;
|
|
|
|
#ifdef HAVE_STRUCT_CMSGCRED
|
|
/* Prevent padding */
|
|
char cmsgmem[sizeof(struct cmsghdr) + sizeof(struct cmsgcred)];
|
|
|
|
/* Point to start of first structure */
|
|
struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem;
|
|
#endif
|
|
|
|
/*
|
|
* The backend doesn't care what we send here, but it wants exactly one
|
|
* character to force recvmsg() to block and wait for us.
|
|
*/
|
|
buf = '\0';
|
|
iov.iov_base = &buf;
|
|
iov.iov_len = 1;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
|
|
#ifdef HAVE_STRUCT_CMSGCRED
|
|
/* Create control header, FreeBSD */
|
|
msg.msg_control = cmsg;
|
|
msg.msg_controllen = sizeof(cmsgmem);
|
|
memset(cmsg, 0, sizeof(cmsgmem));
|
|
cmsg->cmsg_len = sizeof(cmsgmem);
|
|
cmsg->cmsg_level = SOL_SOCKET;
|
|
cmsg->cmsg_type = SCM_CREDS;
|
|
#endif
|
|
|
|
if (sendmsg(conn->sock, &msg, 0) == -1)
|
|
{
|
|
char sebuf[256];
|
|
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
"pg_local_sendauth: sendmsg: %s\n",
|
|
pqStrerror(errno, sebuf, sizeof(sebuf)));
|
|
return STATUS_ERROR;
|
|
}
|
|
return STATUS_OK;
|
|
#else
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("SCM_CRED authentication method not supported\n"));
|
|
return STATUS_ERROR;
|
|
#endif
|
|
}
|
|
|
|
static int
|
|
pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
|
|
{
|
|
int ret;
|
|
char *crypt_pwd;
|
|
|
|
/* Encrypt the password if needed. */
|
|
|
|
switch (areq)
|
|
{
|
|
case AUTH_REQ_MD5:
|
|
{
|
|
char *crypt_pwd2;
|
|
|
|
/* Allocate enough space for two MD5 hashes */
|
|
crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1));
|
|
if (!crypt_pwd)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("out of memory\n"));
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1;
|
|
if (!pg_md5_encrypt(password, conn->pguser,
|
|
strlen(conn->pguser), crypt_pwd2))
|
|
{
|
|
free(crypt_pwd);
|
|
return STATUS_ERROR;
|
|
}
|
|
if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), conn->md5Salt,
|
|
sizeof(conn->md5Salt), crypt_pwd))
|
|
{
|
|
free(crypt_pwd);
|
|
return STATUS_ERROR;
|
|
}
|
|
break;
|
|
}
|
|
case AUTH_REQ_PASSWORD:
|
|
/* discard const so we can assign it */
|
|
crypt_pwd = (char *) password;
|
|
break;
|
|
default:
|
|
return STATUS_ERROR;
|
|
}
|
|
/* Packet has a message type as of protocol 3.0 */
|
|
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
|
|
ret = pqPacketSend(conn, 'p', crypt_pwd, strlen(crypt_pwd) + 1);
|
|
else
|
|
ret = pqPacketSend(conn, 0, crypt_pwd, strlen(crypt_pwd) + 1);
|
|
if (areq == AUTH_REQ_MD5)
|
|
free(crypt_pwd);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* pg_fe_sendauth
|
|
* client demux routine for outgoing authentication information
|
|
*/
|
|
int
|
|
pg_fe_sendauth(AuthRequest areq, PGconn *conn)
|
|
{
|
|
switch (areq)
|
|
{
|
|
case AUTH_REQ_OK:
|
|
break;
|
|
|
|
case AUTH_REQ_KRB4:
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("Kerberos 4 authentication not supported\n"));
|
|
return STATUS_ERROR;
|
|
|
|
case AUTH_REQ_KRB5:
|
|
#ifdef KRB5
|
|
pglock_thread();
|
|
if (pg_krb5_sendauth(conn) != STATUS_OK)
|
|
{
|
|
/* Error message already filled in */
|
|
pgunlock_thread();
|
|
return STATUS_ERROR;
|
|
}
|
|
pgunlock_thread();
|
|
break;
|
|
#else
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("Kerberos 5 authentication not supported\n"));
|
|
return STATUS_ERROR;
|
|
#endif
|
|
|
|
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
|
|
case AUTH_REQ_GSS:
|
|
{
|
|
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);
|
|
else
|
|
r = pg_SSPI_startup(conn, 0);
|
|
#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI)
|
|
r = pg_GSS_startup(conn);
|
|
#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI)
|
|
r = pg_SSPI_startup(conn, 0);
|
|
#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);
|
|
else
|
|
r = pg_GSS_continue(conn);
|
|
#elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI)
|
|
r = pg_GSS_continue(conn);
|
|
#elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI)
|
|
r = pg_SSPI_continue(conn);
|
|
#endif
|
|
if (r != STATUS_OK)
|
|
{
|
|
/* Error message already filled in. */
|
|
pgunlock_thread();
|
|
return STATUS_ERROR;
|
|
}
|
|
pgunlock_thread();
|
|
}
|
|
break;
|
|
#else
|
|
case AUTH_REQ_GSS:
|
|
case AUTH_REQ_GSS_CONT:
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("GSSAPI authentication not supported\n"));
|
|
return STATUS_ERROR;
|
|
#endif
|
|
|
|
#ifdef ENABLE_SSPI
|
|
case AUTH_REQ_SSPI:
|
|
|
|
/*
|
|
* SSPI has it's 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) != STATUS_OK)
|
|
{
|
|
/* Error message already filled in. */
|
|
pgunlock_thread();
|
|
return STATUS_ERROR;
|
|
}
|
|
pgunlock_thread();
|
|
break;
|
|
#else
|
|
case AUTH_REQ_SSPI:
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("SSPI authentication not supported\n"));
|
|
return STATUS_ERROR;
|
|
#endif
|
|
|
|
|
|
case AUTH_REQ_CRYPT:
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("Crypt authentication not supported\n"));
|
|
return STATUS_ERROR;
|
|
|
|
case AUTH_REQ_MD5:
|
|
case AUTH_REQ_PASSWORD:
|
|
conn->password_needed = true;
|
|
if (conn->pgpass == NULL || conn->pgpass[0] == '\0')
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
PQnoPasswordSupplied);
|
|
return STATUS_ERROR;
|
|
}
|
|
if (pg_password_sendauth(conn, conn->pgpass, areq) != STATUS_OK)
|
|
{
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
"fe_sendauth: error sending password authentication\n");
|
|
return STATUS_ERROR;
|
|
}
|
|
break;
|
|
|
|
case AUTH_REQ_SCM_CREDS:
|
|
if (pg_local_sendauth(conn) != STATUS_OK)
|
|
return STATUS_ERROR;
|
|
break;
|
|
|
|
default:
|
|
printfPQExpBuffer(&conn->errorMessage,
|
|
libpq_gettext("authentication method %u not supported\n"), areq);
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
return STATUS_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
* pg_fe_getauthname -- returns a pointer to dynamic space containing whatever
|
|
* name the user has authenticated to the system
|
|
*
|
|
* if there is an error, return NULL with an error message in errorMessage
|
|
*/
|
|
char *
|
|
pg_fe_getauthname(PQExpBuffer errorMessage)
|
|
{
|
|
const char *name = NULL;
|
|
char *authn;
|
|
|
|
#ifdef WIN32
|
|
char username[128];
|
|
DWORD namesize = sizeof(username) - 1;
|
|
#else
|
|
char pwdbuf[BUFSIZ];
|
|
struct passwd pwdstr;
|
|
struct passwd *pw = NULL;
|
|
#endif
|
|
|
|
/*
|
|
* Some users are using configure
|
|
* --enable-thread-safety-force, so we might as well do the locking within
|
|
* our library to protect pqGetpwuid(). In fact, application developers
|
|
* can use getpwuid() in their application if they use the locking call we
|
|
* provide, or install their own locking function using
|
|
* PQregisterThreadLock().
|
|
*/
|
|
pglock_thread();
|
|
|
|
if (!name)
|
|
{
|
|
#ifdef WIN32
|
|
if (GetUserName(username, &namesize))
|
|
name = username;
|
|
#else
|
|
if (pqGetpwuid(geteuid(), &pwdstr, pwdbuf, sizeof(pwdbuf), &pw) == 0)
|
|
name = pw->pw_name;
|
|
#endif
|
|
}
|
|
|
|
authn = name ? strdup(name) : NULL;
|
|
|
|
pgunlock_thread();
|
|
|
|
return authn;
|
|
}
|
|
|
|
|
|
/*
|
|
* PQencryptPassword -- 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 enceyption is MD5
|
|
* or something else.
|
|
*
|
|
* Arguments are the cleartext password, and the SQL name of the user it
|
|
* is for.
|
|
*
|
|
* Return value is a malloc'd string, or NULL if out-of-memory. The client
|
|
* may assume the string doesn't contain any special characters that would
|
|
* require escaping.
|
|
*/
|
|
char *
|
|
PQencryptPassword(const char *passwd, const char *user)
|
|
{
|
|
char *crypt_pwd;
|
|
|
|
crypt_pwd = malloc(MD5_PASSWD_LEN + 1);
|
|
if (!crypt_pwd)
|
|
return NULL;
|
|
|
|
if (!pg_md5_encrypt(passwd, user, strlen(user), crypt_pwd))
|
|
{
|
|
free(crypt_pwd);
|
|
return NULL;
|
|
}
|
|
|
|
return crypt_pwd;
|
|
}
|