/* -*-pgsql-c-*- */
/*
 * pgpool: a language independent connection pool server for PostgreSQL
 * written by Tatsuo Ishii
 *
 * Copyright (c) 2003-2026	PgPool Global Development Group
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that copyright notice and this permission
 * notice appear in supporting documentation, and that the name of the
 * author not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. The author makes no representations about the
 * suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * pool_auth.c: authentication stuff
 *
*/

#include "pool.h"
#include <unistd.h>
#include "context/pool_session_context.h"
#include "protocol/pool_process_query.h"
#include "protocol/pool_proto_modules.h"
#include "utils/pool_stream.h"
#include "pool_config.h"
#include "auth/pool_auth.h"
#include "auth/pool_hba.h"
#include "auth/pool_passwd.h"
#include "auth/scram.h"
#include "utils/elog.h"
#include "utils/palloc.h"
#include "utils/memutils.h"
#include "auth/md5.h"
#include <unistd.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_PARAM_H
#include <param.h>
#endif
#ifdef USE_SSL
#include <openssl/rand.h>
#endif

#include <errno.h>
#include <string.h>
#include <stdlib.h>

#define AUTHFAIL_ERRORCODE "28000"
#define MAX_SASL_PAYLOAD_LEN 1024


static void pool_send_backend_key_data(POOL_CONNECTION *frontend, int pid,
									   char *key, int32 keylen, int protoMajor);
static int	do_clear_text_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor);
static void pool_send_auth_fail(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp);
static int	do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor,
				   char *storedPassword, PasswordType passwordType);
static void send_md5auth_request(POOL_CONNECTION *frontend, int protoMajor, char *salt);
static int	read_password_packet(POOL_CONNECTION *frontend, int protoMajor, char *password, int *pwdSize);
static int	send_password_packet(POOL_CONNECTION *backend, int protoMajor, char *password);
static int	send_auth_ok(POOL_CONNECTION *frontend, int protoMajor);
static void sendAuthRequest(POOL_CONNECTION *frontend, int protoMajor, int32 auth_req_type, char *extradata, int extralen);

static int	pg_SASL_continue(POOL_CONNECTION *backend, char *payload, int payloadlen, void *sasl_state, bool final);
static void *pg_SASL_init(POOL_CONNECTION *backend, char *payload, int payloadlen, char *username, char *storedPassword);
static bool do_SCRAM(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, int protoMajor, int message_length,
					 char *username, char *storedPassword, PasswordType passwordType);
static void authenticate_frontend_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor);
static void authenticate_frontend_cert(POOL_CONNECTION *frontend);
static void authenticate_frontend_SCRAM(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth);
static void authenticate_frontend_clear_text(POOL_CONNECTION *frontend);
static bool get_auth_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth,
							  char **password, PasswordType *passwordType);
static void ProcessNegotiateProtocol(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp);

/*
 * Do authentication. Assuming the only caller is
 * make_persistent_db_connection().
 */
void
connection_do_auth(POOL_CONNECTION_POOL_SLOT *cp, char *password)
{
	char		kind;
	int			length;
	int			auth_kind;
	char		state;
	int			pid;
	bool		keydata_done;

	/*
	 * read kind expecting 'R' packet (authentication response)
	 */
	pool_read_with_error(cp->con, &kind, sizeof(kind),
						 "authentication message response type");

	if (kind != 'R')
	{
		char	   *msg;
		int			sts = 0;

		if (kind == 'E' || kind == 'N')
		{
			sts = pool_extract_error_message(false, cp->con, cp->sp->major, false, &msg);
		}

		if (sts == 1)			/* succeeded in extracting error/notice
								 * message */
		{
			ereport(ERROR,
					(errmsg("failed to authenticate"),
					 errdetail("%s", msg)));
			pfree(msg);
		}
		ereport(ERROR,
				(errmsg("failed to authenticate"),
				 errdetail("invalid authentication message response type, Expecting 'R' and received '%c'", kind)));
	}

	/* read message length */
	pool_read_with_error(cp->con, &length, sizeof(length),
						 "authentication message response length");

	length = ntohl(length);

	/* read auth kind */
	pool_read_with_error(cp->con, &auth_kind, sizeof(auth_kind),
						 "authentication method from response");
	auth_kind = ntohl(auth_kind);
	ereport(DEBUG1,
			(errmsg("authenticate kind = %d", auth_kind)));

	if (auth_kind == AUTH_REQ_OK)	/* trust authentication? */
	{
		cp->con->auth_kind = AUTH_REQ_OK;
	}
	else if (auth_kind == AUTH_REQ_PASSWORD)	/* clear text password? */
	{
		kind = send_password_packet(cp->con, PROTO_MAJOR_V3, password);
		if (kind != AUTH_REQ_OK)
			ereport(ERROR,
					(errmsg("password authentication failed for user:%s", cp->sp->user),
					 errdetail("backend replied with invalid kind")));

		cp->con->auth_kind = AUTH_REQ_OK;
	}
	else if (auth_kind == AUTH_REQ_CRYPT)	/* crypt password? */
	{
		char		salt[3];
		char	   *crypt_password;

		pool_read_with_error(cp->con, &salt, 2, "crypt salt");
		salt[2] = '\0';

		crypt_password = crypt(password, salt);
		if (crypt_password == NULL)
			ereport(ERROR,
					(errmsg("crypt authentication failed for user:%s", cp->sp->user),
					 errdetail("failed to encrypt the password")));

		/* Send password packet to backend and receive auth response */
		kind = send_password_packet(cp->con, PROTO_MAJOR_V3, crypt_password);
		if (kind != AUTH_REQ_OK)
			ereport(ERROR,
					(errmsg("crypt authentication failed for user:%s", cp->sp->user),
					 errdetail("backend replied with invalid kind")));

		cp->con->auth_kind = AUTH_REQ_OK;
	}
	else if (auth_kind == AUTH_REQ_MD5) /* md5 password? */
	{
		char		salt[4];
		char	   *buf,
				   *buf1;

		pool_read_with_error(cp->con, &salt, sizeof(salt), "authentication md5 salt");

		buf = palloc0(2 * (MD5_PASSWD_LEN + 4));	/* hash + "md5" + '\0' */

		/* set buffer address for building md5 password */
		buf1 = buf + MD5_PASSWD_LEN + 4;

		/*
		 * If the supplied password is already in md5 hash format, we just
		 * copy it. Otherwise calculate the md5 hash value.
		 */
		if (!strncmp("md5", password, 3) && (strlen(password) - 3) == MD5_PASSWD_LEN)
			memcpy(buf1, password + 3, MD5_PASSWD_LEN + 1);
		else
			pool_md5_encrypt(password, cp->sp->user, strlen(cp->sp->user), buf1);

		pool_md5_encrypt(buf1, salt, 4, buf + 3);
		memcpy(buf, "md5", 3);

		/* Send password packet to backend and receive auth response */
		kind = send_password_packet(cp->con, PROTO_MAJOR_V3, buf);
		pfree(buf);
		if (kind != AUTH_REQ_OK)
			ereport(ERROR,
					(errmsg("md5 authentication failed for user:%s", cp->sp->user),
					 errdetail("backend replied with invalid kind")));

		cp->con->auth_kind = AUTH_REQ_OK;
	}
	else if (auth_kind == AUTH_REQ_SASL)
	{
		if (do_SCRAM(NULL, cp->con, PROTO_MAJOR_V3, length, cp->sp->user, password, PASSWORD_TYPE_PLAINTEXT) == false)
		{
			ereport(ERROR,
					(errmsg("failed to authenticate with backend"),
					 errdetail("SCRAM authentication failed for user:%s", cp->sp->user)));
		}
		ereport(DEBUG1,
				(errmsg("SCRAM authentication successful for user:%s", cp->sp->user)));
		cp->con->auth_kind = AUTH_REQ_OK;
	}
	else
	{
		ereport(ERROR,
				(errmsg("failed to authenticate"),
				 errdetail("auth kind %d is not yet supported", auth_kind)));
	}

	/*
	 * Read backend key data and wait until Ready for query arriving or error
	 * happens.
	 */

	keydata_done = false;

	for (;;)
	{
		pool_read_with_error(cp->con, &kind, sizeof(kind),
							 "authentication message kind");

		switch (kind)
		{
				char	   *p;
				int32		keylen;

			case 'K':			/* backend key data */
				keydata_done = true;
				ereport(DEBUG1,
						(errmsg("authenticate backend: key data received")));

				/* read message length */
				pool_read_with_error(cp->con, &length, sizeof(length), "message length for authentication kind 'K'");
				length = ntohl(length);
				keylen = length - sizeof(int32) - sizeof(int32);
				if (keylen > MAX_CANCELKEY_LENGTH)
				{
					ereport(ERROR,
							(errmsg("failed to authenticate"),
							 errdetail("invalid backend key data length. received %d bytes exceeding %d",
									   ntohl(length), MAX_CANCELKEY_LENGTH)));
				}

				/* read pid */
				pool_read_with_error(cp->con, &pid, sizeof(pid), "pid for authentication kind 'K'");
				cp->pid = pid;

				/* read key */
				keylen = length - sizeof(int32) - sizeof(int32);
				p = pool_read2(cp->con, keylen);
				memcpy(cp->key, p, keylen);
				break;

			case 'Z':			/* Ready for query */
				/* read message length */
				pool_read_with_error(cp->con, &length, sizeof(length),
									 "message length for authentication kind 'Z'");
				length = ntohl(length);

				/* read transaction state */
				pool_read_with_error(cp->con, &state, sizeof(state),
									 "transaction state  for authentication kind 'Z'");

				ereport(DEBUG1,
						(errmsg("authenticate backend: transaction state: %c", state)));

				cp->con->tstate = state;

				if (!keydata_done)
				{
					ereport(ERROR,
							(errmsg("failed to authenticate"),
							 errdetail("ready for query arrived before receiving keydata")));
				}
				return;
				break;

			case 'S':			/* parameter status */
			case 'N':			/* notice response */
			case 'E':			/* error response */
				/* Just throw away data */
				pool_read_with_error(cp->con, &length, sizeof(length),
									 "backend message length");

				length = ntohl(length);
				length -= 4;

				p = pool_read2(cp->con, length);
				if (p == NULL)
					ereport(ERROR,
							(errmsg("failed to authenticate"),
							 errdetail("unable to read data from socket")));

				break;

			default:
				ereport(ERROR,
						(errmsg("failed to authenticate"),
						 errdetail("unknown authentication message response received '%c'", kind)));
				break;
		}
	}
	ereport(ERROR,
			(errmsg("failed to authenticate")));
}

