/*-------------------------------------------------------------------------
 *
 * fe-secure.c
 *	  functions related to setting up a secure connection to the backend.
 *	  Secure connections are expected to provide confidentiality,
 *	  message integrity and endpoint authentication.
 *
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-secure.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <signal.h>
#include <fcntl.h>
#include <ctype.h>

#ifdef WIN32
#include "win32.h"
#else
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#endif

#include <sys/stat.h>

#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif

#include "fe-auth.h"
#include "libpq-fe.h"
#include "libpq-int.h"

/*
 * Macros to handle disabling and then restoring the state of SIGPIPE handling.
 * On Windows, these are all no-ops since there's no SIGPIPEs.
 */

#ifndef WIN32

#define SIGPIPE_MASKED(conn)	((conn)->sigpipe_so || (conn)->sigpipe_flag)

struct sigpipe_info
{
	sigset_t	oldsigmask;
	bool		sigpipe_pending;
	bool		got_epipe;
};

#define DECLARE_SIGPIPE_INFO(spinfo) struct sigpipe_info spinfo

#define DISABLE_SIGPIPE(conn, spinfo, failaction) \
	do { \
		(spinfo).got_epipe = false; \
		if (!SIGPIPE_MASKED(conn)) \
		{ \
			if (pq_block_sigpipe(&(spinfo).oldsigmask, \
								 &(spinfo).sigpipe_pending) < 0) \
				failaction; \
		} \
	} while (0)

#define REMEMBER_EPIPE(spinfo, cond) \
	do { \
		if (cond) \
			(spinfo).got_epipe = true; \
	} while (0)

#define RESTORE_SIGPIPE(conn, spinfo) \
	do { \
		if (!SIGPIPE_MASKED(conn)) \
			pq_reset_sigpipe(&(spinfo).oldsigmask, (spinfo).sigpipe_pending, \
							 (spinfo).got_epipe); \
	} while (0)
#else							/* WIN32 */

#define DECLARE_SIGPIPE_INFO(spinfo)
#define DISABLE_SIGPIPE(conn, spinfo, failaction)
#define REMEMBER_EPIPE(spinfo, cond)
#define RESTORE_SIGPIPE(conn, spinfo)
#endif							/* WIN32 */

/* ------------------------------------------------------------ */
/*			 Procedures common to all secure sessions			*/
/* ------------------------------------------------------------ */


int
PQsslInUse(PGconn *conn)
{
	if (!conn)
		return 0;
	return conn->ssl_in_use;
}

/*
 *	Exported function to allow application to tell us it's already
 *	initialized OpenSSL.
 */
void
PQinitSSL(int do_init)
{
#ifdef USE_SSL
	pgtls_init_library(do_init, do_init);
#endif
}

/*
 *	Exported function to allow application to tell us it's already
 *	initialized OpenSSL and/or libcrypto.
 */
void
PQinitOpenSSL(int do_ssl, int do_crypto)
{
#ifdef USE_SSL
	pgtls_init_library(do_ssl, do_crypto);
#endif
}

/*
 *	Initialize global SSL context
 */
int
pqsecure_initialize(PGconn *conn, bool do_ssl, bool do_crypto)
{
	int			r = 0;

#ifdef USE_SSL
	r = pgtls_init(conn, do_ssl, do_crypto);
#endif

	return r;
}

/*
 *	Begin or continue negotiating a secure session.
 */
PostgresPollingStatusType
pqsecure_open_client(PGconn *conn)
{
#ifdef USE_SSL
	return pgtls_open_client(conn);
#else
	/* shouldn't get here */
	return PGRES_POLLING_FAILED;
#endif
}

/*
 *	Close secure session.
 */
void
pqsecure_close(PGconn *conn)
{
#ifdef USE_SSL
	pgtls_close(conn);
#endif
}

/*
 *	Read data from a secure connection.
 *
 * On failure, this function is responsible for appending a suitable message
 * to conn->errorMessage.  The caller must still inspect errno, but only
 * to determine whether to continue/retry after error.
 */
ssize_t
pqsecure_read(PGconn *conn, void *ptr, size_t len)
{
	ssize_t		n;

#ifdef USE_SSL
	if (conn->ssl_in_use)
	{
		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);
	}

	return n;
}

ssize_t
pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
{
	ssize_t		n;
	int			result_errno = 0;
	char		sebuf[PG_STRERROR_R_BUFLEN];

	SOCK_ERRNO_SET(0);

	n = recv(conn->sock, ptr, len, 0);

	if (n < 0)
	{
		result_errno = SOCK_ERRNO;

		/* Set error message if appropriate */
		switch (result_errno)
		{
#ifdef EAGAIN
			case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
#endif
			case EINTR:
				/* no error message, caller is expected to retry */
				break;

			case EPIPE:
			case ECONNRESET:
				libpq_append_conn_error(conn, "server closed the connection unexpectedly\n"
										"\tThis probably means the server terminated abnormally\n"
										"\tbefore or while processing the request.");
				break;

			case 0:
				/* If errno didn't get set, treat it as regular EOF */
				n = 0;
				break;

			default:
				libpq_append_conn_error(conn, "could not receive data from server: %s",
										SOCK_STRERROR(result_errno,
													  sebuf, sizeof(sebuf)));
				break;
		}
	}

	/* ensure we return the intended errno to caller */
	SOCK_ERRNO_SET(result_errno);

	return n;
}

