1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-07 00:36:50 +03:00

GSSAPI encryption support

On both the frontend and backend, prepare for GSSAPI encryption
support by moving common code for error handling into a separate file.
Fix a TODO for handling multiple status messages in the process.
Eliminate the OIDs, which have not been needed for some time.

Add frontend and backend encryption support functions.  Keep the
context initiation for authentication-only separate on both the
frontend and backend in order to avoid concerns about changing the
requested flags to include encryption support.

In postmaster, pull GSSAPI authorization checking into a shared
function.  Also share the initiator name between the encryption and
non-encryption codepaths.

For HBA, add "hostgssenc" and "hostnogssenc" entries that behave
similarly to their SSL counterparts.  "hostgssenc" requires either
"gss", "trust", or "reject" for its authentication.

Similarly, add a "gssencmode" parameter to libpq.  Supported values are
"disable", "require", and "prefer".  Notably, negotiation will only be
attempted if credentials can be acquired.  Move credential acquisition
into its own function to support this behavior.

Add a simple pg_stat_gssapi view similar to pg_stat_ssl, for monitoring
if GSSAPI authentication was used, what principal was used, and if
encryption is being used on the connection.

Finally, add documentation for everything new, and update existing
documentation on connection security.

Thanks to Michael Paquier for the Windows fixes.

Author: Robbie Harwood, with changes to the read/write functions by me.
Reviewed in various forms and at different times by: Michael Paquier,
   Andres Freund, David Steele.
Discussion: https://www.postgresql.org/message-id/flat/jlg1tgq1ktm.fsf@thriss.redhat.com
This commit is contained in:
Stephen Frost
2019-04-03 15:02:33 -04:00
parent 5f6fc34af5
commit b0b39f72b9
35 changed files with 2575 additions and 197 deletions

View File

@ -38,6 +38,10 @@ ifeq ($(with_openssl),yes)
OBJS += fe-secure-openssl.o fe-secure-common.o
endif
ifeq ($(with_gssapi),yes)
OBJS += fe-gssapi-common.o fe-secure-gssapi.o
endif
ifeq ($(PORTNAME), cygwin)
override shlib = cyg$(NAME)$(DLSUFFIX)
endif

View File

@ -174,3 +174,5 @@ PQresultVerboseErrorMessage 171
PQencryptPasswordConn 172
PQresultMemorySize 173
PQhostaddr 174
PQgssEncInUse 175
PQgetgssctx 176

View File

@ -49,52 +49,7 @@
* GSSAPI authentication system.
*/
#if defined(WIN32) && !defined(_MSC_VER)
/*
* 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 lmin_s;
gss_buffer_desc lmsg;
OM_uint32 msg_ctx = 0;
do
{
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);
}
#include "fe-gssapi-common.h"
/*
* Continue GSS authentication with next token as needed.
@ -195,10 +150,7 @@ pg_GSS_continue(PGconn *conn, int payloadlen)
static int
pg_GSS_startup(PGconn *conn, int payloadlen)
{
OM_uint32 maj_stat,
min_stat;
int maxlen;
gss_buffer_desc temp_gbuf;
int ret;
char *host = conn->connhost[conn->whichhost].host;
if (!(host && host[0] != '\0'))
@ -215,33 +167,9 @@ pg_GSS_startup(PGconn *conn, int payloadlen)
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);
if (!temp_gbuf.value)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return STATUS_ERROR;
}
snprintf(temp_gbuf.value, maxlen, "%s@%s",
conn->krbsrvname, host);
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;
}
ret = pg_GSS_load_servicename(conn);
if (ret != STATUS_OK)
return ret;
/*
* Initial packet is the same as a continuation packet with no initial
@ -977,7 +905,7 @@ pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("SSPI authentication not supported\n"));
return STATUS_ERROR;
#endif /* !define(ENABLE_GSSAPI) */
#endif /* !define(ENABLE_GSS) */
#endif /* ENABLE_SSPI */

View File