/*
 * After sending the start up packet to the backend, do the
 * authentication against backend. if success return 0 otherwise non
 * 0.
*/
int
pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp)
{
	signed char kind;
	int			pid;
	int			protoMajor;
	int			length;
	int			authkind;
	int			i;
	int			message_length = 0;
	StartupPacket *sp;
	int32		keylen;			/* cancel key length */
	char		cancel_key[MAX_CANCELKEY_LENGTH];
	char	   *p;

	protoMajor = MAIN_CONNECTION(cp)->sp->major;

read_kind:
	kind = pool_read_kind(cp);
	if (kind < 0)
		ereport(ERROR,
				(errmsg("invalid authentication packet from backend"),
				 errdetail("failed to get response kind")));

	/* error response? */
	if (kind == 'E')
	{
		/*
		 * we assume error response at this stage is likely version protocol
		 * mismatch (v3 frontend vs. v2 backend). So we throw a V2 protocol
		 * error response in the hope that v3 frontend will negotiate again
		 * using v2 protocol.
		 */

		ErrorResponse(frontend, cp);

		ereport(ERROR,
				(errmsg("backend authentication failed"),
				 errdetail("backend response with kind \'E\' when expecting \'R\'"),
				 errhint("This issue can be caused by version mismatch (current version %d)", protoMajor)));
	}
	else if (kind == 'v')
	{
		/* NegotiateProtocolVersion received */
		ProcessNegotiateProtocol(frontend, cp);
		goto read_kind;
	}
	else if (kind != 'R')
		ereport(ERROR,
				(errmsg("backend authentication failed"),
				 errdetail("backend response with kind \'%c\' when expecting \'R\'", kind)));

	/*
	 * message length (v3 only)
	 */
	if (protoMajor == PROTO_MAJOR_V3)
	{
		message_length = pool_read_message_length(cp);
		if (message_length <= 0)
			ereport(ERROR,
					(errmsg("invalid authentication packet from backend"),
					 errdetail("failed to get the authentication packet length"),
					 errhint("This is likely caused by the inconsistency of auth method among DB nodes. \
								Please check the previous error messages (hint: length field) \
								from pool_read_message_length and recheck the pg_hba.conf settings.")));
	}

	/*-------------------------------------------------------------------------
	 * read authentication request kind.
	 *
	 * 0: authentication ok
	 * 1: kerberos v4
	 * 2: kerberos v5
	 * 3: clear text password
	 * 4: crypt password
	 * 5: md5 password
	 * 6: scm credential
	 *
	 * in replication mode, we only support  kind = 0, 3. this is because "salt"
	 * cannot be replicated.
	 * in non replication mode, we support kind = 0, 3, 4, 5
	 *-------------------------------------------------------------------------
	 */

	authkind = pool_read_int(cp);
	if (authkind < 0)
		ereport(ERROR,
				(errmsg("invalid authentication packet from backend"),
				 errdetail("failed to get auth kind")));

	authkind = ntohl(authkind);

	ereport(DEBUG1,
			(errmsg("authentication backend"),
			 errdetail("auth kind:%d", authkind)));
	/* trust? */
	if (authkind == AUTH_REQ_OK)
	{
		int			msglen;

		pool_write(frontend, "R", 1);

		if (protoMajor == PROTO_MAJOR_V3)
		{
			msglen = htonl(8);
			pool_write(frontend, &msglen, sizeof(msglen));
		}

		msglen = htonl(0);
		pool_write_and_flush(frontend, &msglen, sizeof(msglen));
		MAIN(cp)->auth_kind = AUTH_REQ_OK;
	}

	/* clear text password authentication? */
	else if (authkind == AUTH_REQ_PASSWORD)
	{
		for (i = 0; i < NUM_BACKENDS; i++)
		{
			if (!VALID_BACKEND(i))
				continue;

			ereport(DEBUG1,
					(errmsg("authentication backend"),
					 errdetail("trying clear text password authentication")));

			authkind = do_clear_text_password(CONNECTION(cp, i), frontend, 0, protoMajor);

			if (authkind < 0)
			{
				ereport(DEBUG1,
						(errmsg("authentication backend"),
						 errdetail("clear text password failed in slot %d", i)));
				pool_send_auth_fail(frontend, cp);
				ereport(ERROR,
						(errmsg("failed to authenticate with backend"),
						 errdetail("do_clear_text_password failed in slot %d", i)));
			}
		}
	}

	/* md5 authentication? */
	else if (authkind == AUTH_REQ_MD5)
	{
		char	   *password = NULL;
		PasswordType passwordType = PASSWORD_TYPE_UNKNOWN;

		/*
		 * check if we can use md5 authentication.
		 */
		if (get_auth_password(MAIN(cp), frontend, 0,
							  &password, &passwordType) == false)
		{
			/*
			 * We do not have any password, we can still get the password from
			 * client using plain text authentication if it is allowed by user
			 */
			if (frontend->pool_hba == NULL && pool_config->allow_clear_text_frontend_auth)
			{
				ereport(LOG,
						(errmsg("using clear text authentication with frontend"),
						 errdetail("backend will still use md5 auth"),
						 errhint("you can disable this behavior by setting allow_clear_text_frontend_auth to off")));
				authenticate_frontend_clear_text(frontend);
				/* now check again if we have a password now */
				if (get_auth_password(MAIN(cp), frontend, 0,
									  &password, &passwordType) == false)
				{
					ereport(ERROR,
							(errmsg("failed to authenticate with backend using md5"),
							 errdetail("unable to get the password")));
				}
			}
		}
		/* we have a password to use, validate the password type */
		if (passwordType != PASSWORD_TYPE_PLAINTEXT && passwordType != PASSWORD_TYPE_MD5
			&& passwordType != PASSWORD_TYPE_AES)
		{
			ereport(ERROR,
					(errmsg("failed to authenticate with backend using md5"),
					 errdetail("valid password not found")));
		}

		for (i = 0; i < NUM_BACKENDS; i++)
		{
			if (!VALID_BACKEND(i))
				continue;

			ereport(DEBUG1,
					(errmsg("authentication backend: %d", i),
					 errdetail("trying md5 authentication")));

			authkind = do_md5(CONNECTION(cp, i), frontend, 0, protoMajor, password, passwordType);

			if (authkind < 0)
			{
				pool_send_auth_fail(frontend, cp);
				ereport(ERROR,
						(errmsg("failed to authenticate with backend"),
						 errdetail("MD5 authentication failed in slot [%d].", i)));
			}
		}
	}

	/* SCRAM authentication? */
	else if (authkind == AUTH_REQ_SASL)
	{
		char	   *password;
		PasswordType passwordType = PASSWORD_TYPE_UNKNOWN;

		if (get_auth_password(MAIN(cp), frontend, 0,
							  &password, &passwordType) == false)
		{
			/*
			 * We do not have any password, we can still get the password from
			 * client using plain text authentication if it is allowed by user
			 */
			if (frontend->pool_hba == NULL && pool_config->allow_clear_text_frontend_auth)
			{
				ereport(LOG,
						(errmsg("using clear text authentication with frontend"),
						 errdetail("backend will still use SCRAM auth"),
						 errhint("you can disable this behavior by setting allow_clear_text_frontend_auth to off")));
				authenticate_frontend_clear_text(frontend);
				/* now check again if we have a password now */
				if (get_auth_password(MAIN(cp), frontend, 0,
									  &password, &passwordType) == false)
				{
					ereport(ERROR,
							(errmsg("failed to authenticate with backend using SCRAM"),
							 errdetail("unable to get the password")));
				}
			}
		}

		/*
		 * if we have encrypted password, Decrypt it before going any further
		 */
		if (passwordType == PASSWORD_TYPE_AES)
		{
			password = get_decrypted_password(password);
			if (password == NULL)
				ereport(ERROR,
						(errmsg("SCRAM authentication failed"),
						 errdetail("unable to decrypt password from pool_passwd"),
						 errhint("verify the valid pool_key exists")));
			/* we have converted the password to plain text */
			passwordType = PASSWORD_TYPE_PLAINTEXT;
		}

		/* we have a password to use, validate the password type */
		if (passwordType != PASSWORD_TYPE_PLAINTEXT)
		{
			ereport(ERROR,
					(errmsg("failed to authenticate with backend using SCRAM"),
					 errdetail("valid password not found")));
		}

		for (i = 0; i < NUM_BACKENDS; i++)
		{
			if (!VALID_BACKEND(i))
				continue;

			ereport(DEBUG1,
					(errmsg("authentication backend %d", i),
					 errdetail("trying SCRAM authentication")));

			if (do_SCRAM(frontend, CONNECTION(cp, i), protoMajor, message_length, frontend->username, password, passwordType) == false)
			{
				pool_send_auth_fail(frontend, cp);
				ereport(ERROR,
						(errmsg("failed to authenticate with backend"),
						 errdetail("SCRAM authentication failed in slot [%d].", i)));
			}
			ereport(DEBUG1,
					(errmsg("SCRAM authentication successful for backend %d", i)));

		}

		if (kind == 'R')
		{
			send_auth_ok(frontend, protoMajor);
			authkind = 0;
		}
	}

	else
	{
		ereport(ERROR,
				(errmsg("failed to authenticate with backend"),
				 errdetail("unsupported auth kind received from backend: authkind:%d", authkind)));
	}

	if (authkind != 0)
		ereport(ERROR,
				(errmsg("failed to authenticate with backend"),
				 errdetail("invalid auth kind received from backend: authkind:%d", authkind)));

	/*
	 * authentication ok. now read pid and secret key from the backend
	 */
	for (;;)
	{
		char	   *message = NULL;

		kind = pool_read_kind(cp);

		if (kind < 0)
		{
			ereport(ERROR,
					(errmsg("authentication failed from backend"),
					 errdetail("failed to read kind before BackendKeyData")));
		}
		else if (kind == 'K')
			break;

		if (protoMajor == PROTO_MAJOR_V3)
		{
			switch (kind)
			{
				case 'S':
					/* process parameter status */
					if (ParameterStatus(frontend, cp) != POOL_CONTINUE)
						return -1;
					pool_flush(frontend);
					break;

				case 'N':
					if (pool_extract_error_message(false, MAIN(cp), protoMajor, true, &message) == 1)
					{
						ereport(NOTICE,
								(errmsg("notice from backend"),
								 errdetail("BACKEND NOTICE: \"%s\"", message)));
						pfree(message);
					}
					/* process notice message */
					if (SimpleForwardToFrontend(kind, frontend, cp))
						ereport(ERROR,
								(errmsg("authentication failed"),
								 errdetail("failed to forward message to frontend")));
					pool_flush(frontend);
					break;

					/* process error message */
				case 'E':

					if (pool_extract_error_message(false, MAIN(cp), protoMajor, true, &message) == 1)
					{
						ereport(LOG,
								(errmsg("backend throws an error message"),
								 errdetail("%s", message)));
					}

					SimpleForwardToFrontend(kind, frontend, cp);

					pool_flush(frontend);

					ereport(ERROR,
							(errmsg("authentication failed, backend node replied with an error"),
							 errdetail("SERVER ERROR:\"%s\"", message ? message : "could not extract backend message")));

					if (message)
						pfree(message);
					break;

				default:
					ereport(ERROR,
							(errmsg("authentication failed"),
							 errdetail("unknown response \"%c\" before processing BackendKeyData", kind)));
					break;
			}
		}
		else
		{
			/* V2 case */
			switch (kind)
			{
				case 'N':
					/* process notice message */
					NoticeResponse(frontend, cp);
					break;

					/* process error message */
				case 'E':
					ErrorResponse(frontend, cp);
					/* fallthrough */
				default:
					ereport(ERROR,
							(errmsg("authentication failed"),
							 errdetail("invalid response \"%c\" before processing BackendKeyData", kind)));
					break;
			}
		}
	}

	/*
	 * Read BackendKeyData message length.
	 */
	if (protoMajor == PROTO_MAJOR_V3)
	{
		length = pool_read_message_length(cp);
		keylen = length - sizeof(int32) - sizeof(int32);
		if (keylen > MAX_CANCELKEY_LENGTH)
		{
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("cancel key length exceeds 256 bytes")));
		}
	}
	else
		keylen = 4;

	elog(DEBUG1, "cancel key length: %d", keylen);

	/*
	 * OK, read pid and secret key
	 */
	sp = MAIN_CONNECTION(cp)->sp;
	pid = -1;

	for (i = 0; i < NUM_BACKENDS; i++)
	{
		if (VALID_BACKEND(i))
		{
			/* read pid */
			if (pool_read(CONNECTION(cp, i), &pid, sizeof(pid)) < 0)
			{
				ereport(ERROR,
						(errmsg("authentication failed"),
						 errdetail("failed to read pid in slot %d", i)));
			}
			ereport(DEBUG1,
					(errmsg("authentication backend"),
					 errdetail("cp->info[i]:%p pid:%u", &cp->info[i], ntohl(pid))));

			CONNECTION_SLOT(cp, i)->pid = cp->info[i].pid = pid;

			/* read key */
			p = pool_read2(CONNECTION(cp, i), keylen);
			if (p == NULL)
			{
				ereport(ERROR,
						(errmsg("authentication failed"),
						 errdetail("failed to read key of length: %d in slot %d", keylen, i)));
			}
			memcpy(CONNECTION_SLOT(cp, i)->key, p, keylen);
			memcpy(cp->info[i].key, p, keylen);
			memcpy(cancel_key, p, keylen);
			CONNECTION_SLOT(cp, i)->keylen = cp->info[i].keylen = keylen;

			cp->info[i].major = sp->major;

			/*
			 * If NegotiateProtocol message has been received, set the minor
			 * version. Othewise use the version in the StartupMessage.
			 */
			if (CONNECTION_SLOT(cp, i)->nplen > 0)
				cp->info[i].minor = CONNECTION_SLOT(cp, i)->negotiated_minor;
			else
				cp->info[i].minor = sp->minor;

			strlcpy(cp->info[i].database, sp->database, sizeof(cp->info[i].database));
			strlcpy(cp->info[i].user, sp->user, sizeof(cp->info[i].user));
			cp->info[i].counter = 1;
			CONNECTION(cp, i)->con_info = &cp->info[i];
			cp->info[i].swallow_termination = 0;
		}
	}

	if (pid == -1)
	{
		ereport(ERROR,
				(errmsg("authentication failed"),
				 errdetail("pool_do_auth: all backends are down")));
	}

	/*
	 * We send the BackendKeyData to frontend, which belongs to the last
	 * backend in the backend group.
	 */
	pool_send_backend_key_data(frontend, pid, cancel_key,
							   MAIN_CONNECTION(cp)->keylen, protoMajor);
	return 0;
}