/*
 *	Return the number of bytes available in the transport buffer.
 *
 * If pqsecure_read() is called for this number of bytes, it's guaranteed to
 * return successfully with the same number of bytes, without reading from the
 * underlying socket.  See pqDrainPending() for a more complete discussion of
 * the concepts involved.
 */
ssize_t
pqsecure_bytes_pending(PGconn *conn)
{
#ifdef USE_SSL
	if (conn->ssl_in_use)
		return pgtls_bytes_pending(conn);
#endif
#ifdef ENABLE_GSS
	if (conn->gssenc)
		return pg_GSS_bytes_pending(conn);
#endif

	/* Plaintext connections have no transport buffer. */
	return 0;
}

/*
 *	Write data to a secure connection.
 *
 * Returns the number of bytes written, or a negative value (with errno
 * set) upon failure.  The write count could be less than requested.
 *
 * Note that socket-level hard failures are masked from the caller,
 * instead setting conn->write_failed and storing an error message
 * in conn->write_err_msg; see pqsecure_raw_write.  This allows us to
 * postpone reporting of write failures until we're sure no error
 * message is available from the server.
 *
 * However, errors detected in the SSL or GSS management level are reported
 * via a negative result, with message appended to conn->errorMessage.
 * It's frequently unclear whether such errors should be considered read or
 * write errors, so we don't attempt to postpone reporting them.
 *
 * The caller must still inspect errno upon failure, but only to determine
 * whether to continue/retry; a message has been saved someplace in any case.
 */
ssize_t
pqsecure_write(PGconn *conn, const void *ptr, size_t len)
{
	ssize_t		n;

#ifdef USE_SSL
	if (conn->ssl_in_use)
	{
		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);
	}

	return n;
}

/*
 * Low-level implementation of pqsecure_write.
 *
 * This is used directly for an unencrypted connection.  For encrypted
 * connections, this does the physical I/O on behalf of pgtls_write or
 * pg_GSS_write.
 *
 * This function reports failure (i.e., returns a negative result) only
 * for retryable errors such as EINTR.  Looping for such cases is to be
 * handled at some outer level, maybe all the way up to the application.
 * For hard failures, we set conn->write_failed and store an error message
 * in conn->write_err_msg, but then claim to have written the data anyway.
 * This is because we don't want to report write failures so long as there
 * is a possibility of reading from the server and getting an error message
 * that could explain why the connection dropped.  Many TCP stacks have
 * race conditions such that a write failure may or may not be reported
 * before all incoming data has been read.
 *
 * Note that this error behavior happens below the SSL management level when
 * we are using SSL.  That's because at least some versions of OpenSSL are
 * too quick to report a write failure when there's still a possibility to
 * get a more useful error from the server.
 */
ssize_t
pqsecure_raw_write(PGconn *conn, const void *ptr, size_t len)
{
	ssize_t		n;
	int			flags = 0;
	int			result_errno = 0;
	char		msgbuf[1024];
	char		sebuf[PG_STRERROR_R_BUFLEN];

	DECLARE_SIGPIPE_INFO(spinfo);

	/*
	 * If we already had a write failure, we will never again try to send data
	 * on that connection.  Even if the kernel would let us, we've probably
	 * lost message boundary sync with the server.  conn->write_failed
	 * therefore persists until the connection is reset, and we just discard
	 * all data presented to be written.
	 */
	if (conn->write_failed)
		return len;

#ifdef MSG_NOSIGNAL
	if (conn->sigpipe_flag)
		flags |= MSG_NOSIGNAL;

retry_masked:
#endif							/* MSG_NOSIGNAL */

	DISABLE_SIGPIPE(conn, spinfo, return -1);

	n = send(conn->sock, ptr, len, flags);

	if (n < 0)
	{
		result_errno = SOCK_ERRNO;

		/*
		 * If we see an EINVAL, it may be because MSG_NOSIGNAL isn't available
		 * on this machine.  So, clear sigpipe_flag so we don't try the flag
		 * again, and retry the send().
		 */
#ifdef MSG_NOSIGNAL
		if (flags != 0 && result_errno == EINVAL)
		{
			conn->sigpipe_flag = false;
			flags = 0;
			goto retry_masked;
		}
#endif							/* MSG_NOSIGNAL */

		/* Set error message if appropriate */
		switch (result_errno)
		{
#ifdef EAGAIN
			case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
#endif
			case EINTR:
				/* no error message, caller is expected to retry */
				break;

			case EPIPE:
				/* Set flag for EPIPE */
				REMEMBER_EPIPE(spinfo, true);

				/* FALL THRU */

			case ECONNRESET:
				conn->write_failed = true;
				/* Store error message in conn->write_err_msg, if possible */
				/* (strdup failure is OK, we'll cope later) */
				snprintf(msgbuf, sizeof(msgbuf),
						 libpq_gettext("server closed the connection unexpectedly\n"
									   "\tThis probably means the server terminated abnormally\n"
									   "\tbefore or while processing the request."));
				/* keep newline out of translated string */
				strlcat(msgbuf, "\n", sizeof(msgbuf));
				conn->write_err_msg = strdup(msgbuf);
				/* Now claim the write succeeded */
				n = len;
				break;

			default:
				conn->write_failed = true;
				/* Store error message in conn->write_err_msg, if possible */
				/* (strdup failure is OK, we'll cope later) */
				snprintf(msgbuf, sizeof(msgbuf),
						 libpq_gettext("could not send data to server: %s"),
						 SOCK_STRERROR(result_errno,
									   sebuf, sizeof(sebuf)));
				/* keep newline out of translated string */
				strlcat(msgbuf, "\n", sizeof(msgbuf));
				conn->write_err_msg = strdup(msgbuf);
				/* Now claim the write succeeded */
				n = len;
				break;
		}
	}

	RESTORE_SIGPIPE(conn, spinfo);

	/* ensure we return the intended errno to caller */
	SOCK_ERRNO_SET(result_errno);

	return n;
}