@ -129,6 +129,12 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
#else
#define DefaultSSLMode "disable"
#endif
#ifdef ENABLE_GSS
#include "fe-gssapi-common.h"
#define DefaultGSSMode "prefer"
#else
#define DefaultGSSMode "disable"
#endif
/* ----------
* Definition of the conninfo parameters and their fallback resources.
@ -298,6 +304,14 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Peer", "", 10,
offsetof(struct pg_conn, requirepeer)},
/*
* Expose gssencmode similarly to sslmode - we can still handle "disable"
* and "prefer".
*/
{"gssencmode", "PGGSSMODE", DefaultGSSMode, NULL,
"GSS-Mode", "", 7, /* sizeof("disable") == 7 */
offsetof(struct pg_conn, gssencmode)},
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/* Kerberos and GSSAPI authentication support specifying the service name */
{"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL,
@ -1226,6 +1240,39 @@ connectOptions2(PGconn *conn)
goto oom_error;
}
/*
* validate gssencmode option
*/
if (conn->gssencmode)
{
if (strcmp(conn->gssencmode, "disable") != 0 &&
strcmp(conn->gssencmode, "prefer") != 0 &&
strcmp(conn->gssencmode, "require") != 0)
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid gssencmode value: \"%s\"\n"),
conn->gssencmode);
return false;
}
#ifndef ENABLE_GSS
if (strcmp(conn->gssencmode, "require") == 0)
{
conn->status = CONNECTION_BAD;
printfPQExpBuffer(
&conn->errorMessage,
libpq_gettext("no GSSAPI support; cannot require GSSAPI\n"));
return false;
}
#endif
}
else
{
conn->gssencmode = strdup(DefaultGSSMode);
if (!conn->gssencmode)
goto oom_error;
}
/*
* Resolve special "auto" client_encoding from the locale
*/
@ -1827,6 +1874,11 @@ connectDBStart(PGconn *conn)
*/
resetPQExpBuffer(&conn->errorMessage);
#ifdef ENABLE_GSS
if (conn->gssencmode[0] == 'd') /* "disable" */
conn->try_gss = false;
#endif
/*
* Set up to try to connect to the first host. (Setting whichhost = -1 is
* a bit of a cheat, but PQconnectPoll will advance it to 0 before
@ -2099,6 +2151,7 @@ PQconnectPoll(PGconn *conn)
case CONNECTION_NEEDED:
case CONNECTION_CHECK_WRITABLE:
case CONNECTION_CONSUME:
case CONNECTION_GSS_STARTUP:
break;
default:
@ -2640,17 +2693,57 @@ keep_going: /* We will come back to here until there is
}
#endif /* HAVE_UNIX_SOCKETS */
if (IS_AF_UNIX(conn->raddr.addr.ss_family))
{
/* Don't request SSL or GSSAPI over Unix sockets */
#ifdef USE_SSL
conn->allow_ssl_try = false;
#endif
#ifdef ENABLE_GSS
conn->try_gss = false;
#endif
}
#ifdef ENABLE_GSS
/*
* If GSSAPI is enabled and we have a ccache, try to set it up
* before sending startup messages. If it's already
* operating, don't try SSL and instead just build the startup
* packet.
*/
if (conn->try_gss && !conn->gctx)
conn->try_gss = pg_GSS_have_ccache(&conn->gcred);
if (conn->try_gss && !conn->gctx)
{
ProtocolVersion pv = pg_hton32(NEGOTIATE_GSS_CODE);
if (pqPacketSend(conn, 0, &pv, sizeof(pv)) != STATUS_OK)
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("could not send GSSAPI negotiation packet: %s\n"),
SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf)));
goto error_return;
}
/* Ok, wait for response */
conn->status = CONNECTION_GSS_STARTUP;
return PGRES_POLLING_READING;
}
else if (!conn->gctx && conn->gssencmode[0] == 'r')
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("GSSAPI encryption required, but was impossible (possibly no ccache, no server support, or using a local socket)\n"));
goto error_return;
}
#endif
#ifdef USE_SSL
/*
* If SSL is enabled and we haven't already got it running,
* request it instead of sending the startup message.
*/
if (IS_AF_UNIX(conn->raddr.addr.ss_family))
{
/* Don't bother requesting SSL over a Unix socket */
conn->allow_ssl_try = false;
}
if (conn->allow_ssl_try && !conn->wait_ssl_try &&
!conn->ssl_in_use)
{
@ -2844,6 +2937,98 @@ keep_going: /* We will come back to here until there is
#endif /* USE_SSL */
}
case CONNECTION_GSS_STARTUP:
{
#ifdef ENABLE_GSS
PostgresPollingStatusType pollres;
/*
* If we haven't yet, get the postmaster's response to our
* negotiation packet
*/
if (conn->try_gss && !conn->gctx)
{
char gss_ok;
int rdresult = pqReadData(conn);
if (rdresult < 0)
/* pqReadData fills in error message */
goto error_return;
else if (rdresult == 0)
/* caller failed to wait for data */
return PGRES_POLLING_READING;
if (pqGetc(&gss_ok, conn) < 0)
/* shouldn't happen... */
return PGRES_POLLING_READING;
if (gss_ok == 'E')
{
/*
* Server failure of some sort. Assume it's a
* protocol version support failure, and let's see if
* we can't recover (if it's not, we'll get a better
* error message on retry). Server gets fussy if we
* don't hang up the socket, though.
*/
conn->try_gss = false;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
/* mark byte consumed */
conn->inStart = conn->inCursor;
if (gss_ok == 'N')
{
/* Server doesn't want GSSAPI; fall back if we can */
if (conn->gssencmode[0] == 'r')
{
appendPQExpBufferStr(&conn->errorMessage,
libpq_gettext("server doesn't support GSSAPI encryption, but it was required\n"));
goto error_return;
}
conn->try_gss = false;
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
else if (gss_ok != 'G')
{
appendPQExpBuffer(&conn->errorMessage,
libpq_gettext("received invalid response to GSSAPI negotiation: %c\n"),
gss_ok);
goto error_return;
}
}
/* Begin or continue GSSAPI negotiation */
pollres = pqsecure_open_gss(conn);
if (pollres == PGRES_POLLING_OK)
{
/* All set for startup packet */
conn->status = CONNECTION_MADE;
return PGRES_POLLING_WRITING;
}
else if (pollres == PGRES_POLLING_FAILED &&
conn->gssencmode[0] == 'p')
{
/*
* We failed, but we can retry on "prefer". Have to drop
* the current connection to do so, though.
*/
conn->try_gss = false;
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
return pollres;
#else /* !ENABLE_GSS */
/* unreachable */
goto error_return;
#endif /* ENABLE_GSS */
}
/*
* Handle authentication exchange: wait for postmaster messages
* and respond as necessary.
@ -2997,6 +3182,26 @@ keep_going: /* We will come back to here until there is
/* Check to see if we should mention pgpassfile */
pgpassfileWarning(conn);
#ifdef ENABLE_GSS
/*
* If gssencmode is "prefer" and we're using GSSAPI, retry
* without it.
*/
if (conn->gssenc && conn->gssencmode[0] == 'p')
{
OM_uint32 minor;
/* postmaster expects us to drop the connection */
conn->try_gss = false;
conn->gssenc = false;
gss_delete_sec_context(&minor, &conn->gctx, NULL);
pqDropConnection(conn, true);
conn->status = CONNECTION_NEEDED;
goto keep_going;
}
#endif
#ifdef USE_SSL
/*
@ -3564,6 +3769,9 @@ makeEmptyPGconn(void)
conn->verbosity = PQERRORS_DEFAULT;
conn->show_context = PQSHOW_CONTEXT_ERRORS;
conn->sock = PGINVALID_SOCKET;
#ifdef ENABLE_GSS
conn->try_gss = true;
#endif
/*
* We try to send at least 8K at a time, which is the usual size of pipe
@ -3695,10 +3903,28 @@ freePGconn(PGconn *conn)
free(conn->requirepeer);
if (conn->connip)
free(conn->connip);
if (conn->gssencmode)
free(conn->gssencmode);
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
if (conn->krbsrvname)
free(conn->krbsrvname);
#endif
#ifdef ENABLE_GSS
if (conn->gcred != GSS_C_NO_CREDENTIAL)
{
OM_uint32 minor;
gss_release_cred(&minor, &conn->gcred);
conn->gcred = GSS_C_NO_CREDENTIAL;
}
if (conn->gctx)
{
OM_uint32 minor;
gss_delete_sec_context(&minor, &conn->gctx, GSS_C_NO_BUFFER);
conn->gctx = NULL;
}
#endif
#if defined(ENABLE_GSS) && defined(ENABLE_SSPI)
if (conn->gsslib)
free(conn->gsslib);

View File

@ -0,0 +1,130 @@
/*-------------------------------------------------------------------------
*
* fe-gssapi-common.c
* The front-end (client) GSSAPI common code
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/interfaces/libpq/fe-gssapi-common.c
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include "fe-gssapi-common.h"
#include "libpq-int.h"
#include "pqexpbuffer.h"
/*
* 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 lmin_s;
gss_buffer_desc lmsg;
OM_uint32 msg_ctx = 0;
do
{
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.
*/
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);
}
/*
* Check if we can acquire credentials at all (and yield them if so).
*/
bool
pg_GSS_have_ccache(gss_cred_id_t *cred_out)
{
OM_uint32 major,
minor;
gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET,
GSS_C_INITIATE, &cred, NULL, NULL);
if (major != GSS_S_COMPLETE)
{
*cred_out = NULL;
return false;
}
*cred_out = cred;
return true;
}
/*
* Try to load service name for a connection
*/
int
pg_GSS_load_servicename(PGconn *conn)
{
OM_uint32 maj_stat,
min_stat;
int maxlen;
gss_buffer_desc temp_gbuf;
char *host;
if (conn->gtarg_nam != NULL)
/* Already taken care of - move along */
return STATUS_OK;
host = PQhost(conn);
if (!(host && host[0] != '\0'))
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("host name must be specified\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);
if (!temp_gbuf.value)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return STATUS_ERROR;
}
snprintf(temp_gbuf.value, maxlen, "%s@%s",
conn->krbsrvname, host);
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;
}
return STATUS_OK;
}

