| /*------------------------------------------------------------------------- |
| * |
| * fe-secure-gssapi.c |
| * The front-end (client) encryption support for GSSAPI |
| * |
| * Portions Copyright (c) 2016-2023, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * src/interfaces/libpq/fe-secure-gssapi.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| |
| /* |
| * This file is compiled with both frontend and backend codes, symlinked by |
| * src/backend/Makefile, and use macro FRONTEND to switch. |
| * |
| * Include "c.h" to adopt Cloudberry C types. Don't include "postgres_fe.h", |
| * which only defines FRONTEND besides including "c.h" |
| */ |
| #include "c.h" |
| |
| #include "fe-gssapi-common.h" |
| #include "libpq-fe.h" |
| #include "libpq-int.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 |
| |
| /* |
| * 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 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 |
| |
| /* |
| * 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) |
| |
| |
| /* |
| * 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 added to conn->errorMessage. |
| * For retryable errors, caller should call again (passing the same or more |
| * data) once the socket is ready. |
| */ |
| ssize_t |
| pg_GSS_write(PGconn *conn, const void *ptr, size_t len) |
| { |
| OM_uint32 major, |
| minor; |
| gss_buffer_desc input, |
| output = GSS_C_EMPTY_BUFFER; |
| ssize_t ret = -1; |
| size_t bytes_to_encrypt; |
| size_t bytes_encrypted; |
| gss_ctx_id_t gctx = conn->gctx; |
| |
| /* |
| * When we get a retryable failure, we must not tell the caller we have |
| * successfully transmitted everything, else it won't retry. For |
| * simplicity, we claim we haven't transmitted anything until we have |
| * successfully transmitted all "len" bytes. Between calls, the amount of |
| * the current input data that's already been encrypted and placed into |
| * PqGSSSendBuffer (and perhaps transmitted) 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. |
| * |
| * Note: it may seem attractive to report partial write completion once |
| * we've successfully sent any encrypted packets. However, that can cause |
| * problems for callers; notably, pqPutMsgEnd's heuristic to send only |
| * full 8K blocks interacts badly with such a hack. We won't save much, |
| * typically, by letting callers discard data early, so don't risk it. |
| */ |
| if (len < PqGSSSendConsumed) |
| { |
| appendPQExpBufferStr(&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). |
| */ |
| 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 (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 (PqGSSSendLength) |
| { |
| ssize_t retval; |
| ssize_t amount = PqGSSSendLength - PqGSSSendNext; |
| |
| retval = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amount); |
| if (retval <= 0) |
| return retval; |
| |
| /* |
| * Check if this was a partial write, and if so, move forward that |
| * far in our buffer and try again. |
| */ |
| if (retval < amount) |
| { |
| PqGSSSendNext += retval; |
| continue; |
| } |
| |
| /* We've successfully sent whatever data was in the buffer. */ |
| PqGSSSendLength = PqGSSSendNext = 0; |
| } |
| |
| /* |
| * Check if there are any bytes left to encrypt. If not, we're done. |
| */ |
| if (!bytes_to_encrypt) |
| 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 > PqGSSMaxPktSize) |
| input.length = PqGSSMaxPktSize; |
| else |
| input.length = bytes_to_encrypt; |
| |
| input.value = (char *) ptr + bytes_encrypted; |
| |
| output.value = NULL; |
| output.length = 0; |
| |
| /* |
| * Create the next encrypted packet. Any failure here is considered a |
| * hard failure, so we return -1 even if some data has been sent. |
| */ |
| 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; |
| } |
| |
| if (conf_state == 0) |
| { |
| libpq_append_conn_error(conn, "outgoing GSSAPI message would not use confidentiality"); |
| errno = EIO; /* for lack of a better idea */ |
| goto cleanup; |
| } |
| |
| if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) |
| { |
| libpq_append_conn_error(conn, "client tried to send oversize GSSAPI packet (%zu > %zu)", |
| (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 = pg_hton32(output.length); |
| memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32)); |
| PqGSSSendLength += sizeof(uint32); |
| |
| memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length); |
| PqGSSSendLength += output.length; |
| |
| /* Release buffer storage allocated by GSSAPI */ |
| gss_release_buffer(&minor, &output); |
| } |
| |
| /* If we get here, our counters should all match up. */ |
| Assert(len == PqGSSSendConsumed); |
| Assert(len == bytes_encrypted); |
| |
| /* We're reporting all the data as sent, so reset PqGSSSendConsumed. */ |
| PqGSSSendConsumed = 0; |
| |
| ret = bytes_encrypted; |
| |
| cleanup: |
| /* Release GSSAPI buffer storage, if we didn't already */ |
| 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. |
| * |
| * 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 added to 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) |
| { |
| OM_uint32 major, |
| minor; |
| gss_buffer_desc input = GSS_C_EMPTY_BUFFER, |
| output = GSS_C_EMPTY_BUFFER; |
| ssize_t ret; |
| size_t bytes_returned = 0; |
| gss_ctx_id_t gctx = conn->gctx; |
| |
| /* |
| * 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_returned < len) |
| { |
| int conf_state = 0; |
| |
| /* Check if we have data in our buffer that we can return immediately */ |
| if (PqGSSResultNext < PqGSSResultLength) |
| { |
| size_t bytes_in_buffer = PqGSSResultLength - PqGSSResultNext; |
| size_t bytes_to_copy = Min(bytes_in_buffer, len - bytes_returned); |
| |
| /* |
| * 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 + PqGSSResultNext, bytes_to_copy); |
| PqGSSResultNext += bytes_to_copy; |
| bytes_returned += bytes_to_copy; |
| |
| /* |
| * 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; |
| |
| /* |
| * 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. |
| */ |
| |
| /* Collect the length if we haven't already */ |
| if (PqGSSRecvLength < sizeof(uint32)) |
| { |
| ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, |
| sizeof(uint32) - PqGSSRecvLength); |
| |
| /* 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)) |
| { |
| errno = EWOULDBLOCK; |
| return -1; |
| } |
| } |
| |
| /* Decode the packet length and check for overlength packet */ |
| input.length = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); |
| |
| if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) |
| { |
| libpq_append_conn_error(conn, "oversize GSSAPI packet sent by the server (%zu > %zu)", |
| (size_t) input.length, |
| PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)); |
| errno = EIO; /* for lack of a better idea */ |
| return -1; |
| } |
| |
| /* |
| * 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, pqsecure_raw_read already set the correct errno */ |
| if (ret <= 0) |
| return ret; |
| |
| PqGSSRecvLength += ret; |
| |
| /* If we don't yet have the whole packet, return to the caller */ |
| if (PqGSSRecvLength - sizeof(uint32) < input.length) |
| { |
| errno = EWOULDBLOCK; |
| return -1; |
| } |
| |
| /* |
| * We now have the full packet and we can perform the decryption and |
| * 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, 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; |
| } |
| |
| if (conf_state == 0) |
| { |
| libpq_append_conn_error(conn, "incoming GSSAPI message did not use confidentiality"); |
| ret = -1; |
| errno = EIO; /* for lack of a better idea */ |
| goto cleanup; |
| } |
| |
| memcpy(PqGSSResultBuffer, output.value, output.length); |
| PqGSSResultLength = output.length; |
| |
| /* Our receive buffer is now empty, reset it */ |
| PqGSSRecvLength = 0; |
| |
| /* Release buffer storage allocated by GSSAPI */ |
| gss_release_buffer(&minor, &output); |
| } |
| |
| ret = bytes_returned; |
| |
| cleanup: |
| /* Release GSSAPI buffer storage, if we didn't already */ |
| 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) |
| { |
| if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) |
| return PGRES_POLLING_READING; |
| else |
| 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) |
| { |
| if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) |
| return PGRES_POLLING_READING; |
| else |
| return PGRES_POLLING_FAILED; |
| } |
| 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) |
| { |
| ssize_t ret; |
| OM_uint32 major, |
| minor, |
| gss_flags = GSS_REQUIRED_FLAGS; |
| uint32 netlen; |
| PostgresPollingStatusType result; |
| gss_buffer_desc input = GSS_C_EMPTY_BUFFER, |
| output = GSS_C_EMPTY_BUFFER; |
| |
| /* |
| * 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) |
| { |
| 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) |
| { |
| libpq_append_conn_error(conn, "out of memory"); |
| 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 (PqGSSSendLength) |
| { |
| ssize_t amount = PqGSSSendLength - PqGSSSendNext; |
| |
| ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amount); |
| if (ret < 0) |
| { |
| 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; |
| } |
| |
| PqGSSSendLength = PqGSSSendNext = 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 start with two zero |
| * bytes. |
| */ |
| if (PqGSSRecvBuffer[0] == 'E') |
| { |
| /* |
| * For an error packet during startup, we don't get a length, so |
| * simply read as much as we can fit into our buffer (as a string, |
| * so leave a spot at the end for a NULL byte too) and report that |
| * back to the caller. |
| */ |
| result = gss_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, PQ_GSS_RECV_BUFFER_SIZE - PqGSSRecvLength - 1, &ret); |
| if (result != PGRES_POLLING_OK) |
| return result; |
| |
| PqGSSRecvLength += ret; |
| |
| Assert(PqGSSRecvLength < PQ_GSS_RECV_BUFFER_SIZE); |
| PqGSSRecvBuffer[PqGSSRecvLength] = '\0'; |
| appendPQExpBuffer(&conn->errorMessage, "%s\n", PqGSSRecvBuffer + 1); |
| |
| 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 = pg_ntoh32(*(uint32 *) PqGSSRecvBuffer); |
| if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) |
| { |
| libpq_append_conn_error(conn, "oversize GSSAPI packet sent by the server (%zu > %zu)", |
| (size_t) input.length, |
| PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)); |
| 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; |
| |
| if (conn->gssdelegation && conn->gssdelegation[0] == '1') |
| { |
| /* Acquire credentials if possible */ |
| if (conn->gcred == GSS_C_NO_CREDENTIAL) |
| (void) pg_GSS_have_cred_cache(&conn->gcred); |
| |
| /* |
| * We have credentials and gssdelegation is enabled, so request |
| * credential delegation. This may or may not actually result in |
| * credentials being delegated- it depends on if the forwardable flag |
| * has been set in the credential and if the server is configured to |
| * accept delegated credentials. |
| */ |
| if (conn->gcred != GSS_C_NO_CREDENTIAL) |
| gss_flags |= GSS_C_DELEG_FLAG; |
| } |
| |
| /* |
| * 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_flags, 0, 0, &input, NULL, |
| &output, NULL, NULL); |
| |
| /* GSS Init Sec Context uses the whole packet, so clear it */ |
| PqGSSRecvLength = 0; |
| |
| if (GSS_ERROR(major)) |
| { |
| pg_GSS_error(libpq_gettext("could not initiate GSSAPI security context"), |
| conn, major, minor); |
| return PGRES_POLLING_FAILED; |
| } |
| |
| if (output.length == 0) |
| { |
| /* |
| * We're done - hooray! Set flag to tell the low-level I/O routines |
| * to do GSS wrapping/unwrapping. |
| */ |
| conn->gssenc = true; |
| conn->gssapi_used = true; |
| |
| /* Clean up */ |
| gss_release_cred(&minor, &conn->gcred); |
| conn->gcred = GSS_C_NO_CREDENTIAL; |
| gss_release_buffer(&minor, &output); |
| |
| /* |
| * Determine the max packet size which will fit in our buffer, after |
| * 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), |
| &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; |
| } |
| |
| /* 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); |
| gss_release_buffer(&minor, &output); |
| return PGRES_POLLING_FAILED; |
| } |
| |
| /* Queue the token for writing */ |
| netlen = pg_hton32(output.length); |
| |
| memcpy(PqGSSSendBuffer, (char *) &netlen, sizeof(uint32)); |
| PqGSSSendLength += sizeof(uint32); |
| |
| memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length); |
| PqGSSSendLength += output.length; |
| |
| /* We don't bother with PqGSSSendConsumed here */ |
| |
| /* Release buffer storage allocated by GSSAPI */ |
| gss_release_buffer(&minor, &output); |
| |
| /* Ask 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; |
| } |