Extensive code review for GSSAPI encryption mechanism.

Fix assorted bugs in handling of non-blocking I/O when using GSSAPI
encryption.  The encryption layer could return the wrong status
information to its caller, resulting in effectively dropping some data
(or possibly in aborting a not-broken connection), or in a "livelock"
situation where data remains to be sent but the upper layers think
transmission is done and just go to sleep.  There were multiple small
thinkos contributing to that, as well as one big one (failure to think
through what to do when a send fails after having already transmitted
data).  Note that these errors could cause failures whether the client
application asked for non-blocking I/O or not, since both libpq and
the backend always run things in non-block mode at this level.

Also get rid of use of static variables for GSSAPI inside libpq;
that's entirely not okay given that multiple connections could be
open at once inside a single client process.

Also adjust a bunch of random small discrepancies between the frontend
and backend versions of the send/receive functions -- except for error
handling, they should be identical, and now they are.

Also extend the Kerberos TAP tests to exercise cases where nontrivial
amounts of data need to be pushed through encryption.  Before, those
tests didn't provide any useful coverage at all for the cases of
interest here.  (They still might not, depending on timing, but at
least there's a chance.)

Per complaint from pmc@citylink and subsequent investigation.
Back-patch to v12 where this code was introduced.

Discussion: https://postgr.es/m/20200109181822.GA74698@gate.oper.dinoex.org
This commit is contained in:
Tom Lane 2020-01-11 17:14:08 -05:00
parent c67a55da4e
commit 2c0cdc8183
5 changed files with 556 additions and 345 deletions

View File

@ -5,7 +5,6 @@
*
* Portions Copyright (c) 2018-2020, PostgreSQL Global Development Group
*
*
* IDENTIFICATION
* src/backend/libpq/be-secure-gssapi.c
*
@ -28,106 +27,127 @@
* Handle the encryption/decryption of data using GSSAPI.
*
* In the encrypted data stream on the wire, we break up the data
* into packets where each packet starts with a sizeof(uint32)-byte
* length (not allowed to be larger than the buffer sizes defined
* below) and then the encrypted data of that length immediately
* following.
* into packets where each packet starts with a uint32-size length
* word (in network byte order), then encrypted data of that length
* immediately following. Decryption yields the same data stream
* that would appear when not using encryption.
*
* Encrypted data typically ends up being larger than the same data
* unencrypted, so 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.
* packets (and therefore multiple 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
* don't want the other side to send arbitrarily huge packets as we
* would have to allocate memory for them to then pass them to GSSAPI.
*
* Therefore, these two #define's are effectively part of the protocol
* spec and can't ever be changed.
*/
#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
/*
* Since we manage at most one GSS-encrypted connection per backend,
* we can just keep all this state in static variables. The char *
* variables point to buffers that are allocated once and re-used.
*/
static char *PqGSSSendBuffer; /* Encrypted data waiting to be sent */
static int PqGSSSendLength; /* End of data available in PqGSSSendBuffer */
static int PqGSSSendNext; /* Next index to send a byte from
* PqGSSSendBuffer */
static int PqGSSSendConsumed; /* Number of *unencrypted* bytes consumed for
* current contents of PqGSSSendBuffer */
/* PqGSSRecvBuffer is for *encrypted* data */
static char PqGSSRecvBuffer[PQ_GSS_RECV_BUFFER_SIZE];
static char *PqGSSRecvBuffer; /* Received, encrypted data */
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 char *PqGSSResultBuffer; /* Decryption of data in gss_RecvBuffer */
static int PqGSSResultLength; /* End of data available in PqGSSResultBuffer */
static int PqGSSResultNext; /* Next index to read a byte from
* PqGSSResultBuffer */
uint32 max_packet_size; /* Maximum size we can encrypt and fit the
static uint32 PqGSSMaxPktSize; /* Maximum size we can encrypt and fit the
* results into our output buffer */
/*
* Attempt to write len bytes of data from ptr along a GSSAPI-encrypted connection.
* Attempt to write len bytes of data from ptr to a GSSAPI-encrypted connection.
*
* Connection must be fully established (including authentication step) before
* calling. Returns the bytes actually consumed once complete. Data is
* internally buffered; in the case of an incomplete write, the amount of data we
* processed (encrypted into our output buffer to be sent) will be returned. If
* an error occurs or we would block, a negative value is returned and errno is
* set appropriately.
* The connection must be already set up for GSSAPI encryption (i.e., GSSAPI
* transport negotiation is complete).
*
* To continue writing in the case of EWOULDBLOCK and similar, call this function
* again with matching ptr and len parameters.
* On success, returns the number of data bytes consumed (possibly less than
* len). On failure, returns -1 with errno set appropriately. (For fatal
* errors, we may just elog and exit, if errno wouldn't be sufficient to
* describe the error.) For retryable errors, caller should call again
* (passing the same data) once the socket is ready.
*/
ssize_t
be_gssapi_write(Port *port, void *ptr, size_t len)
{
size_t bytes_to_encrypt = len;
size_t bytes_encrypted = 0;
OM_uint32 major,
minor;
gss_buffer_desc input,
output;
size_t bytes_sent = 0;
size_t bytes_to_encrypt;
size_t bytes_encrypted;
gss_ctx_id_t gctx = port->gss->ctx;
/*
* Loop through encrypting data and sending it out until
* When we get a failure, we must not tell the caller we have successfully
* transmitted everything, else it won't retry. Hence a "success"
* (positive) return value must only count source bytes corresponding to
* fully-transmitted encrypted packets. The amount of source data
* corresponding to the current partly-transmitted packet is remembered in
* PqGSSSendConsumed. On a retry, the caller *must* be sending that data
* again, so if it offers a len less than that, something is wrong.
*/
if (len < PqGSSSendConsumed)
elog(FATAL, "GSSAPI caller failed to retransmit all data needing to be retried");
/* Discount whatever source data we already encrypted. */
bytes_to_encrypt = len - PqGSSSendConsumed;
bytes_encrypted = PqGSSSendConsumed;
/*
* Loop through encrypting data and sending it out until it's all done or
* secure_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.
* kind of actual error).
*/
while (bytes_to_encrypt || PqGSSSendPointer)
while (bytes_to_encrypt || PqGSSSendLength)
{
OM_uint32 major,
minor;
gss_buffer_desc input,
output;
int conf_state = 0;
uint32 netlen;
pg_gssinfo *gss = port->gss;
/*
* 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.
* be sent (possibly left over from a previous call), and if so, try
* to send it. If we aren't able to, return that fact back up to the
* caller.
*/
if (PqGSSSendPointer)
if (PqGSSSendLength)
{
ssize_t ret;
ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
ssize_t amount = PqGSSSendLength - PqGSSSendNext;
ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, amount);
ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendNext, 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
* caller 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.
* Report any previously-sent data; if there was none, reflect
* the secure_raw_write result up to our caller. When there
* was some, we're effectively assuming that any interesting
* failure condition will recur on the next try.
*/
if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
return bytes_encrypted;
else
return ret;
if (bytes_sent)
return bytes_sent;
return ret;
}
/*
@ -136,32 +156,30 @@ be_gssapi_write(Port *port, void *ptr, size_t len)
*/
if (ret != amount)
{
PqGSSSendStart += ret;
PqGSSSendNext += ret;
continue;
}
/* We've successfully sent whatever data was in that packet. */
bytes_sent += PqGSSSendConsumed;
/* All encrypted data was sent, our buffer is empty now. */
PqGSSSendPointer = PqGSSSendStart = 0;
PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
}
/*
* Check if there are any bytes left to encrypt. If not, we're done.
*/
if (!bytes_to_encrypt)
return bytes_encrypted;
break;
/*
* max_packet_size is the maximum amount of unencrypted data that,
* when encrypted, will fit into our encrypted-data output buffer.
*
* If we are being asked to send more than max_packet_size unencrypted
* data, then we will loop and create multiple packets, each with
* max_packet_size unencrypted data encrypted in them (at least, until
* secure_raw_write returns a failure saying we would be blocked, at
* which point we will let the caller know how far we got).
* 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;
if (bytes_to_encrypt > PqGSSMaxPktSize)
input.length = PqGSSMaxPktSize;
else
input.length = bytes_to_encrypt;
@ -171,7 +189,7 @@ be_gssapi_write(Port *port, void *ptr, size_t len)
output.length = 0;
/* Create the next encrypted packet */
major = gss_wrap(&minor, gss->ctx, 1, GSS_C_QOP_DEFAULT,
major = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT,
&input, &conf_state, &output);
if (major != GSS_S_COMPLETE)
pg_GSS_error(FATAL, gettext_noop("GSSAPI wrap error"), major, minor);
@ -188,24 +206,34 @@ be_gssapi_write(Port *port, void *ptr, size_t len)
bytes_encrypted += input.length;
bytes_to_encrypt -= input.length;
PqGSSSendConsumed += input.length;
/* 4 network-order length bytes, then payload */
/* 4 network-order bytes of length, then payload */
netlen = htonl(output.length);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, &netlen, sizeof(uint32));
PqGSSSendPointer += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32));
PqGSSSendLength += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
PqGSSSendPointer += output.length;
memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length);
PqGSSSendLength += output.length;
}
return bytes_encrypted;
/* If we get here, our counters should all match up. */
Assert(bytes_sent == len);
Assert(bytes_sent == bytes_encrypted);
return bytes_sent;
}
/*
* Read up to len bytes from a GSSAPI-encrypted connection into ptr. Call
* only after the connection has been fully established (i.e., GSSAPI
* authentication is complete). On success, returns the number of bytes
* written into ptr; otherwise, returns -1 and sets errno appropriately.
* Read up to len bytes of data into ptr from a GSSAPI-encrypted connection.
*
* The connection must be already set up for GSSAPI encryption (i.e., GSSAPI
* transport negotiation is complete).
*
* Returns the number of data bytes read, or on failure, returns -1
* with errno set appropriately. (For fatal errors, we may just elog and
* exit, if errno wouldn't be sufficient to describe the error.) For
* retryable errors, caller should call again once the socket is ready.
*/
ssize_t
be_gssapi_read(Port *port, void *ptr, size_t len)
@ -215,89 +243,85 @@ be_gssapi_read(Port *port, void *ptr, size_t len)
gss_buffer_desc input,
output;
ssize_t ret;
size_t bytes_to_return = len;
size_t bytes_returned = 0;
int conf_state = 0;
pg_gssinfo *gss = port->gss;
gss_ctx_id_t gctx = port->gss->ctx;
/*
* 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.
*
* First we look to see if we have unencrypted bytes available and, if so,
* copy those to the result. If the caller asked for more than we had
* immediately available, then we try to read a packet off the wire and
* decrypt it. If the read would block, then return the amount of
* unencrypted data we copied into the caller's ptr.
* The plan here is to read one incoming encrypted packet into
* PqGSSRecvBuffer, decrypt it into PqGSSResultBuffer, and then dole out
* data from there to the caller. When we exhaust the current input
* packet, read another.
*/
while (bytes_to_return)
while (bytes_returned < len)
{
int conf_state = 0;
/* Check if we have data in our buffer that we can return immediately */
if (PqGSSResultPointer < PqGSSResultLength)
if (PqGSSResultNext < PqGSSResultLength)
{
int bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
int bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
size_t bytes_in_buffer = PqGSSResultLength - PqGSSResultNext;
size_t bytes_to_copy = Min(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
* Copy the data from our result 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;
memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultNext, bytes_to_copy);
PqGSSResultNext += 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, we've either filled the caller's buffer or
* emptied our result buffer. Either way, return to caller. In
* the second case, we could try to read another encrypted packet,
* but the odds are good that there isn't one available. (If this
* isn't true, we chose too small a max packet size.) In any
* case, there's no harm letting the caller process the data we've
* already returned.
*/
break;
}
/* Result buffer is empty, so reset buffer pointers */
PqGSSResultLength = PqGSSResultNext = 0;
/*
* 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 the number of unencrypted bytes we were able to
* copy (if any, if we didn't copy any, then we return whatever
* secure_raw_read returned when we called it; likely -1) into the
* caller's ptr and wait to be called again, until we get a full
* packet to decrypt.
* Because we chose above to return immediately as soon as we emit
* some data, bytes_returned must be zero at this point. Therefore
* the failure exits below can just return -1 without worrying about
* whether we already emitted some data.
*/
Assert(bytes_returned == 0);
/*
* At this point, our result buffer is empty with more bytes being
* requested to be read. We are now ready to load the next packet and
* decrypt it (entirely) into our result buffer.
*/
/* Check if we have the size of the packet already in our buffer. */
/* Collect the length if we haven't already */
if (PqGSSRecvLength < sizeof(uint32))
{
/*
* We were not able to get the length of the packet last time, so
* we need to do that first.
*/
ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength,
sizeof(uint32) - PqGSSRecvLength);
if (ret < 0)
return bytes_returned ? bytes_returned : ret;
/* If ret <= 0, secure_raw_read already set the correct errno */
if (ret <= 0)
return ret;
PqGSSRecvLength += ret;
/*
* If we only got part of the packet length, then return however
* many unencrypted bytes we copied to the caller and wait to be
* called again.
*/
/* If we still haven't got the length, return to the caller */
if (PqGSSRecvLength < sizeof(uint32))
return bytes_returned;
{
errno = EWOULDBLOCK;
return -1;
}
}
/*
* We have the length of the next packet at this point, so pull it out
* and then read whatever we have left of the packet to read.
*/
/* Decode the packet length and check for overlength packet */
input.length = ntohl(*(uint32 *) PqGSSRecvBuffer);
/* Check for over-length packet */
if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32))
ereport(FATAL,
(errmsg("oversize GSSAPI packet sent by the client (%zu > %zu)",
@ -310,37 +334,29 @@ be_gssapi_read(Port *port, void *ptr, size_t len)
*/
ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength,
input.length - (PqGSSRecvLength - sizeof(uint32)));
if (ret < 0)
return bytes_returned ? bytes_returned : ret;
/* If ret <= 0, secure_raw_read already set the correct errno */
if (ret <= 0)
return ret;
PqGSSRecvLength += ret;
/*
* If we got less than the rest of the packet then we need to return
* and be called again. If we didn't have any bytes to return on this
* run then return -1 and set errno to EWOULDBLOCK.
*/
/* If we don't yet have the whole packet, return to the caller */
if (PqGSSRecvLength - sizeof(uint32) < input.length)
{
if (!bytes_returned)
{
errno = EWOULDBLOCK;
return -1;
}
return bytes_returned;
errno = EWOULDBLOCK;
return -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.
* refill our result buffer, then loop back up to pass data back to
* the caller.
*/
output.value = NULL;
output.length = 0;
input.value = PqGSSRecvBuffer + sizeof(uint32);
major = gss_unwrap(&minor, gss->ctx, &input, &output, &conf_state, NULL);
major = gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL);
if (major != GSS_S_COMPLETE)
pg_GSS_error(FATAL, gettext_noop("GSSAPI unwrap error"),
major, minor);
@ -350,10 +366,9 @@ be_gssapi_read(Port *port, void *ptr, size_t len)
(errmsg("incoming GSSAPI message did not use confidentiality")));
memcpy(PqGSSResultBuffer, output.value, output.length);
PqGSSResultLength = output.length;
/* Our buffer is now empty, reset it */
/* Our receive buffer is now empty, reset it */
PqGSSRecvLength = 0;
gss_release_buffer(&minor, &output);
@ -379,37 +394,27 @@ read_or_wait(Port *port, ssize_t len)
* Keep going until we either read in everything we were asked to, or we
* error out.
*/
while (PqGSSRecvLength != len)
while (PqGSSRecvLength < len)
{
ret = secure_raw_read(port, PqGSSRecvBuffer + PqGSSRecvLength, len - PqGSSRecvLength);
/*
* If we got back an error and it wasn't just EWOULDBLOCK/EAGAIN, then
* give up.
* If we got back an error and it wasn't just
* EWOULDBLOCK/EAGAIN/EINTR, then give up.
*/
if (ret < 0 && !(errno == EWOULDBLOCK || errno == EAGAIN))
if (ret < 0 &&
!(errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR))
return -1;
/*
* Ok, we got back either a positive value, zero, or a negative result
* but EWOULDBLOCK or EAGAIN was set.
* indicating we should retry.
*
* If it was zero or negative, then we try to wait on the socket to be
* If it was zero or negative, then we wait on the socket to be
* readable again.
*/
if (ret <= 0)
{
/*
* If we got back less than zero, indicating an error, and that
* wasn't just a EWOULDBLOCK/EAGAIN, then give up.
*/
if (ret < 0 && !(errno == EWOULDBLOCK || errno == EAGAIN))
return -1;
/*
* We got back either zero, or -1 with EWOULDBLOCK/EAGAIN, so wait
* on socket to be readable again.
*/
WaitLatchOrSocket(MyLatch,
WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH,
port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
@ -430,7 +435,7 @@ read_or_wait(Port *port, ssize_t len)
if (ret == 0)
return -1;
}
else
if (ret < 0)
continue;
}
@ -459,8 +464,22 @@ secure_open_gssapi(Port *port)
OM_uint32 major,
minor;
/* initialize state variables */
PqGSSSendPointer = PqGSSSendStart = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
/*
* Allocate buffers and initialize state variables. By malloc'ing the
* buffers at this point, we avoid wasting static data space in processes
* that will never use them, and we ensure that the buffers are
* sufficiently aligned for the length-word accesses that we do in some
* places in this file.
*/
PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE);
PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0;
/*
* Use the configured keytab, if there is one. Unfortunately, Heimdal
@ -542,7 +561,7 @@ secure_open_gssapi(Port *port)
* Check if we have data to send and, if we do, make sure to send it
* all
*/
if (output.length != 0)
if (output.length > 0)
{
uint32 netlen = htonl(output.length);
@ -553,14 +572,27 @@ secure_open_gssapi(Port *port)
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32))));
memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
PqGSSSendPointer += sizeof(uint32);
PqGSSSendLength += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
PqGSSSendPointer += output.length;
memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length);
PqGSSSendLength += output.length;
while (PqGSSSendStart != sizeof(uint32) + output.length)
/* we don't bother with PqGSSSendConsumed here */
while (PqGSSSendNext < PqGSSSendLength)
{
ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendStart, sizeof(uint32) + output.length - PqGSSSendStart);
ret = secure_raw_write(port, PqGSSSendBuffer + PqGSSSendNext,
PqGSSSendLength - PqGSSSendNext);
/*
* If we got back an error and it wasn't just
* EWOULDBLOCK/EAGAIN/EINTR, then give up.
*/
if (ret < 0 &&
!(errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR))
return -1;
/* Wait and retry if we couldn't write yet */
if (ret <= 0)
{
WaitLatchOrSocket(MyLatch,
@ -569,18 +601,18 @@ secure_open_gssapi(Port *port)
continue;
}
PqGSSSendStart += ret;
PqGSSSendNext += ret;
}
/* Done sending the packet, reset our buffer */
PqGSSSendStart = PqGSSSendPointer = 0;
PqGSSSendLength = PqGSSSendNext = 0;
gss_release_buffer(&minor, &output);
}
/*
* If we got back that the connection is finished being set up, now
* that's we've sent the last packet, exit our loop.
* that we've sent the last packet, exit our loop.
*/
if (complete_next)
break;
@ -588,10 +620,11 @@ secure_open_gssapi(Port *port)
/*
* Determine the max packet size which will fit in our buffer, after
* accounting for the length
* accounting for the length. be_gssapi_write will need this.
*/
major = gss_wrap_size_limit(&minor, port->gss->ctx, 1, GSS_C_QOP_DEFAULT,
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32),
&PqGSSMaxPktSize);
if (GSS_ERROR(major))
pg_GSS_error(FATAL, gettext_noop("GSSAPI size check error"),

View File

@ -462,7 +462,7 @@ pqDropConnection(PGconn *conn, bool flushInput)
/* Always discard any unsent data */
conn->outCount = 0;
/* Free authentication state */
/* Free authentication/encryption state */
#ifdef ENABLE_GSS
{
OM_uint32 min_s;
@ -471,6 +471,21 @@ pqDropConnection(PGconn *conn, bool flushInput)
gss_delete_sec_context(&min_s, &conn->gctx, GSS_C_NO_BUFFER);
if (conn->gtarg_nam)
gss_release_name(&min_s, &conn->gtarg_nam);
if (conn->gss_SendBuffer)
{
free(conn->gss_SendBuffer);
conn->gss_SendBuffer = NULL;
}
if (conn->gss_RecvBuffer)
{
free(conn->gss_RecvBuffer);
conn->gss_RecvBuffer = NULL;
}
if (conn->gss_ResultBuffer)
{
free(conn->gss_ResultBuffer);
conn->gss_ResultBuffer = NULL;
}
}
#endif
#ifdef ENABLE_SSPI

View File

@ -18,6 +18,7 @@
#include "libpq-int.h"
#include "port/pg_bswap.h"
/*
* Require encryption support, as well as mutual authentication and
* tamperproofing measures.
@ -26,124 +27,161 @@
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.
* Handle the encryption/decryption of data using GSSAPI.
*
* In the encrypted data stream on the wire, we break up the data
* into packets where each packet starts with a uint32-size length
* word (in network byte order), then encrypted data of that length
* immediately following. Decryption yields the same data stream
* that would appear when not using encryption.
*
* Encrypted data typically ends up being larger than the same data
* unencrypted, so 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 multiple 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
* don't want the other side to send arbitrarily huge packets as we
* would have to allocate memory for them to then pass them to GSSAPI.
*
* Therefore, these two #define's are effectively part of the protocol
* spec and can't ever be changed.
*/
#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 */
/*
* We need these state variables per-connection. To allow the functions
* in this file to look mostly like those in be-secure-gssapi.c, set up
* these macros.
*/
#define PqGSSSendBuffer (conn->gss_SendBuffer)
#define PqGSSSendLength (conn->gss_SendLength)
#define PqGSSSendNext (conn->gss_SendNext)
#define PqGSSSendConsumed (conn->gss_SendConsumed)
#define PqGSSRecvBuffer (conn->gss_RecvBuffer)
#define PqGSSRecvLength (conn->gss_RecvLength)
#define PqGSSResultBuffer (conn->gss_ResultBuffer)
#define PqGSSResultLength (conn->gss_ResultLength)
#define PqGSSResultNext (conn->gss_ResultNext)
#define PqGSSMaxPktSize (conn->gss_MaxPktSize)
/* 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.
* Attempt to write len bytes of data from ptr to a GSSAPI-encrypted connection.
*
* The connection must be already set up for GSSAPI encryption (i.e., GSSAPI
* transport negotiation is complete).
*
* On success, returns the number of data bytes consumed (possibly less than
* len). On failure, returns -1 with errno set appropriately. If the errno
* indicates a non-retryable error, a message is put into conn->errorMessage.
* For retryable errors, caller should call again (passing the same data)
* once the socket is ready.
*/
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;
gss_buffer_desc input,
output = GSS_C_EMPTY_BUFFER;
ssize_t ret = -1;
size_t bytes_to_encrypt = len;
size_t bytes_encrypted = 0;
size_t bytes_sent = 0;
size_t bytes_to_encrypt;
size_t bytes_encrypted;
gss_ctx_id_t gctx = conn->gctx;
/*
* Loop through encrypting data and sending it out until
* When we get a failure, we must not tell the caller we have successfully
* transmitted everything, else it won't retry. Hence a "success"
* (positive) return value must only count source bytes corresponding to
* fully-transmitted encrypted packets. The amount of source data
* corresponding to the current partly-transmitted packet is remembered in
* PqGSSSendConsumed. On a retry, the caller *must* be sending that data
* again, so if it offers a len less than that, something is wrong.
*/
if (len < PqGSSSendConsumed)
{
printfPQExpBuffer(&conn->errorMessage,
"GSSAPI caller failed to retransmit all data needing to be retried\n");
errno = EINVAL;
return -1;
}
/* Discount whatever source data we already encrypted. */
bytes_to_encrypt = len - PqGSSSendConsumed;
bytes_encrypted = PqGSSSendConsumed;
/*
* Loop through encrypting data and sending it out until it's all done or
* 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.
* kind of actual error).
*/
while (bytes_to_encrypt || PqGSSSendPointer)
while (bytes_to_encrypt || PqGSSSendLength)
{
int conf_state = 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.
* be sent (possibly left over from a previous call), and if so, try
* to send it. If we aren't able to, return that fact back up to the
* caller.
*/
if (PqGSSSendPointer)
if (PqGSSSendLength)
{
ssize_t ret;
ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
ssize_t amount = PqGSSSendLength - PqGSSSendNext;
ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
if (ret < 0)
ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, 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.
* Report any previously-sent data; if there was none, reflect
* the pqsecure_raw_write result up to our caller. When there
* was some, we're effectively assuming that any interesting
* failure condition will recur on the next try.
*/
if (bytes_encrypted != 0 && (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR))
return bytes_encrypted;
else
return ret;
if (bytes_sent)
return bytes_sent;
return ret;
}
/*
* Partial write, move forward that far in our buffer and try
* again
* Check if this was a partial write, and if so, move forward that
* far in our buffer and try again.
*/
if (ret != amount)
{
PqGSSSendStart += ret;
PqGSSSendNext += ret;
continue;
}
/* We've successfully sent whatever data was in that packet. */
bytes_sent += PqGSSSendConsumed;
/* All encrypted data was sent, our buffer is empty now. */
PqGSSSendPointer = PqGSSSendStart = 0;
PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
}
/*
* Check if there are any bytes left to encrypt. If not, we're done.
*/
if (!bytes_to_encrypt)
return bytes_encrypted;
break;
/*
* 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;
if (bytes_to_encrypt > PqGSSMaxPktSize)
input.length = PqGSSMaxPktSize;
else
input.length = bytes_to_encrypt;
@ -152,18 +190,24 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
output.value = NULL;
output.length = 0;
/* Create the next encrypted packet */
major = gss_wrap(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
/*
* Create the next encrypted packet. Any failure here is considered a
* hard failure, so we return -1 even if bytes_sent > 0.
*/
major = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT,
&input, &conf_state, &output);
if (major != GSS_S_COMPLETE)
{
pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
errno = EIO; /* for lack of a better idea */
goto cleanup;
}
else if (conf_state == 0)
if (conf_state == 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("outgoing GSSAPI message would not use confidentiality\n"));
errno = EIO; /* for lack of a better idea */
goto cleanup;
}
@ -173,22 +217,28 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
libpq_gettext("client tried to send oversize GSSAPI packet (%zu > %zu)\n"),
(size_t) output.length,
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32));
errno = EIO; /* for lack of a better idea */
goto cleanup;
}
bytes_encrypted += input.length;
bytes_to_encrypt -= input.length;
PqGSSSendConsumed += 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 + PqGSSSendLength, &netlen, sizeof(uint32));
PqGSSSendLength += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
PqGSSSendPointer += output.length;
memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length);
PqGSSSendLength += output.length;
}
ret = bytes_encrypted;
/* If we get here, our counters should all match up. */
Assert(bytes_sent == len);
Assert(bytes_sent == bytes_encrypted);
ret = bytes_sent;
cleanup:
if (output.value != NULL)
@ -198,9 +248,14 @@ cleanup:
/*
* 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.
*
* The connection must be already set up for GSSAPI encryption (i.e., GSSAPI
* transport negotiation is complete).
*
* Returns the number of data bytes read, or on failure, returns -1
* with errno set appropriately. If the errno indicates a non-retryable
* error, a message is put into conn->errorMessage. For retryable errors,
* caller should call again once the socket is ready.
*/
ssize_t
pg_GSS_read(PGconn *conn, void *ptr, size_t len)
@ -209,90 +264,94 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
minor;
gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
output = GSS_C_EMPTY_BUFFER;
ssize_t ret = 0;
size_t bytes_to_return = len;
ssize_t ret;
size_t bytes_returned = 0;
gss_ctx_id_t gctx = conn->gctx;
/*
* 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.
* The plan here is to read one incoming encrypted packet into
* PqGSSRecvBuffer, decrypt it into PqGSSResultBuffer, and then dole out
* data from there to the caller. When we exhaust the current input
* packet, read another.
*/
while (bytes_to_return)
while (bytes_returned < len)
{
int conf_state = 0;
/* Check if we have data in our buffer that we can return immediately */
if (PqGSSResultPointer < PqGSSResultLength)
if (PqGSSResultNext < PqGSSResultLength)
{
int bytes_in_buffer = PqGSSResultLength - PqGSSResultPointer;
int bytes_to_copy = bytes_in_buffer < len - bytes_returned ? bytes_in_buffer : len - bytes_returned;
size_t bytes_in_buffer = PqGSSResultLength - PqGSSResultNext;
size_t bytes_to_copy = Min(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
* Copy the data from our result 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;
memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultNext, bytes_to_copy);
PqGSSResultNext += 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, we've either filled the caller's buffer or
* emptied our result buffer. Either way, return to caller. In
* the second case, we could try to read another encrypted packet,
* but the odds are good that there isn't one available. (If this
* isn't true, we chose too small a max packet size.) In any
* case, there's no harm letting the caller process the data we've
* already returned.
*/
break;
}
/* Result buffer is empty, so reset buffer pointers */
PqGSSResultLength = PqGSSResultNext = 0;
/*
* 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.
* Because we chose above to return immediately as soon as we emit
* some data, bytes_returned must be zero at this point. Therefore
* the failure exits below can just return -1 without worrying about
* whether we already emitted some data.
*/
Assert(bytes_returned == 0);
/*
* At this point, our result buffer is empty with more bytes being
* requested to be read. We are now ready to load the next packet and
* decrypt it (entirely) into our result buffer.
*/
/* Check if we got a partial read just trying to get the length */
/* Collect the length if we haven't already */
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;
/* If ret <= 0, pqsecure_raw_read already set the correct errno */
if (ret <= 0)
return ret;
PqGSSRecvLength += ret;
/* If we still haven't got the length, return to the caller */
if (PqGSSRecvLength < sizeof(uint32))
return bytes_returned;
{
errno = EWOULDBLOCK;
return -1;
}
}
/*
* We should have the whole length at this point, so pull it out and
* then read whatever we have left of the packet
*/
/* Decode the packet length and check for overlength 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("oversize GSSAPI packet sent by the server (%zu > %zu)\n"),
(size_t) input.length,
PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32));
ret = -1;
goto cleanup;
errno = EIO; /* for lack of a better idea */
return -1;
}
/*
@ -301,47 +360,53 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
*/
ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength,
input.length - (PqGSSRecvLength - sizeof(uint32)));
if (ret < 0)
return bytes_returned ? bytes_returned : ret;
/* If ret <= 0, pqsecure_raw_read already set the correct errno */
if (ret <= 0)
return ret;
/*
* If we got less than the rest of the packet then we need to return
* and be called again.
*/
PqGSSRecvLength += ret;
/* If we don't yet have the whole packet, return to the caller */
if (PqGSSRecvLength - sizeof(uint32) < input.length)
return bytes_returned ? bytes_returned : -1;
{
errno = EWOULDBLOCK;
return -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.
* refill our result buffer, then loop back up to pass data back to
* the caller. Note that error exits below here must take care of
* releasing the gss output buffer.
*/
output.value = NULL;
output.length = 0;
input.value = PqGSSRecvBuffer + sizeof(uint32);
major = gss_unwrap(&minor, conn->gctx, &input, &output, &conf_state, NULL);
major = gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL);
if (major != GSS_S_COMPLETE)
{
pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
major, minor);
ret = -1;
errno = EIO; /* for lack of a better idea */
goto cleanup;
}
else if (conf_state == 0)
if (conf_state == 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("incoming GSSAPI message did not use confidentiality\n"));
ret = -1;
errno = EIO; /* for lack of a better idea */
goto cleanup;
}
memcpy(PqGSSResultBuffer, output.value, output.length);
PqGSSResultLength = output.length;
/* Our buffer is now empty, reset it */
PqGSSRecvPointer = PqGSSRecvLength = 0;
/* Our receive buffer is now empty, reset it */
PqGSSRecvLength = 0;
gss_release_buffer(&minor, &output);
}
@ -365,10 +430,13 @@ 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;
if (*ret < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return PGRES_POLLING_READING;
else
return PGRES_POLLING_FAILED;
}
/* Check for EOF */
if (*ret == 0)
@ -382,6 +450,13 @@ gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
return PGRES_POLLING_READING;
*ret = pqsecure_raw_read(conn, recv_buffer, length);
if (*ret < 0)
{
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return PGRES_POLLING_READING;
else
return PGRES_POLLING_FAILED;
}
if (*ret == 0)
return PGRES_POLLING_FAILED;
}
@ -398,7 +473,6 @@ gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
PostgresPollingStatusType
pqsecure_open_gss(PGconn *conn)
{
static int first = 1;
ssize_t ret;
OM_uint32 major,
minor;
@ -407,31 +481,50 @@ pqsecure_open_gss(PGconn *conn)
gss_buffer_desc input = GSS_C_EMPTY_BUFFER,
output = GSS_C_EMPTY_BUFFER;
/* Check for data that needs to be written */
if (first)
/*
* If first time through for this connection, allocate buffers and
* initialize state variables. By malloc'ing the buffers separately, we
* ensure that they are sufficiently aligned for the length-word accesses
* that we do in some places in this file.
*/
if (PqGSSSendBuffer == NULL)
{
PqGSSSendPointer = PqGSSSendStart = PqGSSRecvPointer = PqGSSRecvLength = PqGSSResultPointer = PqGSSResultLength = 0;
first = 0;
PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE);
PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE);
if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("out of memory\n"));
return PGRES_POLLING_FAILED;
}
PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0;
PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0;
}
/*
* Check if we have anything to send from a prior call and if so, send it.
*/
if (PqGSSSendPointer)
if (PqGSSSendLength)
{
ssize_t amount = PqGSSSendPointer - PqGSSSendStart;
ssize_t amount = PqGSSSendLength - PqGSSSendNext;
ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendStart, amount);
if (ret < 0 && errno == EWOULDBLOCK)
return PGRES_POLLING_WRITING;
if (ret != amount)
ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amount);
if (ret < 0)
{
PqGSSSendStart += amount;
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return PGRES_POLLING_WRITING;
else
return PGRES_POLLING_FAILED;
}
if (ret < amount)
{
PqGSSSendNext += ret;
return PGRES_POLLING_WRITING;
}
PqGSSSendPointer = PqGSSSendStart = 0;
PqGSSSendLength = PqGSSSendNext = 0;
}
/*
@ -536,7 +629,7 @@ pqsecure_open_gss(PGconn *conn)
&output, NULL, NULL);
/* GSS Init Sec Context uses the whole packet, so clear it */
PqGSSRecvPointer = PqGSSRecvLength = 0;
PqGSSRecvLength = 0;
if (GSS_ERROR(major))
{
@ -544,7 +637,8 @@ pqsecure_open_gss(PGconn *conn)
conn, major, minor);
return PGRES_POLLING_FAILED;
}
else if (output.length == 0)
if (output.length == 0)
{
/*
* We're done - hooray! Kind of gross, but we need to disable SSL
@ -553,20 +647,26 @@ pqsecure_open_gss(PGconn *conn)
#ifdef USE_SSL
conn->allow_ssl_try = false;
#endif
/* Clean up */
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
* accounting for the length. pg_GSS_write will need this.
*/
major = gss_wrap_size_limit(&minor, conn->gctx, 1, GSS_C_QOP_DEFAULT,
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), &max_packet_size);
PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32),
&PqGSSMaxPktSize);
if (GSS_ERROR(major))
{
pg_GSS_error(libpq_gettext("GSSAPI size check error"), conn,
major, minor);
return PGRES_POLLING_FAILED;
}
return PGRES_POLLING_OK;
}
@ -583,14 +683,16 @@ pqsecure_open_gss(PGconn *conn)
netlen = htonl(output.length);
memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32));
PqGSSSendPointer += sizeof(uint32);
PqGSSSendLength += sizeof(uint32);
memcpy(PqGSSSendBuffer + PqGSSSendPointer, output.value, output.length);
PqGSSSendPointer += output.length;
memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length);
PqGSSSendLength += output.length;
/* We don't bother with PqGSSSendConsumed here */
gss_release_buffer(&minor, &output);
/* Asked to be called again to write data */
/* Ask to be called again to write data */
return PGRES_POLLING_WRITING;
}