View File

@ -0,0 +1,23 @@
/*-------------------------------------------------------------------------
*
* fe-gssapi-common.h
*
* Definitions for GSSAPI common routines
*
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/interfaces/libpq/fe-gssapi-common.h
*/
#ifndef FE_GSSAPI_COMMON_H
#define FE_GSSAPI_COMMON_H
#include "libpq-fe.h"
#include "libpq-int.h"
void pg_GSS_error(const char *mprefix, PGconn *conn,
OM_uint32 maj_stat, OM_uint32 min_stat);
bool pg_GSS_have_ccache(gss_cred_id_t *cred_out);
int pg_GSS_load_servicename(PGconn *conn);
#endif /* FE_GSSAPI_COMMON_H */

View File

@ -0,0 +1,635 @@
/*-------------------------------------------------------------------------
*
* fe-secure-gssapi.c
* The front-end (client) encryption support for GSSAPI
*
* Portions Copyright (c) 2016-2018, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/interfaces/libpq/fe-secure-gssapi.c
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include "libpq-fe.h"
#include "libpq-int.h"
#include "fe-gssapi-common.h"
#include "port/pg_bswap.h"
/*
* Require encryption support, as well as mutual authentication and
* tamperproofing measures.
*/
#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \
GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG
/*
* We use fixed-size buffers for handling the encryption/decryption
* which are larger than PQComm's buffer will typically be to minimize
* the times where we have to make multiple packets and therefore sets
* of recv/send calls for a single read/write call to us.
*
* NOTE: The client and server have to agree on the max packet size,
* because we have to pass an entire packet to GSSAPI at a time and we
* don't want the other side to send arbitrairly huge packets as we
* would have to allocate memory for them to then pass them to GSSAPI.
*/
#define PQ_GSS_SEND_BUFFER_SIZE 16384
#define PQ_GSS_RECV_BUFFER_SIZE 16384
/* PqGSSSendBuffer is for *encrypted* data */
static char PqGSSSendBuffer[PQ_GSS_SEND_BUFFER_SIZE];
static int PqGSSSendPointer; /* Next index to store a byte in
* PqGSSSendBuffer */
static int PqGSSSendStart; /* Next index to send a byte in
* PqGSSSendBuffer */
/* PqGSSRecvBuffer is for *encrypted* data */
static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE];
static int PqGSSRecvPointer; /* Next index to read a byte from
* PqGSSRecvBuffer */
static int PqGSSRecvLength; /* End of data available in PqGSSRecvBuffer */
/* PqGSSResultBuffer is for *unencrypted* data */
static char PqGSSResultBuffer[PQ_GSS_RECV_BUFFER_SIZE];
static int PqGSSResultPointer; /* Next index to read a byte from
* PqGSSResultBuffer */
static int PqGSSResultLength; /* End of data available in PqGSSResultBuffer */
uint32 max_packet_size; /* Maximum size we can encrypt and fit the
* results into our output buffer */
/*
* Write len bytes of data from ptr along a GSSAPI-encrypted connection. Note
* that the connection must be already set up for GSSAPI encryption (i.e.,
* GSSAPI transport negotiation is complete). Returns len when all data has
* been written; retry when errno is EWOULDBLOCK or similar with the same
* values of ptr and len. On non-socket failures, will log an error message.
*/
ssize_t
pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
{
gss_buffer_desc input,
output = GSS_C_EMPTY_BUFFER;
OM_uint32 major,
minor;
ssize_t ret = -1;
size_t bytes_to_encrypt = len;
size_t bytes_encrypted = 0;
/*
* Loop through encrypting data and sending it out until
* pqsecure_raw_write() complains (which would likely mean that the socket
* is non-blocking and the requested send() would block, or there was some
* kind of actual error) and then return.
*/
while (bytes_to_encrypt || PqGSSSendPointer)
{
int conf = 0;
uint32 netlen;
/*
* Check if we have data in the encrypted output buffer that needs to
* be sent, and if so, try to send it. If we aren't able to, return
* that back up to the caller.
*/
if (PqGSSSendPointer)
{
ssize_t ret;
ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
if (ret < 0)
{
/*
* If we encrypted some data and it's in our output buffer,
* but send() is saying that we would block, then tell the
* client how far we got with encrypting the data so that they
* can call us again with whatever is left, at which point we
* will try to send the remaining encrypted data first and
* then move on to encrypting the rest of the data.
*/
if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
return bytes_encrypted;
else
return ret;
}
/*
* Partial write, move forward that far in our buffer and try
* again
*/
if (ret != amount)
{
PqGSSSendStart += ret;
continue;
}
/* All encrypted data was sent, our buffer is empty now. */
PqGSSSendPointer = PqGSSSendStart = 0;
}
/*
* Check if there are any bytes left to encrypt. If not, we're done.
*/
if (!bytes_to_encrypt)
return bytes_encrypted;
/*
* Check how much we are being asked to send, if it's too much, then
* we will have to loop and possibly be called multiple times to get
* through all the data.
*/
if (bytes_to_encrypt > max_packet_size)
input.length = max_packet_size;
else
input.length = bytes_to_encrypt;
input.value = (char *) ptr + bytes_encrypted;
output.value = NULL;
output.length = 0;
/* Create the next encrypted packet */
major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
&input, &conf, &output);
if (major != GSS_S_COMPLETE)
{
pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
goto cleanup;
}
else if (conf == 0)
{
printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
"GSSAPI did not provide confidentiality\n"));
goto cleanup;
}
if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
{
printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
"GSSAPI attempt to send oversize packet\n"));
goto cleanup;
}
bytes_encrypted += input.length;
bytes_to_encrypt -= input.length;
/* 4 network-order bytes of length, then payload */
netlen = htonl(output.length);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32));
PqGSSSendPointer += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
PqGSSSendPointer += output.length;
}
ret = bytes_encrypted;
cleanup:
if (output.value != NULL)
gss_release_buffer(&minor, &output);
return ret;
}
/*
* Read up to len bytes of data into ptr from a GSSAPI-encrypted connection.
* Note that GSSAPI transport must already have been negotiated. Returns the
* number of bytes read into ptr; otherwise, returns -1. Retry with the same
* ptr and len when errno is EWOULDBLOCK or similar.
*/
ssize_t
pg_GSS_read(PGconn *conn, void *ptr, size_t len)
{
OM_uint32 major,
minor;
gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
output = GSS_C_EMPTY_BUFFER;
ssize_t ret = 0;
size_t bytes_to_return = len;
size_t bytes_returned = 0;
/*
* The goal here is to read an incoming encrypted packet, one at a time,
* decrypt it into our out buffer, returning to the caller what they asked
* for, and then saving anything else for the next call.
*
* We get a read request, we look if we have cleartext bytes available
* and, if so, copy those to the result, and then we try to decrypt the
* next packet.
*
* We should not try to decrypt the next packet until the read buffer is
* completely empty.
*
* If the caller asks for more bytes than one decrypted packet, then we
* should try to return all bytes asked for.
*/
while (bytes_to_return)
{
int conf = 0;
/* Check if we have data in our buffer that we can return immediately */
if (PqGSSResultPointer < PqGSSResultLength)
{
int bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
int bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
/*
* Copy the data from our output buffer into the caller's buffer,
* at the point where we last left off filling their buffer
*/
memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultPointer, bytes_to_copy);
PqGSSResultPointer += bytes_to_copy;
bytes_to_return -= bytes_to_copy;
bytes_returned += bytes_to_copy;
/* Check if our result buffer is now empty and, if so, reset */
if (PqGSSResultPointer == PqGSSResultLength)
PqGSSResultPointer = PqGSSResultLength = 0;
continue;
}
/*
* At this point, our output buffer should be empty with more bytes
* being requested to be read. We are now ready to load the next
* packet and decrypt it (entirely) into our buffer.
*
* If we get a partial read back while trying to read a packet off the
* wire then we return back what bytes we were able to return and wait
* to be called again, until we get a full packet to decrypt.
*/
/* Check if we got a partial read just trying to get the length */
if (PqGSSRecvLength < sizeof(uint32))
{
/* Try to get whatever of the length we still need */
ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
sizeof(uint32) - PqGSSRecvLength);
if (ret < 0)
return bytes_returned ? bytes_returned : ret;
PqGSSRecvLength += ret;
if (PqGSSRecvLength < sizeof(uint32))
return bytes_returned;
}
/*
* We should have the whole length at this point, so pull it out and
* then read whatever we have left of the packet
*/
input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
/* Check for over-length packet */
if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
{
printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
"GSSAPI did not provide confidentiality\n"));
ret = -1;
goto cleanup;
}
/*
* Read as much of the packet as we are able to on this call into
* wherever we left off from the last time we were called.
*/
ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
input.length - (PqGSSRecvLength - sizeof(uint32)));
if (ret < 0)
return bytes_returned ? bytes_returned : ret;
/*
* If we got less than the rest of the packet then we need to return
* and be called again.
*/
PqGSSRecvLength += ret;
if (PqGSSRecvLength - sizeof(uint32) < input.length)
return bytes_returned ? bytes_returned : -1;
/*
* We now have the full packet and we can perform the decryption and
* refill our output buffer, then loop back up to pass that back to
* the user.
*/
output.value = NULL;
output.length = 0;
input.value = PqGSSRecvBuffer + sizeof(uint32);
major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf, NULL);
if (major != GSS_S_COMPLETE)
{
pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
major, minor);
ret = -1;
goto cleanup;
}
else if (conf == 0)
{
printfPQExpBuffer(&conn->errorMessage, libpq_gettext(
"GSSAPI did not provide confidentiality\n"));
ret = -1;
goto cleanup;
}
memcpy(PqGSSResultBuffer, output.value, output.length);
PqGSSResultLength = output.length;
/* Our buffer is now empty, reset it */
PqGSSRecvPointer = PqGSSRecvLength = 0;
gss_release_buffer(&minor, &output);
}
ret = bytes_returned;
cleanup:
if (output.value != NULL)
gss_release_buffer(&minor, &output);
return ret;
}
/*
* Simple wrapper for reading from pqsecure_raw_read.
*
* This takes the same arguments as pqsecure_raw_read, plus an output parameter
* to return the number of bytes read. This handles if blocking would occur and
* if we detect EOF on the connection.
*/
static PostgresPollingStatusType
gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
{
*ret = pqsecure_raw_read(conn, recv_buffer, length);
if (*ret < 0 && errno == EWOULDBLOCK)
return PGRES_POLLING_READING;
else if (*ret < 0)
return PGRES_POLLING_FAILED;
/* Check for EOF */
if (*ret == 0)
{
int result = pqReadReady(conn);
if (result < 0)
return PGRES_POLLING_FAILED;
if (!result)
return PGRES_POLLING_READING;
*ret = pqsecure_raw_read(conn, recv_buffer, length);
if (*ret == 0)
return PGRES_POLLING_FAILED;
}
return PGRES_POLLING_OK;
}
/*
* Negotiate GSSAPI transport for a connection. When complete, returns
* PGRES_POLLING_OK. Will return PGRES_POLLING_READING or
* PGRES_POLLING_WRITING as appropriate whenever it would block, and
* PGRES_POLLING_FAILED if transport could not be negotiated.
*/
PostgresPollingStatusType
pqsecure_open_gss(PGconn *conn)
{
static int first = 1;
ssize_t ret;
OM_uint32 major,
minor;
uint32 netlen;
PostgresPollingStatusType result;
gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
output = GSS_C_EMPTY_BUFFER;
/* Check for data that needs to be written */
if (first)
{
PqGSSSendPointer = PqGSSSendStart = PqGSSRecvPointer = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
first = 0;
}
/*
* Check if we have anything to send from a prior call and if so, send it.
*/
if (PqGSSSendPointer)
{
ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
if (ret < 0 && errno == EWOULDBLOCK)
return PGRES_POLLING_WRITING;
if (ret != amount)
{
PqGSSSendStart += amount;
return PGRES_POLLING_WRITING;
}
PqGSSSendPointer = PqGSSSendStart = 0;
}
/*
* Client sends first, and sending creates a context, therefore this will
* be false the first time through, and then when we get called again we
* will check for incoming data.
*/
if (conn->gctx)
{
/* Process any incoming data we might have */
/* See if we are still trying to get the length */
if (PqGSSRecvLength < sizeof(uint32))
{
/* Attempt to get the length first */
result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, sizeof(uint32) - PqGSSRecvLength, &ret);
if (result != PGRES_POLLING_OK)
return result;
PqGSSRecvLength += ret;
if (PqGSSRecvLength < sizeof(uint32))
return PGRES_POLLING_READING;
}
/*
* Check if we got an error packet
*
* This is safe to do because we shouldn't ever get a packet over 8192
* and therefore the actual length bytes, being that they are in
* network byte order, for any real packet will be two zero bytes.
*/
if (PqGSSRecvBuffer[0] == 'E')
{
/*
* For an error message, the length is after the E, so read one
* more byte to get the full length
*/
result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, 1, &ret);
if (result != PGRES_POLLING_OK)
return result;
PqGSSRecvLength += ret;
if (PqGSSRecvLength < 1 + sizeof(uint32))
return PGRES_POLLING_READING;
input.length = ntohl(*(uint32 *) PqGSSRecvBuffer + 1);
if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32) - 1)
{
printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Over-size error packet sent by the server."));
return PGRES_POLLING_FAILED;
}
result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, input.length - PqGSSRecvLength - 1 - sizeof(uint32), &ret);
if (result != PGRES_POLLING_OK)
return result;
PqGSSRecvLength += ret;
if (PqGSSRecvLength < 1 + sizeof(uint32) + input.length)
return PGRES_POLLING_READING;
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("Server error: %s"),
PqGSSRecvBuffer + 1 + sizeof(int32));
return PGRES_POLLING_FAILED;
}
/*
* We should have the whole length at this point, so pull it out and
* then read whatever we have left of the packet
*/
/* Get the length and check for over-length packet */
input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
{
printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Over-size GSSAPI packet sent by the server: %ld"), input.length);
return PGRES_POLLING_FAILED;
}
/*
* Read as much of the packet as we are able to on this call into
* wherever we left off from the last time we were called.
*/
result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
input.length - (PqGSSRecvLength - sizeof(uint32)), &ret);
if (result != PGRES_POLLING_OK)
return result;
PqGSSRecvLength += ret;
/*
* If we got less than the rest of the packet then we need to return
* and be called again.
*/
if (PqGSSRecvLength - sizeof(uint32) < input.length)
return PGRES_POLLING_READING;
input.value = PqGSSRecvBuffer + sizeof(uint32);
}
/* Load the service name (no-op if already done */
ret = pg_GSS_load_servicename(conn);
if (ret != STATUS_OK)
return PGRES_POLLING_FAILED;
/*
* Call GSS init context, either with an empty input, or with a complete
* packet from the server.
*/
major = gss_init_sec_context(&minor, conn->gcred, &conn->gctx,
conn->gtarg_nam, GSS_C_NO_OID,
GSS_REQUIRED_FLAGS, 0, 0, &input, NULL,
&output, NULL, NULL);
/* GSS Init Sec Context uses the whole packet, so clear it */
PqGSSRecvPointer = PqGSSRecvLength = 0;
if (GSS_ERROR(major))
{
pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
conn, major, minor);
return PGRES_POLLING_FAILED;
}
else if (output.length == 0)
{
/*
* We're done - hooray! Kind of gross, but we need to disable SSL
* here so that we don't accidentally tunnel one over the other.
*/
#ifdef USE_SSL
conn->allow_ssl_try = false;
#endif
gss_release_cred(&minor, &conn->gcred);
conn->gcred = GSS_C_NO_CREDENTIAL;
conn->gssenc = true;
/*
* Determine the max packet size which will fit in our buffer, after
* accounting for the length
*/
major = gss_wrap_size_limit(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
if (GSS_ERROR(major))
pg_GSS_error(libpq_gettext("GSSAPI size check error"), conn,
major, minor);
return PGRES_POLLING_OK;
}
/* Must have output.length > 0 */
if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))
{
pg_GSS_error(libpq_gettext("GSSAPI context establishment error"),
conn, major, minor);
return PGRES_POLLING_FAILED;
}
/* Queue the token for writing */
netlen = htonl(output.length);
memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
PqGSSSendPointer += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
PqGSSSendPointer += output.length;
gss_release_buffer(&minor, &output);
/* Asked to be called again to write data */
return PGRES_POLLING_WRITING;
}
/*
* GSSAPI Information functions.
*/
/*
* Return the GSSAPI Context itself.
*/
void *
PQgetgssctx(PGconn *conn)
{
if (!conn)
return NULL;
return conn->gctx;
}
/*
* Return true if GSSAPI encryption is in use.
*/
int
PQgssEncInUse(PGconn *conn)
{
if (!conn || !conn->gctx)
return 0;
return conn->gssenc;
}