/* Dummy versions of SSL info functions, when built without SSL support */
#ifndef USE_SSL

void *
PQgetssl(PGconn *conn)
{
	return NULL;
}

void *
PQsslStruct(PGconn *conn, const char *struct_name)
{
	return NULL;
}

const char *
PQsslAttribute(PGconn *conn, const char *attribute_name)
{
	return NULL;
}

const char *const *
PQsslAttributeNames(PGconn *conn)
{
	static const char *const result[] = {NULL};

	return result;
}
#endif							/* USE_SSL */

/*
 * Dummy versions of OpenSSL key password hook functions, when built without
 * OpenSSL.
 */
#ifndef USE_OPENSSL

PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
{
	return NULL;
}

void
PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook)
{
	return;
}

int
PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn)
{
	return 0;
}
#endif							/* USE_OPENSSL */

/* 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(WIN32)

/*
 *	Block SIGPIPE for this thread.  This prevents send()/write() from exiting
 *	the application.
 */
int
pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
{
	sigset_t	sigpipe_sigset;
	sigset_t	sigset;

	sigemptyset(&sigpipe_sigset);
	sigaddset(&sigpipe_sigset, SIGPIPE);

	/* Block SIGPIPE and save previous mask for later reset */
	SOCK_ERRNO_SET(pthread_sigmask(SIG_BLOCK, &sigpipe_sigset, osigset));
	if (SOCK_ERRNO)
		return -1;

	/* We can have a pending SIGPIPE only if it was blocked before */
	if (sigismember(osigset, SIGPIPE))
	{
		/* Is there a pending SIGPIPE? */
		if (sigpending(&sigset) != 0)
			return -1;

		if (sigismember(&sigset, SIGPIPE))
			*sigpipe_pending = true;
		else
			*sigpipe_pending = false;
	}
	else
		*sigpipe_pending = false;

	return 0;
}

/*
 *	Discard any pending SIGPIPE and reset the signal mask.
 *
 * Note: we are effectively assuming here that the C library doesn't queue
 * up multiple SIGPIPE events.  If it did, then we'd accidentally leave
 * ours in the queue when an event was already pending and we got another.
 * As long as it doesn't queue multiple events, we're OK because the caller
 * can't tell the difference.
 *
 * The caller should say got_epipe = false if it is certain that it
 * didn't get an EPIPE error; in that case we'll skip the clear operation
 * and things are definitely OK, queuing or no.  If it got one or might have
 * gotten one, pass got_epipe = true.
 *
 * We do not want this to change errno, since if it did that could lose
 * the error code from a preceding send().  We essentially assume that if
 * we were able to do pq_block_sigpipe(), this can't fail.
 */
void
pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
{
	int			save_errno = SOCK_ERRNO;
	int			signo;
	sigset_t	sigset;

	/* Clear SIGPIPE only if none was pending */
	if (got_epipe && !sigpipe_pending)
	{
		if (sigpending(&sigset) == 0 &&
			sigismember(&sigset, SIGPIPE))
		{
			sigset_t	sigpipe_sigset;

			sigemptyset(&sigpipe_sigset);
			sigaddset(&sigpipe_sigset, SIGPIPE);

			sigwait(&sigpipe_sigset, &signo);
		}
	}

	/* Restore saved block mask */
	pthread_sigmask(SIG_SETMASK, osigset, NULL);

	SOCK_ERRNO_SET(save_errno);
}

#endif							/* !WIN32 */