/*
* do re-authentication for reused connection. if success return 0 otherwise
* throws ereport.
*/
int
pool_do_reauth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp)
{
	int			protoMajor;
	int			msglen;
	POOL_CONNECTION_POOL_SLOT *sp;

	protoMajor = MAJOR(cp);

	/*
	 * If NegotiateProtocolMsg has been received from backend, forward it to
	 * frontend. If the frontend dislike it, it will disconnect the
	 * connection. Otherwise it will silently continue.
	 */
	sp = CONNECTION_SLOT(cp, MAIN_NODE_ID);
	if (protoMajor == PROTO_MAJOR_V3 && sp->nplen > 0)
	{
		elog(DEBUG1, "negotiateProtocol message is forwarded to frontend at reauth");
		pool_write_and_flush(frontend, sp->negotiateProtocolMsg,
							 sp->nplen);
	}

	/*
	 * if hba is enabled we would already have passed authentication
	 */
	if (!frontend->frontend_authenticated)
	{
		switch (MAIN(cp)->auth_kind)
		{
			case AUTH_REQ_OK:
				/* trust */
				break;

			case AUTH_REQ_PASSWORD:
				/* clear text password */
				do_clear_text_password(MAIN(cp), frontend, 1, protoMajor);
				break;

			case AUTH_REQ_MD5:
				/* md5 password */
				authenticate_frontend_md5(MAIN(cp), frontend, 1, protoMajor);
				break;

			case AUTH_REQ_SASL:
				/* SCRAM */
				authenticate_frontend_SCRAM(MAIN(cp), frontend, 1);
				break;

			default:
				ereport(ERROR,
						(errmsg("authentication failed"),
						 errdetail("unknown authentication request code %d", MAIN(cp)->auth_kind)));
		}
	}

	/*
	 * Send auth ok
	 */
	pool_write(frontend, "R", 1);

	if (protoMajor == PROTO_MAJOR_V3)
	{
		msglen = htonl(8);
		pool_write(frontend, &msglen, sizeof(msglen));
	}

	msglen = htonl(0);
	pool_write_and_flush(frontend, &msglen, sizeof(msglen));

	/* send BackendKeyData */
	pool_send_backend_key_data(frontend, MAIN_CONNECTION(cp)->pid, MAIN_CONNECTION(cp)->key,
							   MAIN_CONNECTION(cp)->keylen, protoMajor);

	return 0;
}