View File

@ -220,6 +220,13 @@ pqsecure_read(PGconn *conn, void *ptr, size_t len)
n = pgtls_read(conn, ptr, len);
}
else
#endif
#ifdef ENABLE_GSS
if (conn->gssenc)
{
n = pg_GSS_read(conn, ptr, len);
}
else
#endif
{
n = pqsecure_raw_read(conn, ptr, len);
@ -297,6 +304,13 @@ pqsecure_write(PGconn *conn, const void *ptr, size_t len)
n = pgtls_write(conn, ptr, len);
}
else
#endif
#ifdef ENABLE_GSS
if (conn->gssenc)
{
n = pg_GSS_write(conn, ptr, len);
}
else
#endif
{
n = pqsecure_raw_write(conn, ptr, len);
@ -420,6 +434,23 @@ PQsslAttributeNames(PGconn *conn)
}
#endif /* USE_SSL */
/* Dummy version of GSSAPI information functions, when built without GSS support */
#ifndef ENABLE_GSS
void *
PQgetgssctx(PGconn *conn)
{
return NULL;
}
int
PQgssEncInUse(PGconn *conn)
{
return 0;
}
#endif /* ENABLE_GSS */
#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)

View File

@ -65,8 +65,9 @@ typedef enum
CONNECTION_NEEDED, /* Internal state: connect() needed */
CONNECTION_CHECK_WRITABLE, /* Check if we could make a writable
* connection. */
CONNECTION_CONSUME /* Wait for any pending message and consume
CONNECTION_CONSUME, /* Wait for any pending message and consume
* them. */
CONNECTION_GSS_STARTUP /* Negotiating GSSAPI. */
} ConnStatusType;
typedef enum
@ -346,6 +347,12 @@ extern void PQinitSSL(int do_init);
/* More detailed way to tell libpq whether it needs to initialize OpenSSL */
extern void PQinitOpenSSL(int do_ssl, int do_crypto);
/* Return true if GSSAPI encryption is in use */
extern int PQgssEncInUse(PGconn *conn);
/* Returns GSSAPI context if GSSAPI is in use */
extern void *PQgetgssctx(PGconn *conn);
/* Set verbosity for PQerrorMessage and PQresultErrorMessage */
extern PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);