View File

@ -493,6 +493,23 @@ struct pg_conn
bool try_gss; /* GSS attempting permitted */
bool gssenc; /* GSS encryption is usable */
gss_cred_id_t gcred; /* GSS credential temp storage. */
/* GSS encryption I/O state --- see fe-secure-gssapi.c */
char *gss_SendBuffer; /* Encrypted data waiting to be sent */
int gss_SendLength; /* End of data available in gss_SendBuffer */
int gss_SendNext; /* Next index to send a byte from
* gss_SendBuffer */
int gss_SendConsumed; /* Number of *unencrypted* bytes consumed
* for current contents of gss_SendBuffer */
char *gss_RecvBuffer; /* Received, encrypted data */
int gss_RecvLength; /* End of data available in gss_RecvBuffer */
char *gss_ResultBuffer; /* Decryption of data in gss_RecvBuffer */
int gss_ResultLength; /* End of data available in
* gss_ResultBuffer */
int gss_ResultNext; /* Next index to read a byte from
* gss_ResultBuffer */
uint32 gss_MaxPktSize; /* Maximum size we can encrypt and fit the
* results into our output buffer */
#endif
#ifdef ENABLE_SSPI

View File

@ -19,7 +19,7 @@ use Test::More;
if ($ENV{with_gssapi} eq 'yes')
{
plan tests => 12;
plan tests => 18;
}
else
{
@ -166,15 +166,15 @@ $node->safe_psql('postgres', 'CREATE USER test1;');
note "running tests";
# Test connection success or failure, and if success, that query returns true.
sub test_access
{
my ($node, $role, $server_check, $expected_res, $gssencmode, $test_name)
= @_;
my ($node, $role, $query, $expected_res, $gssencmode, $test_name) = @_;
# need to connect over TCP/IP for Kerberos
my ($res, $stdoutres, $stderrres) = $node->psql(
'postgres',
"$server_check",
"$query",
extra_params => [
'-XAtd',
$node->connstr('postgres')
@ -195,6 +195,29 @@ sub test_access
return;
}
# As above, but test for an arbitrary query result.
sub test_query
{
my ($node, $role, $query, $expected, $gssencmode, $test_name) = @_;
# need to connect over TCP/IP for Kerberos
my ($res, $stdoutres, $stderrres) = $node->psql(
'postgres',
"$query",
extra_params => [
'-XAtd',
$node->connstr('postgres')
. " host=$host hostaddr=$hostaddr $gssencmode",
'-U',
$role
]);
is($res, 0, $test_name);
like($stdoutres, $expected, $test_name);
is($stderrres, "", $test_name);
return;
}
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
qq{host all all $hostaddr/32 gss map=mymap});
@ -231,6 +254,27 @@ test_access(
"gssencmode=require",
"succeeds with GSS-encrypted access required with host hba");
# Test that we can transport a reasonable amount of data.
test_query(
$node,
"test1",
'SELECT * FROM generate_series(1, 100000);',
qr/^1\n.*\n1024\n.*\n9999\n.*\n100000$/s,
"gssencmode=require",
"receiving 100K lines works");
test_query(
$node,
"test1",
"CREATE TABLE mytab (f1 int primary key);\n"
. "COPY mytab FROM STDIN;\n"
. join("\n", (1 .. 100000))
. "\n\\.\n"
. "SELECT COUNT(*) FROM mytab;",
qr/^100000$/s,
"gssencmode=require",
"sending 100K lines works");
unlink($node->data_dir . '/pg_hba.conf');
$node->append_conf('pg_hba.conf',
qq{hostgssenc all all $hostaddr/32 gss map=mymap});