/*
* send authentication failure message text to frontend
*/
static void
pool_send_auth_fail(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp)
{
	int			messagelen;
	char	   *errmessage;
	int			protoMajor;

	bool		send_error_to_frontend = true;

	protoMajor = MAJOR(cp);

	messagelen = strlen(MAIN_CONNECTION(cp)->sp->user) + 100;
	errmessage = (char *) palloc(messagelen + 1);

	snprintf(errmessage, messagelen, "password authentication failed for user \"%s\"",
			 MAIN_CONNECTION(cp)->sp->user);
	if (send_error_to_frontend)
		pool_send_fatal_message(frontend, protoMajor, "XX000", errmessage,
								"", "", __FILE__, __LINE__);
	pfree(errmessage);
}

/*
 * Send backend key data to frontend.
 */
static void
pool_send_backend_key_data(POOL_CONNECTION *frontend, int pid,
						   char *key, int32 keylen, int protoMajor)
{
	char		kind;
	int32		len;

	/* Send backend key data */
	kind = 'K';
	pool_write(frontend, &kind, 1);
	if (protoMajor == PROTO_MAJOR_V3)
	{
		len = htonl(sizeof(int32) + sizeof(int32) + keylen);
		pool_write(frontend, &len, sizeof(len));
	}
	ereport(DEBUG1,
			(errmsg("sending backend key data")));
	pool_write(frontend, &pid, sizeof(pid));
	pool_write_and_flush(frontend, key, keylen);
}

static void
authenticate_frontend_clear_text(POOL_CONNECTION *frontend)
{
	static int	size;
	char		password[MAX_PASSWORD_SIZE];
	char		userPassword[MAX_PASSWORD_SIZE];
	char	   *storedPassword = NULL;
	char	   *userPwd = NULL;

	sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_PASSWORD, NULL, 0);

	/* Read password packet */
	read_password_packet(frontend, frontend->protoVersion, password, &size);

	/* save the password in frontend */
	frontend->auth_kind = AUTH_REQ_PASSWORD;
	frontend->pwd_size = size;
	memcpy(frontend->password, password, frontend->pwd_size);
	frontend->password[size] = 0;	/* Null terminate the password string */
	frontend->passwordType = PASSWORD_TYPE_PLAINTEXT;

	if (!frontend->passwordMapping)
	{
		/*
		 * if the password is not present in pool_passwd just bail out from
		 * here
		 */
		return;
	}

	storedPassword = frontend->passwordMapping->pgpoolUser.password;
	userPwd = password;

	/*
	 * If we have md5 password stored in pool_passwd, convert the user
	 * supplied password to md5 for comparison
	 */
	if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_MD5)
	{
		pg_md5_encrypt(password,
					   frontend->username,
					   strlen(frontend->username), userPassword);
		userPwd = userPassword;
		size = strlen(userPwd);
	}
	else if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_AES)
	{
		/*
		 * decrypt the stored AES password for comparing it
		 */
		storedPassword = get_decrypted_password(storedPassword);
		if (storedPassword == NULL)
			ereport(ERROR,
					(errmsg("clear text authentication failed"),
					 errdetail("unable to decrypt password from pool_passwd"),
					 errhint("verify the valid pool_key exists")));
	}
	else if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_TEXT_PREFIXED)
	{
		storedPassword = frontend->passwordMapping->pgpoolUser.password + strlen(PASSWORD_TEXT_PREFIX);
	}
	else if (frontend->passwordMapping->pgpoolUser.passwordType != PASSWORD_TYPE_PLAINTEXT)
	{
		ereport(ERROR,
				(errmsg("clear text authentication failed"),
				 errdetail("password type stored in pool_passwd can't be used for clear text authentication")));
	}

	if (memcmp(userPwd, storedPassword, size))
	{
		/* Password does not match */
		ereport(ERROR,
				(errmsg("clear text authentication failed"),
				 errdetail("password does not match")));
	}
	ereport(DEBUG1,
			(errmsg("clear text authentication successful with frontend")));

	if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_AES)
		pfree(storedPassword);

	frontend->frontend_authenticated = true;
}

/*
 * perform clear text password authentication
 */