View File

@ -480,9 +480,15 @@ struct pg_conn
#endif /* USE_OPENSSL */
#endif /* USE_SSL */
char *gssencmode; /* GSS mode (require,prefer,disable) */
#ifdef ENABLE_GSS
gss_ctx_id_t gctx; /* GSS context */
gss_name_t gtarg_nam; /* GSS target name */
/* The following are encryption-only */
bool try_gss; /* GSS attempting permitted */
bool gssenc; /* GSS encryption is usable */
gss_cred_id_t gcred; /* GSS credential temp storage. */
#endif
#ifdef ENABLE_SSPI
@ -749,6 +755,23 @@ extern int pgtls_verify_peer_name_matches_certificate_guts(PGconn *conn,
int *names_examined,
char **first_name);
/* === GSSAPI === */
#ifdef ENABLE_GSS
/*
* Establish a GSSAPI-encrypted connection.
*/
extern PostgresPollingStatusType pqsecure_open_gss(PGconn *conn);
/*
* Read and write functions for GSSAPI-encrypted connections, with internal
* buffering to handle nonblocking sockets.
*/
extern ssize_t pg_GSS_write(PGconn *conn, const void *ptr, size_t len);
extern ssize_t pg_GSS_read(PGconn *conn, void *ptr, size_t len);
#endif
/* === miscellaneous macros === */
/*