static int
do_clear_text_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor)
{
	static int	size;
	char	   *pwd = NULL;
	int			kind;
	PasswordType passwordType = PASSWORD_TYPE_UNKNOWN;
	bool		rtn;

	if (reauth && frontend->frontend_authenticated)
	{
		/* frontend and backend are both authenticated already */
		return 0;
	}

	rtn = get_auth_password(backend, frontend, reauth, &pwd, &passwordType);

	/*
	 * If pool_hba.conf == "password", we should already have frontend
	 * password.  When reauth, we want the password, rather than
	 * backend->password, which is returned by get_auth_password() if
	 * pool_passwd does not have an entry for the user.
	 */
	if (reauth && rtn && frontend->passwordMapping == NULL &&
		frontend->pwd_size > 0 &&
		frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
	{
		pwd = frontend->password;
		passwordType = frontend->passwordType;
	}

	else if (!rtn || frontend->pwd_size == 0)
	{
		/*
		 * We do not have any password, we can still get the password from
		 * client using plain text authentication if it is allowed by user
		 */

		if (frontend->pool_hba == NULL ||
			frontend->pool_hba->auth_method == uaPassword ||
			pool_config->allow_clear_text_frontend_auth)
		{
			ereport(DEBUG1,
					(errmsg("using clear text authentication with frontend"),
					 errdetail("backend is using password authentication")));

			authenticate_frontend_clear_text(frontend);

			/* now check again if we have a password now */
			if (get_auth_password(backend, frontend, 0, &pwd, &passwordType) == false)
			{
				ereport(FATAL,
						(return_code(2),
						 errmsg("clear text password authentication failed"),
						 errdetail("unable to get the password for user: \"%s\"", frontend->username)));
			}
		}
	}

	if (passwordType == PASSWORD_TYPE_AES)
	{
		/*
		 * decrypt the stored AES password for comparing it
		 */
		pwd = get_decrypted_password(pwd);
		if (pwd == NULL)
			ereport(ERROR,
					(errmsg("clear text password authentication failed"),
					 errdetail("unable to decrypt password from pool_passwd"),
					 errhint("verify the valid pool_key exists")));
		/* we have converted the password to plain text */
		passwordType = PASSWORD_TYPE_PLAINTEXT;
	}

	if (pwd == NULL || passwordType != PASSWORD_TYPE_PLAINTEXT)
	{
		/* If we still do not have a password. we can't proceed */
		ereport(ERROR,
				(errmsg("clear text password authentication failed"),
				 errdetail("unable to get the password")));
		return 0;
	}

	size = strlen(pwd);

	/* connection reusing? */
	if (reauth)
	{
		if (size != backend->pwd_size)
			ereport(ERROR,
					(errmsg("clear text password authentication failed"),
					 errdetail("password does not match")));

		if (memcmp(pwd, backend->password, backend->pwd_size) != 0)
			ereport(ERROR,
					(errmsg("clear text password authentication failed"),
					 errdetail("password does not match")));

		return 0;
	}

	kind = send_password_packet(backend, protoMajor, pwd);

	/* if authenticated, save info */
	if (kind == AUTH_REQ_OK)
	{
		if (IS_MAIN_NODE_ID(backend->db_node_id))
		{
			send_auth_ok(frontend, protoMajor);
		}

		backend->auth_kind = AUTH_REQ_PASSWORD;
		backend->pwd_size = size;
		memcpy(backend->password, pwd, backend->pwd_size);
		backend->passwordType = PASSWORD_TYPE_PLAINTEXT;
	}
	return kind;
}

/*
 * Do the SCRAM authentication with the frontend using the stored
 * password in the pool_passwd file.
 */
static void
authenticate_frontend_SCRAM(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth)
{
	void	   *scram_opaq;
	char	   *output = NULL;
	int			outputlen = 0;
	char	   *input;
	int			inputlen;
	int			result;
	bool		initial;
	char	   *logdetail = NULL;
	char	   *shadow_pass;

	PasswordType storedPasswordType = PASSWORD_TYPE_UNKNOWN;
	char	   *storedPassword = NULL;

	if (get_auth_password(backend, frontend, reauth, &storedPassword, &storedPasswordType) == false)
	{
		ereport(FATAL,
				(return_code(2),
				 errmsg("SCRAM authentication failed"),
				 errdetail("pool_passwd file does not contain an entry for \"%s\"", frontend->username)));
	}

	if (storedPasswordType == PASSWORD_TYPE_AES)
	{
		/*
		 * decrypt the stored AES password for comparing it
		 */
		storedPassword = get_decrypted_password(storedPassword);
		if (storedPassword == NULL)
			ereport(ERROR,
					(errmsg("SCRAM authentication failed"),
					 errdetail("unable to decrypt password from pool_passwd"),
					 errhint("verify the valid pool_key exists")));
		/* we have converted the password to plain text */
		storedPasswordType = PASSWORD_TYPE_PLAINTEXT;
	}

	if (storedPasswordType != PASSWORD_TYPE_PLAINTEXT)
	{
		ereport(ERROR,
				(errmsg("SCRAM authentication failed"),
				 errdetail("username \"%s\" has invalid password type", frontend->username)));
	}

	shadow_pass = pg_be_scram_build_verifier(storedPassword);
	if (!shadow_pass)
		ereport(ERROR,
				(errmsg("authentication failed"),
				 errdetail("failed to build the scram verifier")));

	/*
	 * SASL auth is not supported for protocol versions before 3, because it
	 * relies on the overall message length word to determine the SASL payload
	 * size in AuthenticationSASLContinue and PasswordMessage messages.  (We
	 * used to have a hard rule that protocol messages must be parsable
	 * without relying on the length word, but we hardly care about older
	 * protocol version anymore.)
	 */
	if (frontend->protoVersion < PROTO_MAJOR_V3)
		ereport(FATAL,
				(errmsg("SASL authentication is not supported in protocol version 2")));

	/*
	 * Send the SASL authentication request to user.  It includes the list of
	 * authentication mechanisms (which is trivial, because we only support
	 * SCRAM-SHA-256 at the moment).  The extra "\0" is for an empty string to
	 * terminate the list.
	 */

	sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_SASL, SCRAM_SHA_256_NAME "\0",
					strlen(SCRAM_SHA_256_NAME) + 2);

	/*
	 * Initialize the status tracker for message exchanges.
	 *
	 * If the user doesn't exist, or doesn't have a valid password, or it's
	 * expired, we still go through the motions of SASL authentication, but
	 * tell the authentication method that the authentication is "doomed".
	 * That is, it's going to fail, no matter what.
	 *
	 * This is because we don't want to reveal to an attacker what usernames
	 * are valid, nor which users have a valid password.
	 */
	scram_opaq = pg_be_scram_init(frontend->username, shadow_pass);

	/*
	 * Loop through SASL message exchange.  This exchange can consist of
	 * multiple messages sent in both directions.  First message is always
	 * from the client.  All messages from client to server are password
	 * packets (type 'p').
	 */
	initial = true;
	do
	{
		static int	size;
		static char data[MAX_PASSWORD_SIZE];

		/* Read password packet */
		read_password_packet(frontend, frontend->protoVersion, data, &size);

		ereport(DEBUG4,
				(errmsg("Processing received SASL response of length %d", size)));

		/*
		 * The first SASLInitialResponse message is different from the others.
		 * It indicates which SASL mechanism the client selected, and contains
		 * an optional Initial Client Response payload.  The subsequent
		 * SASLResponse messages contain just the SASL payload.
		 */
		if (initial)
		{
			const char *selected_mech;
			char	   *ptr = data;

			/*
			 * We only support SCRAM-SHA-256 at the moment, so anything else
			 * is an error.
			 */
			selected_mech = data;
			if (strcmp(selected_mech, SCRAM_SHA_256_NAME) != 0)
			{
				ereport(ERROR,
						(errmsg("client selected an invalid SASL authentication mechanism")));
			}
			/* get the length of trailing optional data */
			ptr += strlen(selected_mech) + 1;
			memcpy(&inputlen, ptr, sizeof(int));
			inputlen = ntohl(inputlen);
			if (inputlen == -1)
				input = NULL;
			else
				input = ptr + 4;

			initial = false;
		}
		else
		{
			inputlen = size;
			input = data;
		}
		Assert(input == NULL || input[inputlen] == '\0');

		/*
		 * we pass 'logdetail' as NULL when doing a mock authentication,
		 * because we should already have a better error message in that case
		 */
		result = pg_be_scram_exchange(scram_opaq, input, inputlen,
									  &output, &outputlen,
									  &logdetail);

		/* input buffer no longer used */

		if (output)
		{
			/*
			 * Negotiation generated data to be sent to the client.
			 */
			ereport(DEBUG4,
					(errmsg("sending SASL challenge of length %u", outputlen)));

			if (result == SASL_EXCHANGE_SUCCESS)
				sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_SASL_FIN, output, outputlen);
			else
				sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_SASL_CONT, output, outputlen);

			pfree(output);
		}
	} while (result == SASL_EXCHANGE_CONTINUE);

	/* Oops, Something bad happened */
	if (result != SASL_EXCHANGE_SUCCESS)
	{
		ereport(ERROR,
				(errmsg("authentication failed"),
				 errdetail("username \"%s\" or password does not exist in backend", frontend->username)));
	}
	frontend->frontend_authenticated = true;
}

/*
 * Authenticate frontend using pool_hba.conf
 */
void
authenticate_frontend(POOL_CONNECTION *frontend)
{
	switch (frontend->pool_hba->auth_method)
	{
		case uaMD5:
			authenticate_frontend_md5(NULL, frontend, 0, frontend->protoVersion);
			break;
		case uaCert:
			authenticate_frontend_cert(frontend);
			break;
		case uaSCRAM:
			authenticate_frontend_SCRAM(NULL, frontend, 0);
			break;
		case uaPassword:
			authenticate_frontend_clear_text(frontend);
			break;
		case uaImplicitReject:
		case uaReject:
			ereport(ERROR,
					(errmsg("authentication with pgpool failed for user \"%s\" rejected", frontend->username)));
			break;
		case uaTrust:
			frontend->frontend_authenticated = true;
			break;
#ifdef USE_PAM
		case uaPAM:
			break;
#endif							/* USE_PAM */
#ifdef USE_LDAP
		case uaLDAP:
			break;
#endif							/* USE_LDAP */

	}
}

#ifdef USE_SSL
static void
authenticate_frontend_cert(POOL_CONNECTION *frontend)
{
	if (frontend->client_cert_loaded == true && frontend->cert_cn)
	{
		ereport(DEBUG1,
				(errmsg("connecting user is \"%s\" and ssl certificate CN is \"%s\"", frontend->username, frontend->cert_cn)));
		if (strcasecmp(frontend->username, frontend->cert_cn) == 0)
		{
			frontend->frontend_authenticated = true;
			ereport(LOG,
					(errmsg("SSL certificate authentication for user \"%s\" with Pgpool-II is successful", frontend->username)));
			return;
		}
		else
		{
			frontend->frontend_authenticated = false;
			ereport(LOG,
					(errmsg("SSL certificate authentication for user \"%s\" failed", frontend->username)));
		}
	}
	ereport(ERROR,
			(errmsg("CERT authentication failed"),
			 errdetail("no valid certificate presented")));

}
#else
static void
authenticate_frontend_cert(POOL_CONNECTION *frontend)
{
	ereport(ERROR,
			(errmsg("CERT authentication failed"),
			 errdetail("CERT authentication is not supported without SSL")));
}
#endif

static void
authenticate_frontend_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor)
{
	char		salt[4];
	static int	size;
	char		password[MAX_PASSWORD_SIZE];
	char		userPassword[MAX_PASSWORD_SIZE];
	char		encbuf[POOL_PASSWD_LEN + 1];
	char	   *md5;

	PasswordType storedPasswordType = PASSWORD_TYPE_UNKNOWN;
	char	   *storedPassword = NULL;

	if (get_auth_password(backend, frontend, reauth, &storedPassword, &storedPasswordType) == false)
	{
		ereport(FATAL,
				(return_code(2),
				 errmsg("md5 authentication failed"),
				 errdetail("pool_passwd file does not contain an entry for \"%s\"", frontend->username)));
	}

	pool_random_salt(salt);
	send_md5auth_request(frontend, frontend->protoVersion, salt);

	/* Read password packet */
	read_password_packet(frontend, frontend->protoVersion, password, &size);

	/* If we have clear text password stored in pool_passwd, convert it to md5 */
	if (storedPasswordType == PASSWORD_TYPE_PLAINTEXT || storedPasswordType == PASSWORD_TYPE_AES)
	{
		char	   *pwd;

		if (storedPasswordType == PASSWORD_TYPE_AES)
		{
			pwd = get_decrypted_password(storedPassword);
			if (pwd == NULL)
				ereport(ERROR,
						(errmsg("md5 authentication failed"),
						 errdetail("unable to decrypt password from pool_passwd"),
						 errhint("verify the valid pool_key exists")));
		}
		else
		{
			pwd = storedPassword;
		}
		pg_md5_encrypt(pwd,
					   frontend->username,
					   strlen(frontend->username), userPassword);

		if (storedPasswordType == PASSWORD_TYPE_AES)
			pfree(pwd);

		md5 = userPassword;
	}
	else if (storedPasswordType == PASSWORD_TYPE_MD5)
	{
		md5 = storedPassword;
	}
	else
	{
		ereport(FATAL,
				(return_code(2),
				 errmsg("md5 authentication failed"),
				 errdetail("unable to get the password for \"%s\"", frontend->username)));
	}

	/* Check the password using my salt + pool_passwd */
	pg_md5_encrypt(md5 + strlen("md5"), salt, sizeof(salt), encbuf);
	if (strcmp(password, encbuf))
	{
		/* Password does not match */
		ereport(ERROR,
				(errmsg("md5 authentication failed"),
				 errdetail("password does not match")));
	}
	ereport(DEBUG1,
			(errmsg("md5 authentication successful with frontend")));

	frontend->frontend_authenticated = true;
}

/*
 * Get user's password from various sources. Firstly try pool_passwd and
 * return it if the user's password is there. If reauth == 0 (first time the
 * user connect to pgpool) and the password is in frontend, return it. If
 * reauth != 0 (reuse connection pool) and the password is in backendm return
 * it.
 */
static bool
get_auth_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth,
				  char **password, PasswordType *passwordType)
{
	/* First preference is to use the pool_passwd file */
	if (frontend->passwordMapping == NULL)
		frontend->passwordMapping = pool_get_user_credentials(frontend->username);

	if (frontend->passwordMapping == NULL)
	{
		/*
		 * check if we have password stored in the frontend connection. that
		 * could come by using the clear text auth
		 */
		if (!reauth &&
			frontend->pwd_size > 0 && frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
		{
			*password = frontend->password;
			*passwordType = frontend->passwordType;
			return true;
		}
		else if (reauth && backend && backend->pwd_size > 0)
		{
			*password = backend->password;
			*passwordType = backend->passwordType;
			return true;
		}
	}
	else
	{
		if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_TEXT_PREFIXED)
		{
			/* convert the TEXT prefixed password to plain text password */
			*passwordType = PASSWORD_TYPE_PLAINTEXT;
			*password = frontend->passwordMapping->pgpoolUser.password + strlen(PASSWORD_TEXT_PREFIX);
		}
		else
		{
			*password = frontend->passwordMapping->pgpoolUser.password;
			*passwordType = frontend->passwordMapping->pgpoolUser.passwordType;
		}
		return true;
	}
	return false;
}

/*
 * perform MD5 authentication
 */
static int
do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor,
	   char *storedPassword, PasswordType passwordType)
{
	char		salt[4];
	static char userPassword[MAX_PASSWORD_SIZE];
	int			kind;
	bool		password_decrypted = false;
	char		encbuf[POOL_PASSWD_LEN + 1];
	char	   *pool_passwd = NULL;

	if (passwordType == PASSWORD_TYPE_AES)
	{
		/*
		 * decrypt the stored AES password for comparing it
		 */
		storedPassword = get_decrypted_password(storedPassword);
		if (storedPassword == NULL)
			ereport(ERROR,
					(errmsg("md5 authentication failed"),
					 errdetail("unable to decrypt password from pool_passwd"),
					 errhint("verify the valid pool_key exists")));
		/* we have converted the password to plain text */
		passwordType = PASSWORD_TYPE_PLAINTEXT;
		password_decrypted = true;
	}
	if (passwordType == PASSWORD_TYPE_PLAINTEXT)
	{
		pg_md5_encrypt(storedPassword,
					   frontend->username,
					   strlen(frontend->username), userPassword);
		pool_passwd = userPassword;
	}
	else if (passwordType == PASSWORD_TYPE_MD5)
	{
		pool_passwd = storedPassword;
	}
	else
	{
		ereport(ERROR,
				(errmsg("md5 authentication failed"),
				 errdetail("unable to get the password")));
	}

	/* main? */
	if (IS_MAIN_NODE_ID(backend->db_node_id) && frontend->frontend_authenticated == false)
	{
		/*
		 * If frontend is not authenticated and do it it first. but if we have
		 * already received the password from frontend using the clear text
		 * auth, we may not need to authenticate it
		 */
		if ((pool_config->allow_clear_text_frontend_auth ||
			 frontend->auth_kind == AUTH_REQ_PASSWORD) &&
			frontend->pwd_size > 0 &&
			frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
		{
			ereport(DEBUG2,
					(errmsg("MD5 authentication using the password from frontend")));
			/* save this password in backend for the re-auth */
			backend->pwd_size = frontend->pwd_size;
			memcpy(backend->password, frontend->password, frontend->pwd_size);
			backend->password[backend->pwd_size] = 0;	/* null terminate */
			backend->passwordType = frontend->passwordType;
		}
		else
		{
			authenticate_frontend_md5(backend, frontend, reauth, protoMajor);
		}
	}

	if (!reauth)
	{
		/*
		 * now authenticate the backend
		 */

		/* Read salt */
		pool_read(backend, salt, sizeof(salt));

		ereport(DEBUG2,
				(errmsg("performing md5 authentication"),
				 errdetail("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id,
						   salt[0], salt[1], salt[2], salt[3])));

		/* Encrypt password in pool_passwd using the salt */
		pg_md5_encrypt(pool_passwd + strlen("md5"), salt, sizeof(salt), encbuf);

		/* Send password packet to backend and receive auth response */
		kind = send_password_packet(backend, protoMajor, encbuf);
		if (kind < 0)
			ereport(ERROR,
					(errmsg("md5 authentication failed"),
					 errdetail("backend replied with invalid kind")));
	}

	if (!reauth && kind == 0)
	{
		if (IS_MAIN_NODE_ID(backend->db_node_id))
		{
			/* Send auth ok to frontend */
			send_auth_ok(frontend, protoMajor);
		}

		/* Save the auth info */
		backend->auth_kind = AUTH_REQ_MD5;
	}

	if (password_decrypted && storedPassword)
		pfree(storedPassword);

	return 0;
}

/*
 * Send an authentication request packet to the frontend.
 */
static void
sendAuthRequest(POOL_CONNECTION *frontend, int protoMajor, int32 auth_req_type, char *extradata, int extralen)
{
	int			kind = htonl(auth_req_type);

	pool_write(frontend, "R", 1);	/* authentication */
	if (protoMajor == PROTO_MAJOR_V3)
	{
		int			len = 8 + extralen;

		len = htonl(len);
		pool_write(frontend, &len, sizeof(len));
	}
	pool_write(frontend, &kind, sizeof(kind));
	if (extralen > 0)
		pool_write_and_flush(frontend, extradata, extralen);
	else
		pool_flush(frontend);
}

/*
 * Send md5 authentication request packet to frontend
 */
static void
send_md5auth_request(POOL_CONNECTION *frontend, int protoMajor, char *salt)
{
	sendAuthRequest(frontend, protoMajor, AUTH_REQ_MD5, salt, 4);
}


/*
 * Read password packet from frontend
 */
static int
read_password_packet(POOL_CONNECTION *frontend, int protoMajor, char *password, int *pwdSize)
{
	int			size;

	/* Read password packet */
	if (protoMajor == PROTO_MAJOR_V2)
	{
		pool_read(frontend, &size, sizeof(size));
	}
	else
	{
		char		k;

		pool_read(frontend, &k, sizeof(k));
		if (k != 'p')
			ereport(ERROR,
					(errmsg("authentication failed"),
					 errdetail("invalid authentication packet. password packet does not start with \"p\"")));

		pool_read(frontend, &size, sizeof(size));
	}

	*pwdSize = ntohl(size) - 4;
	if (*pwdSize > MAX_PASSWORD_SIZE)
	{
		ereport(ERROR,
				(errmsg("authentication failed"),
				 errdetail("invalid authentication packet. password is too long. password length is %d", *pwdSize)));

		/*
		 * We do not read to throw away packet here. Since it is possible that
		 * it's a denial of service attack.
		 */
	}
	else if (*pwdSize <= 0)
		ereport(ERROR,
				(errmsg("authentication failed"),
				 errdetail("invalid authentication packet. invalid password length. password length is %d", *pwdSize)));

	pool_read(frontend, password, *pwdSize);

	password[*pwdSize] = '\0';

	return 0;
}

/*
 * Send password packet to backend and receive authentication response
 * packet.  Return value is the last field of authentication
 * response. If it's 0, authentication was successful.
 * "password" must be null-terminated.
 */
static int
send_password_packet(POOL_CONNECTION *backend, int protoMajor, char *password)
{
	int			size;
	int			len;
	int			kind;
	char		response;

	/* Send password packet to backend */
	if (protoMajor == PROTO_MAJOR_V3)
		pool_write(backend, "p", 1);
	size = htonl(sizeof(size) + strlen(password) + 1);
	pool_write(backend, &size, sizeof(size));
	pool_write_and_flush(backend, password, strlen(password) + 1);

	pool_read(backend, &response, sizeof(response));

	if (response != 'R')
	{
		if (response == 'E')	/* Backend has thrown an error instead */
		{
			char	   *message = NULL;

			if (pool_extract_error_message(false, backend, protoMajor, false, &message) == 1)
			{
				ereport(ERROR,
						(errmsg("authentication failed"),
						 errdetail("%s", message ? message : "backend throws authentication error")));
			}
			if (message)
				pfree(message);
		}
		ereport(ERROR,
				(errmsg("authentication failed"),
				 errdetail("invalid backend response. Response does not replied with \"R\"")));
	}

	if (protoMajor == PROTO_MAJOR_V3)
	{
		pool_read(backend, &len, sizeof(len));

		if (ntohl(len) != 8)
			ereport(ERROR,
					(errmsg("authentication failed"),
					 errdetail("invalid authentication packet. incorrect authentication packet size (%d)", ntohl(len))));
	}

	/* Expect to read "Authentication OK" response. kind should be 0... */
	pool_read(backend, &kind, sizeof(kind));

	return kind;
}

/*
 * Send auth ok to frontend
 */
static int
send_auth_ok(POOL_CONNECTION *frontend, int protoMajor)
{
	int			msglen;

	pool_write(frontend, "R", 1);

	if (protoMajor == PROTO_MAJOR_V3)
	{
		msglen = htonl(8);
		pool_write(frontend, &msglen, sizeof(msglen));
	}

	msglen = htonl(0);
	pool_write_and_flush(frontend, &msglen, sizeof(msglen));
	return 0;
}

/*
 * Generate random bytes
 */
void
pool_random(void *buf, size_t len)
{
	if (!pg_strong_random(buf, len))
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not generate random bytes")));
}

/*
 *  pool_random_salt
 */
void
pool_random_salt(char *md5Salt)
{
	pool_random(md5Salt, 4);
}

static bool
do_SCRAM(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, int protoMajor, int message_length,
		 char *username, char *storedPassword, PasswordType passwordType)
{
	/* read the packet first */
	void	   *sasl_state = NULL;
	int			payload_len = message_length - 4 - 4;
	int			auth_kind = AUTH_REQ_SASL;
	char		payload[MAX_SASL_PAYLOAD_LEN];

	if (passwordType != PASSWORD_TYPE_PLAINTEXT)
	{
		ereport(ERROR,
				(errmsg("SCRAM authentication failed"),
				 errdetail("invalid password type")));
	}
	if (storedPassword == NULL)
	{
		ereport(ERROR,
				(errmsg("SCRAM authentication failed"),
				 errdetail("password not found")));
	}

	/* main? */
	if (frontend && IS_MAIN_NODE_ID(backend->db_node_id) && frontend->frontend_authenticated == false)
	{
		/*
		 * If frontend is not authenticated and do it it first. but if we have
		 * already received the password from frontend using the clear text
		 * auth, we may not need to authenticate it
		 */
		if ((pool_config->allow_clear_text_frontend_auth ||
			 frontend->auth_kind == AUTH_REQ_PASSWORD) &&
			frontend->pwd_size > 0 &&
			frontend->passwordType == PASSWORD_TYPE_PLAINTEXT)
		{
			ereport(DEBUG2,
					(errmsg("SCRAM authentication using the password from frontend")));
			/* save this password in backend for the re-auth */
			backend->pwd_size = frontend->pwd_size;
			memcpy(backend->password, frontend->password, frontend->pwd_size);
			backend->password[backend->pwd_size] = 0;	/* null terminate */
			backend->passwordType = frontend->passwordType;
		}
		else
		{
			authenticate_frontend_SCRAM(backend, frontend, 0);
		}
	}

	for (;;)
	{
		char		kind;
		int			len;

		/*
		 * at this point we have already read kind, message length and
		 * authkind
		 */
		if (payload_len > MAX_SASL_PAYLOAD_LEN)
			ereport(ERROR,
					(errmsg("invalid authentication data too big")));

		pool_read(backend, payload, payload_len);

		switch (auth_kind)
		{
			case AUTH_REQ_OK:
				/* Save the auth info in backend */
				backend->auth_kind = AUTH_REQ_SASL;
				if (sasl_state)
					pg_fe_scram_free(sasl_state);
				return true;
				break;
			case AUTH_REQ_SASL:

				/*
				 * The request contains the name (as assigned by IANA) of the
				 * authentication mechanism.
				 */
				if (sasl_state)
					pg_fe_scram_free(sasl_state);
				sasl_state = pg_SASL_init(backend, payload, payload_len, username, storedPassword);
				if (!sasl_state)
				{
					ereport(ERROR,
							(errmsg("invalid authentication request from server")));
					return false;
				}
				break;

			case AUTH_REQ_SASL_CONT:
			case AUTH_REQ_SASL_FIN:
				if (sasl_state == NULL)
				{
					ereport(ERROR,
							(errmsg("invalid authentication request from server: AUTH_REQ_SASL_CONT without AUTH_REQ_SASL")));
					return false;
				}
				if (pg_SASL_continue(backend, payload, payload_len, sasl_state, (auth_kind == AUTH_REQ_SASL_FIN)) != 0)
				{
					/* Use error message, if set already */
					ereport(ERROR,
							(errmsg("error in SASL authentication")));

				}
				break;
			default:
				ereport(ERROR,
						(errmsg("invalid authentication request from server: unknown auth kind %d", auth_kind)));
		}
		/* Read next backend */
		pool_read(backend, &kind, sizeof(kind));
		pool_read(backend, &len, sizeof(len));
		if (kind != 'R')
			ereport(ERROR,
					(errmsg("backend authentication failed"),
					 errdetail("backend response with kind \'%c\' when expecting \'R\'", kind)));
		message_length = ntohl(len);
		if (message_length < 8)
			ereport(ERROR,
					(errmsg("backend authentication failed"),
					 errdetail("backend response with no data ")));

		pool_read(backend, &auth_kind, sizeof(auth_kind));
		auth_kind = ntohl(auth_kind);
		payload_len = message_length - 4 - 4;
	}
	return false;
}

static void *
pg_SASL_init(POOL_CONNECTION *backend, char *payload, int payloadlen, char *username, char *storedPassword)
{
	char	   *initialresponse = NULL;
	int			initialresponselen;
	bool		done;
	bool		success;
	const char *selected_mechanism;
	char	   *mechanism_buf = payload;
	void	   *sasl_state = NULL;
	int			size;
	int			send_msg_len;

	/*
	 * Parse the list of SASL authentication mechanisms in the
	 * AuthenticationSASL message, and select the best mechanism that we
	 * support.  (Only SCRAM-SHA-256 is supported at the moment.)
	 */
	selected_mechanism = NULL;
	for (;;)
	{
		/* An empty string indicates end of list */
		if (mechanism_buf[0] == '\0')
			break;

		/*
		 * If we have already selected a mechanism, just skip through the rest
		 * of the list.
		 */
		if (selected_mechanism)
			continue;

		/*
		 * Do we support this mechanism?
		 */
		if (strcmp(mechanism_buf, SCRAM_SHA_256_NAME) == 0)
		{
			/*
			 * This is the password which we need to send to the PG backend
			 * for authentication. It is stored in the file
			 */
			if (storedPassword == NULL || storedPassword[0] == '\0')
			{
				ereport(ERROR,
						(errmsg("password not found")));
			}

			sasl_state = pg_fe_scram_init(username, storedPassword);
			if (!sasl_state)
				ereport(ERROR,
						(errmsg("SASL authentication error\n")));
			selected_mechanism = SCRAM_SHA_256_NAME;
		}
		mechanism_buf += strlen(mechanism_buf) + 1;
	}

	if (!selected_mechanism)
	{
		ereport(ERROR,
				(errmsg("none of the server's SASL authentication mechanisms are supported\n")));
	}

	/* Get the mechanism-specific Initial Client Response, if any */
	pg_fe_scram_exchange(sasl_state,
						 NULL, -1,
						 &initialresponse, &initialresponselen,
						 &done, &success);

	if (done && !success)
		ereport(ERROR,
				(errmsg("SASL authentication error")));

	send_msg_len = strlen(selected_mechanism) + 1;
	if (initialresponse)
	{
		send_msg_len += 4;
		send_msg_len += initialresponselen;
	}

	size = htonl(send_msg_len + 4);

	pool_write(backend, "p", 1);
	pool_write(backend, &size, sizeof(size));
	pool_write(backend, (void *) selected_mechanism, strlen(selected_mechanism) + 1);
	if (initialresponse)
	{
		size = htonl(initialresponselen);
		pool_write(backend, &size, sizeof(size));
		pool_write(backend, initialresponse, initialresponselen);
	}
	pool_flush(backend);

	if (initialresponse)
		pfree(initialresponse);

	return sasl_state;
}

/*
 * Exchange a message for SASL communication protocol with the backend.
 * This should be used after calling pg_SASL_init to set up the status of
 * the protocol.
 */
static int
pg_SASL_continue(POOL_CONNECTION *backend, char *payload, int payloadlen, void *sasl_state, bool final)
{
	char	   *output;
	int			outputlen;
	bool		done;
	bool		success;
	char	   *challenge;

	/* Read the SASL challenge from the AuthenticationSASLContinue message. */
	challenge = palloc(payloadlen + 1);
	memcpy(challenge, payload, payloadlen);
	challenge[payloadlen] = '\0';

	/* For safety and convenience, ensure the buffer is NULL-terminated. */

	pg_fe_scram_exchange(sasl_state,
						 challenge, payloadlen,
						 &output, &outputlen,
						 &done, &success);
	pfree(challenge);			/* don't need the input anymore */

	if (final && !done)
	{
		if (outputlen != 0)
			pfree(output);

		ereport(ERROR,
				(errmsg("AuthenticationSASLFinal received from server, but SASL authentication was not completed")));
		return -1;
	}
	if (outputlen != 0)
	{
		/*
		 * Send the SASL response to the server.
		 */
		int			size = htonl(outputlen + 4);

		pool_write(backend, "p", 1);
		pool_write(backend, &size, sizeof(size));
		pool_write_and_flush(backend, output, outputlen);
		pfree(output);

		return 0;
	}

	if (done && !success)
		return -1;

	return 0;
}

/*
 * Forward NegotiateProtocol message to frontend.
 *
 * When this function is called, message kind has been already read.
 */
static void
ProcessNegotiateProtocol(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp)
{
	int32		len;
	int32		savelen;
	int32		protoMajor;
	int32		protoMinor;
	int32		protov;
	bool		forwardMsg = false;
	int			i;

	elog(DEBUG1, "Forwarding NegotiateProtocol message to frontend");
	pool_write(frontend, "v", 1);	/* forward message kind */
	savelen = len = pool_read_int(cp);	/* message length including self */
	pool_write(frontend, &len, 4);	/* forward message length */
	len = ntohl(len) - 4;		/* length of rest of the message */
	protov = pool_read_int(cp); /* read protocol version */
	protoMajor = PG_PROTOCOL_MAJOR(ntohl(protov));	/* protocol major version */
	protoMinor = PG_PROTOCOL_MINOR(ntohl(protov));	/* protocol minor version */
	pool_write(frontend, &protov, 4);	/* forward protocol version */
	elog(DEBUG1, "protocol verion offered: major: %d minor: %d", protoMajor, protoMinor);
	len -= 4;
	for (i = 0; i < NUM_BACKENDS; i++)
	{
		if (VALID_BACKEND(i))
		{
			POOL_CONNECTION_POOL_SLOT *sp;
			char	   *p;
			char	   *np;
			Size		nplen;

			p = pool_read2(CONNECTION(cp, i), len);
			if (!forwardMsg)
			{
				int32	num_unsupported;
				StringInfoData	buf;
				pool_write_and_flush(frontend, p, len); /* forward rest of
														 * message */
				forwardMsg = true;

				/* debug out unsuported messages */
				memcpy(&num_unsupported, p, sizeof(int32));
				num_unsupported = ntohl(num_unsupported);
				p += sizeof(int32);
				if (num_unsupported > 0)
				{
					initStringInfo(&buf);
					for (int i = 0; i < num_unsupported; i++)
					{
						appendStringInfoString(&buf, p);
						appendStringInfoChar(&buf, ' ');
						p += strlen(p) + 1;
					}
					elog(DEBUG1, "unsupported options: %s", buf.data);
					pfree(buf.data);
				}
			}
			/* save negatiate protocol version */
			sp = CONNECTION_SLOT(cp, i);
			sp->negotiated_major = protoMajor;
			sp->negotiated_minor = protoMinor;

			/* save negatiate protocol message */
			nplen = 1 +			/* message kind */
				sizeof(savelen) +	/* message length */
				sizeof(protov) +	/* protocol version */
				len;			/* rest of message */
			/* allocate message area */
			sp->negotiateProtocolMsg = MemoryContextAlloc(TopMemoryContext, nplen);
			np = sp->negotiateProtocolMsg;
			sp->nplen = nplen;	/* set message length */

			*np++ = 'v';
			memcpy(np, &savelen, sizeof(savelen));
			np += sizeof(savelen);
			memcpy(np, &protov, sizeof(protov));
			np += sizeof(protov);
			memcpy(np, p, len);
		}
	}
}
