From c2b5168608e34acb171e0da6f4e9e16c14f82adb Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouhaud@free.fr>
Date: Mon, 28 Feb 2022 16:50:00 +0800
Subject: [PATCH v2 1/4] Extract view processing code from hba.c

This file is already quite big and a later commit will add an additional view,
so let's move all the view related code in hba.c into a new adt/hbafuncs.c.

Author: Julien Rouhaud
Reviewed-by: FIXME
Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud
---
 src/backend/libpq/hba.c          | 3618 +++++++++++++-----------------
 src/backend/utils/adt/Makefile   |    1 +
 src/backend/utils/adt/hbafuncs.c |  452 ++++
 src/include/libpq/hba.h          |   29 +
 src/tools/pgindent/typedefs.list |    2 +-
 5 files changed, 2067 insertions(+), 2035 deletions(-)
 create mode 100644 src/backend/utils/adt/hbafuncs.c

diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index d84a40b726..184df4942a 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -68,32 +68,6 @@ typedef struct check_network_data
 #define token_is_keyword(t, k)	(!t->quoted && strcmp(t->string, k) == 0)
 #define token_matches(t, k)  (strcmp(t->string, k) == 0)
 
-/*
- * A single string token lexed from a config file, together with whether
- * the token had been quoted.
- */
-typedef struct HbaToken
-{
-	char	   *string;
-	bool		quoted;
-} HbaToken;
-
-/*
- * TokenizedLine represents one line lexed from a config file.
- * Each item in the "fields" list is a sub-list of HbaTokens.
- * We don't emit a TokenizedLine for empty or all-comment lines,
- * so "fields" is never NIL (nor are any of its sub-lists).
- * Exception: if an error occurs during tokenization, we might
- * have fields == NIL, in which case err_msg != NULL.
- */
-typedef struct TokenizedLine
-{
-	List	   *fields;			/* List of lists of HbaTokens */
-	int			line_num;		/* Line number */
-	char	   *raw_line;		/* Raw line text */
-	char	   *err_msg;		/* Error message if any */
-} TokenizedLine;
-
 /*
  * pre-parsed content of HBA config file: list of HbaLine structs.
  * parsed_hba_context is the memory context where it lives.
@@ -138,16 +112,10 @@ static const char *const UserAuthName[] =
 };
 
 
-static MemoryContext tokenize_file(const char *filename, FILE *file,
-								   List **tok_lines, int elevel);
 static List *tokenize_inc_file(List *tokens, const char *outer_filename,
 							   const char *inc_filename, int elevel, char **err_msg);
 static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
 							   int elevel, char **err_msg);
-static ArrayType *gethba_options(HbaLine *hba);
-static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
-						  int lineno, HbaLine *hba, const char *err_msg);
-static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
 
 
 /*
@@ -419,7 +387,7 @@ tokenize_inc_file(List *tokens,
 	}
 
 	/* There is possible recursion here if the file contains @ */
-	linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, elevel);
+	linecxt = tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel);
 
 	FreeFile(inc_file);
 	pfree(inc_fullname);
@@ -427,7 +395,7 @@ tokenize_inc_file(List *tokens,
 	/* Copy all tokens found in the file and append to the tokens list */
 	foreach(inc_line, inc_lines)
 	{
-		TokenizedLine *tok_line = (TokenizedLine *) lfirst(inc_line);
+		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(inc_line);
 		ListCell   *inc_field;
 
 		/* If any line has an error, propagate that up to caller */
@@ -455,122 +423,6 @@ tokenize_inc_file(List *tokens,
 	return tokens;
 }
 
-/*
- * Tokenize the given file.
- *
- * The output is a list of TokenizedLine structs; see struct definition above.
- *
- * filename: the absolute path to the target file
- * file: the already-opened target file
- * tok_lines: receives output list
- * elevel: message logging level
- *
- * Errors are reported by logging messages at ereport level elevel and by
- * adding TokenizedLine structs containing non-null err_msg fields to the
- * output list.
- *
- * Return value is a memory context which contains all memory allocated by
- * this function (it's a child of caller's context).
- */
-static MemoryContext
-tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
-{
-	int			line_number = 1;
-	StringInfoData buf;
-	MemoryContext linecxt;
-	MemoryContext oldcxt;
-
-	linecxt = AllocSetContextCreate(CurrentMemoryContext,
-									"tokenize_file",
-									ALLOCSET_SMALL_SIZES);
-	oldcxt = MemoryContextSwitchTo(linecxt);
-
-	initStringInfo(&buf);
-
-	*tok_lines = NIL;
-
-	while (!feof(file) && !ferror(file))
-	{
-		char	   *lineptr;
-		List	   *current_line = NIL;
-		char	   *err_msg = NULL;
-		int			last_backslash_buflen = 0;
-		int			continuations = 0;
-
-		/* Collect the next input line, handling backslash continuations */
-		resetStringInfo(&buf);
-
-		while (pg_get_line_append(file, &buf, NULL))
-		{
-			/* Strip trailing newline, including \r in case we're on Windows */
-			buf.len = pg_strip_crlf(buf.data);
-
-			/*
-			 * Check for backslash continuation.  The backslash must be after
-			 * the last place we found a continuation, else two backslashes
-			 * followed by two \n's would behave surprisingly.
-			 */
-			if (buf.len > last_backslash_buflen &&
-				buf.data[buf.len - 1] == '\\')
-			{
-				/* Continuation, so strip it and keep reading */
-				buf.data[--buf.len] = '\0';
-				last_backslash_buflen = buf.len;
-				continuations++;
-				continue;
-			}
-
-			/* Nope, so we have the whole line */
-			break;
-		}
-
-		if (ferror(file))
-		{
-			/* I/O error! */
-			int			save_errno = errno;
-
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not read file \"%s\": %m", filename)));
-			err_msg = psprintf("could not read file \"%s\": %s",
-							   filename, strerror(save_errno));
-			break;
-		}
-
-		/* Parse fields */
-		lineptr = buf.data;
-		while (*lineptr && err_msg == NULL)
-		{
-			List	   *current_field;
-
-			current_field = next_field_expand(filename, &lineptr,
-											  elevel, &err_msg);
-			/* add field to line, unless we are at EOL or comment start */
-			if (current_field != NIL)
-				current_line = lappend(current_line, current_field);
-		}
-
-		/* Reached EOL; emit line to TokenizedLine list unless it's boring */
-		if (current_line != NIL || err_msg != NULL)
-		{
-			TokenizedLine *tok_line;
-
-			tok_line = (TokenizedLine *) palloc(sizeof(TokenizedLine));
-			tok_line->fields = current_line;
-			tok_line->line_num = line_number;
-			tok_line->raw_line = pstrdup(buf.data);
-			tok_line->err_msg = err_msg;
-			*tok_lines = lappend(*tok_lines, tok_line);
-		}
-
-		line_number += continuations + 1;
-	}
-
-	MemoryContextSwitchTo(oldcxt);
-
-	return linecxt;
-}
-
 
 /*
  * Does user belong to role?
@@ -950,2217 +802,1915 @@ do { \
 } while (0)
 
 
+
 /*
- * Parse one tokenised line from the hba config file and store the result in a
- * HbaLine structure.
- *
- * If parsing fails, log a message at ereport level elevel, store an error
- * string in tok_line->err_msg, and return NULL.  (Some non-error conditions
- * can also result in such messages.)
- *
- * Note: this function leaks memory when an error occurs.  Caller is expected
- * to have set a memory context that will be reset if this function returns
- * NULL.
+ * Parse one name-value pair as an authentication option into the given
+ * HbaLine.  Return true if we successfully parse the option, false if we
+ * encounter an error.  In the event of an error, also log a message at
+ * ereport level elevel, and store a message string into *err_msg.
  */
-static HbaLine *
-parse_hba_line(TokenizedLine *tok_line, int elevel)
+static bool
+parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
+				   int elevel, char **err_msg)
 {
-	int			line_num = tok_line->line_num;
-	char	  **err_msg = &tok_line->err_msg;
-	char	   *str;
-	struct addrinfo *gai_result;
-	struct addrinfo hints;
-	int			ret;
-	char	   *cidr_slash;
-	char	   *unsupauth;
-	ListCell   *field;
-	List	   *tokens;
-	ListCell   *tokencell;
-	HbaToken   *token;
-	HbaLine    *parsedline;
+	int			line_num = hbaline->linenumber;
 
-	parsedline = palloc0(sizeof(HbaLine));
-	parsedline->linenumber = line_num;
-	parsedline->rawline = pstrdup(tok_line->raw_line);
+#ifdef USE_LDAP
+	hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
+#endif
 
-	/* Check the record type. */
-	Assert(tok_line->fields != NIL);
-	field = list_head(tok_line->fields);
-	tokens = lfirst(field);
-	if (tokens->length > 1)
-	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("multiple values specified for connection type"),
-				 errhint("Specify exactly one connection type per line."),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "multiple values specified for connection type";
-		return NULL;
-	}
-	token = linitial(tokens);
-	if (strcmp(token->string, "local") == 0)
+	if (strcmp(name, "map") == 0)
 	{
-#ifdef HAVE_UNIX_SOCKETS
-		parsedline->conntype = ctLocal;
-#else
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("local connections are not supported by this build"),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "local connections are not supported by this build";
-		return NULL;
-#endif
+		if (hbaline->auth_method != uaIdent &&
+			hbaline->auth_method != uaPeer &&
+			hbaline->auth_method != uaGSS &&
+			hbaline->auth_method != uaSSPI &&
+			hbaline->auth_method != uaCert)
+			INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
+		hbaline->usermap = pstrdup(val);
 	}
-	else if (strcmp(token->string, "host") == 0 ||
-			 strcmp(token->string, "hostssl") == 0 ||
-			 strcmp(token->string, "hostnossl") == 0 ||
-			 strcmp(token->string, "hostgssenc") == 0 ||
-			 strcmp(token->string, "hostnogssenc") == 0)
+	else if (strcmp(name, "clientcert") == 0)
 	{
+		if (hbaline->conntype != ctHostSSL)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("clientcert can only be configured for \"hostssl\" rows"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "clientcert can only be configured for \"hostssl\" rows";
+			return false;
+		}
 
-		if (token->string[4] == 's')	/* "hostssl" */
+		if (strcmp(val, "verify-full") == 0)
 		{
-			parsedline->conntype = ctHostSSL;
-			/* Log a warning if SSL support is not active */
-#ifdef USE_SSL
-			if (!EnableSSL)
+			hbaline->clientcert = clientCertFull;
+		}
+		else if (strcmp(val, "verify-ca") == 0)
+		{
+			if (hbaline->auth_method == uaCert)
 			{
 				ereport(elevel,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("hostssl record cannot match because SSL is disabled"),
-						 errhint("Set ssl = on in postgresql.conf."),
+						 errmsg("clientcert only accepts \"verify-full\" when using \"cert\" authentication"),
 						 errcontext("line %d of configuration file \"%s\"",
 									line_num, HbaFileName)));
-				*err_msg = "hostssl record cannot match because SSL is disabled";
+				*err_msg = "clientcert can only be set to \"verify-full\" when using \"cert\" authentication";
+				return false;
 			}
-#else
+
+			hbaline->clientcert = clientCertCA;
+		}
+		else
+		{
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("hostssl record cannot match because SSL is not supported by this build"),
-					 errhint("Compile with --with-ssl to use SSL connections."),
+					 errmsg("invalid value for clientcert: \"%s\"", val),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			*err_msg = "hostssl record cannot match because SSL is not supported by this build";
-#endif
+			return false;
 		}
-		else if (token->string[4] == 'g')	/* "hostgssenc" */
+	}
+	else if (strcmp(name, "clientname") == 0)
+	{
+		if (hbaline->conntype != ctHostSSL)
 		{
-			parsedline->conntype = ctHostGSS;
-#ifndef ENABLE_GSS
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("hostgssenc record cannot match because GSSAPI is not supported by this build"),
-					 errhint("Compile with --with-gssapi to use GSSAPI connections."),
+					 errmsg("clientname can only be configured for \"hostssl\" rows"),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			*err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
-#endif
+			*err_msg = "clientname can only be configured for \"hostssl\" rows";
+			return false;
 		}
-		else if (token->string[4] == 'n' && token->string[6] == 's')
-			parsedline->conntype = ctHostNoSSL;
-		else if (token->string[4] == 'n' && token->string[6] == 'g')
-			parsedline->conntype = ctHostNoGSS;
-		else
+
+		if (strcmp(val, "CN") == 0)
 		{
-			/* "host" */
-			parsedline->conntype = ctHost;
+			hbaline->clientcertname = clientCertCN;
 		}
-	}							/* record type */
-	else
+		else if (strcmp(val, "DN") == 0)
+		{
+			hbaline->clientcertname = clientCertDN;
+		}
+		else
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("invalid value for clientname: \"%s\"", val),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return false;
+		}
+	}
+	else if (strcmp(name, "pamservice") == 0)
 	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("invalid connection type \"%s\"",
-						token->string),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = psprintf("invalid connection type \"%s\"", token->string);
-		return NULL;
+		REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
+		hbaline->pamservice = pstrdup(val);
 	}
+	else if (strcmp(name, "pam_use_hostname") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaPAM, "pam_use_hostname", "pam");
+		if (strcmp(val, "1") == 0)
+			hbaline->pam_use_hostname = true;
+		else
+			hbaline->pam_use_hostname = false;
 
-	/* Get the databases. */
-	field = lnext(tok_line->fields, field);
-	if (!field)
+	}
+	else if (strcmp(name, "ldapurl") == 0)
 	{
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+		LDAPURLDesc *urldata;
+		int			rc;
+#endif
+
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapurl", "ldap");
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+		rc = ldap_url_parse(val, &urldata);
+		if (rc != LDAP_SUCCESS)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
+			*err_msg = psprintf("could not parse LDAP URL \"%s\": %s",
+								val, ldap_err2string(rc));
+			return false;
+		}
+
+		if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
+			strcmp(urldata->lud_scheme, "ldaps") != 0)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
+			*err_msg = psprintf("unsupported LDAP URL scheme: %s",
+								urldata->lud_scheme);
+			ldap_free_urldesc(urldata);
+			return false;
+		}
+
+		if (urldata->lud_scheme)
+			hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
+		if (urldata->lud_host)
+			hbaline->ldapserver = pstrdup(urldata->lud_host);
+		hbaline->ldapport = urldata->lud_port;
+		if (urldata->lud_dn)
+			hbaline->ldapbasedn = pstrdup(urldata->lud_dn);
+
+		if (urldata->lud_attrs)
+			hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]);	/* only use first one */
+		hbaline->ldapscope = urldata->lud_scope;
+		if (urldata->lud_filter)
+			hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter);
+		ldap_free_urldesc(urldata);
+#else							/* not OpenLDAP */
 		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("end-of-line before database specification"),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "end-of-line before database specification";
-		return NULL;
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("LDAP URLs not supported on this platform")));
+		*err_msg = "LDAP URLs not supported on this platform";
+#endif							/* not OpenLDAP */
 	}
-	parsedline->databases = NIL;
-	tokens = lfirst(field);
-	foreach(tokencell, tokens)
+	else if (strcmp(name, "ldaptls") == 0)
 	{
-		parsedline->databases = lappend(parsedline->databases,
-										copy_hba_token(lfirst(tokencell)));
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
+		if (strcmp(val, "1") == 0)
+			hbaline->ldaptls = true;
+		else
+			hbaline->ldaptls = false;
 	}
-
-	/* Get the roles. */
-	field = lnext(tok_line->fields, field);
-	if (!field)
+	else if (strcmp(name, "ldapscheme") == 0)
 	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("end-of-line before role specification"),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "end-of-line before role specification";
-		return NULL;
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
+		if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0)
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("invalid ldapscheme value: \"%s\"", val),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+		hbaline->ldapscheme = pstrdup(val);
 	}
-	parsedline->roles = NIL;
-	tokens = lfirst(field);
-	foreach(tokencell, tokens)
+	else if (strcmp(name, "ldapserver") == 0)
 	{
-		parsedline->roles = lappend(parsedline->roles,
-									copy_hba_token(lfirst(tokencell)));
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
+		hbaline->ldapserver = pstrdup(val);
 	}
-
-	if (parsedline->conntype != ctLocal)
+	else if (strcmp(name, "ldapport") == 0)
 	{
-		/* Read the IP address field. (with or without CIDR netmask) */
-		field = lnext(tok_line->fields, field);
-		if (!field)
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
+		hbaline->ldapport = atoi(val);
+		if (hbaline->ldapport == 0)
 		{
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("end-of-line before IP address specification"),
+					 errmsg("invalid LDAP port number: \"%s\"", val),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			*err_msg = "end-of-line before IP address specification";
-			return NULL;
+			*err_msg = psprintf("invalid LDAP port number: \"%s\"", val);
+			return false;
 		}
-		tokens = lfirst(field);
-		if (tokens->length > 1)
+	}
+	else if (strcmp(name, "ldapbinddn") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
+		hbaline->ldapbinddn = pstrdup(val);
+	}
+	else if (strcmp(name, "ldapbindpasswd") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
+		hbaline->ldapbindpasswd = pstrdup(val);
+	}
+	else if (strcmp(name, "ldapsearchattribute") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
+		hbaline->ldapsearchattribute = pstrdup(val);
+	}
+	else if (strcmp(name, "ldapsearchfilter") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap");
+		hbaline->ldapsearchfilter = pstrdup(val);
+	}
+	else if (strcmp(name, "ldapbasedn") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
+		hbaline->ldapbasedn = pstrdup(val);
+	}
+	else if (strcmp(name, "ldapprefix") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
+		hbaline->ldapprefix = pstrdup(val);
+	}
+	else if (strcmp(name, "ldapsuffix") == 0)
+	{
+		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
+		hbaline->ldapsuffix = pstrdup(val);
+	}
+	else if (strcmp(name, "krb_realm") == 0)
+	{
+		if (hbaline->auth_method != uaGSS &&
+			hbaline->auth_method != uaSSPI)
+			INVALID_AUTH_OPTION("krb_realm", gettext_noop("gssapi and sspi"));
+		hbaline->krb_realm = pstrdup(val);
+	}
+	else if (strcmp(name, "include_realm") == 0)
+	{
+		if (hbaline->auth_method != uaGSS &&
+			hbaline->auth_method != uaSSPI)
+			INVALID_AUTH_OPTION("include_realm", gettext_noop("gssapi and sspi"));
+		if (strcmp(val, "1") == 0)
+			hbaline->include_realm = true;
+		else
+			hbaline->include_realm = false;
+	}
+	else if (strcmp(name, "compat_realm") == 0)
+	{
+		if (hbaline->auth_method != uaSSPI)
+			INVALID_AUTH_OPTION("compat_realm", gettext_noop("sspi"));
+		if (strcmp(val, "1") == 0)
+			hbaline->compat_realm = true;
+		else
+			hbaline->compat_realm = false;
+	}
+	else if (strcmp(name, "upn_username") == 0)
+	{
+		if (hbaline->auth_method != uaSSPI)
+			INVALID_AUTH_OPTION("upn_username", gettext_noop("sspi"));
+		if (strcmp(val, "1") == 0)
+			hbaline->upn_username = true;
+		else
+			hbaline->upn_username = false;
+	}
+	else if (strcmp(name, "radiusservers") == 0)
+	{
+		struct addrinfo *gai_result;
+		struct addrinfo hints;
+		int			ret;
+		List	   *parsed_servers;
+		ListCell   *l;
+		char	   *dupval = pstrdup(val);
+
+		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius");
+
+		if (!SplitGUCList(dupval, ',', &parsed_servers))
 		{
+			/* syntax error in list */
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("multiple values specified for host address"),
-					 errhint("Specify one address range per line."),
+					 errmsg("could not parse RADIUS server list \"%s\"",
+							val),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			*err_msg = "multiple values specified for host address";
-			return NULL;
+			return false;
 		}
-		token = linitial(tokens);
 
-		if (token_is_keyword(token, "all"))
-		{
-			parsedline->ip_cmp_method = ipCmpAll;
-		}
-		else if (token_is_keyword(token, "samehost"))
+		/* For each entry in the list, translate it */
+		foreach(l, parsed_servers)
 		{
-			/* Any IP on this host is allowed to connect */
-			parsedline->ip_cmp_method = ipCmpSameHost;
-		}
-		else if (token_is_keyword(token, "samenet"))
-		{
-			/* Any IP on the host's subnets is allowed to connect */
-			parsedline->ip_cmp_method = ipCmpSameNet;
-		}
-		else
-		{
-			/* IP and netmask are specified */
-			parsedline->ip_cmp_method = ipCmpMask;
-
-			/* need a modifiable copy of token */
-			str = pstrdup(token->string);
-
-			/* Check if it has a CIDR suffix and if so isolate it */
-			cidr_slash = strchr(str, '/');
-			if (cidr_slash)
-				*cidr_slash = '\0';
-
-			/* Get the IP address either way */
-			hints.ai_flags = AI_NUMERICHOST;
+			MemSet(&hints, 0, sizeof(hints));
+			hints.ai_socktype = SOCK_DGRAM;
 			hints.ai_family = AF_UNSPEC;
-			hints.ai_socktype = 0;
-			hints.ai_protocol = 0;
-			hints.ai_addrlen = 0;
-			hints.ai_canonname = NULL;
-			hints.ai_addr = NULL;
-			hints.ai_next = NULL;
 
-			ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result);
-			if (ret == 0 && gai_result)
-			{
-				memcpy(&parsedline->addr, gai_result->ai_addr,
-					   gai_result->ai_addrlen);
-				parsedline->addrlen = gai_result->ai_addrlen;
-			}
-			else if (ret == EAI_NONAME)
-				parsedline->hostname = str;
-			else
+			ret = pg_getaddrinfo_all((char *) lfirst(l), NULL, &hints, &gai_result);
+			if (ret || !gai_result)
 			{
 				ereport(elevel,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("invalid IP address \"%s\": %s",
-								str, gai_strerror(ret)),
+						 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
+								(char *) lfirst(l), gai_strerror(ret)),
 						 errcontext("line %d of configuration file \"%s\"",
 									line_num, HbaFileName)));
-				*err_msg = psprintf("invalid IP address \"%s\": %s",
-									str, gai_strerror(ret));
 				if (gai_result)
 					pg_freeaddrinfo_all(hints.ai_family, gai_result);
-				return NULL;
-			}
 
+				list_free(parsed_servers);
+				return false;
+			}
 			pg_freeaddrinfo_all(hints.ai_family, gai_result);
+		}
 
-			/* Get the netmask */
-			if (cidr_slash)
-			{
-				if (parsedline->hostname)
-				{
-					ereport(elevel,
-							(errcode(ERRCODE_CONFIG_FILE_ERROR),
-							 errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
-									token->string),
-							 errcontext("line %d of configuration file \"%s\"",
-										line_num, HbaFileName)));
-					*err_msg = psprintf("specifying both host name and CIDR mask is invalid: \"%s\"",
-										token->string);
-					return NULL;
-				}
+		/* All entries are OK, so store them */
+		hbaline->radiusservers = parsed_servers;
+		hbaline->radiusservers_s = pstrdup(val);
+	}
+	else if (strcmp(name, "radiusports") == 0)
+	{
+		List	   *parsed_ports;
+		ListCell   *l;
+		char	   *dupval = pstrdup(val);
 
-				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
-										  parsedline->addr.ss_family) < 0)
-				{
-					ereport(elevel,
-							(errcode(ERRCODE_CONFIG_FILE_ERROR),
-							 errmsg("invalid CIDR mask in address \"%s\"",
-									token->string),
-							 errcontext("line %d of configuration file \"%s\"",
-										line_num, HbaFileName)));
-					*err_msg = psprintf("invalid CIDR mask in address \"%s\"",
-										token->string);
-					return NULL;
-				}
-				parsedline->masklen = parsedline->addrlen;
-				pfree(str);
-			}
-			else if (!parsedline->hostname)
-			{
-				/* Read the mask field. */
-				pfree(str);
-				field = lnext(tok_line->fields, field);
-				if (!field)
-				{
-					ereport(elevel,
-							(errcode(ERRCODE_CONFIG_FILE_ERROR),
-							 errmsg("end-of-line before netmask specification"),
-							 errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
-							 errcontext("line %d of configuration file \"%s\"",
-										line_num, HbaFileName)));
-					*err_msg = "end-of-line before netmask specification";
-					return NULL;
-				}
-				tokens = lfirst(field);
-				if (tokens->length > 1)
-				{
-					ereport(elevel,
-							(errcode(ERRCODE_CONFIG_FILE_ERROR),
-							 errmsg("multiple values specified for netmask"),
-							 errcontext("line %d of configuration file \"%s\"",
-										line_num, HbaFileName)));
-					*err_msg = "multiple values specified for netmask";
-					return NULL;
-				}
-				token = linitial(tokens);
+		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
 
-				ret = pg_getaddrinfo_all(token->string, NULL,
-										 &hints, &gai_result);
-				if (ret || !gai_result)
-				{
-					ereport(elevel,
-							(errcode(ERRCODE_CONFIG_FILE_ERROR),
-							 errmsg("invalid IP mask \"%s\": %s",
-									token->string, gai_strerror(ret)),
-							 errcontext("line %d of configuration file \"%s\"",
-										line_num, HbaFileName)));
-					*err_msg = psprintf("invalid IP mask \"%s\": %s",
-										token->string, gai_strerror(ret));
-					if (gai_result)
-						pg_freeaddrinfo_all(hints.ai_family, gai_result);
-					return NULL;
-				}
+		if (!SplitGUCList(dupval, ',', &parsed_ports))
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("could not parse RADIUS port list \"%s\"",
+							val),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
+			return false;
+		}
 
-				memcpy(&parsedline->mask, gai_result->ai_addr,
-					   gai_result->ai_addrlen);
-				parsedline->masklen = gai_result->ai_addrlen;
-				pg_freeaddrinfo_all(hints.ai_family, gai_result);
+		foreach(l, parsed_ports)
+		{
+			if (atoi(lfirst(l)) == 0)
+			{
+				ereport(elevel,
+						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+						 errmsg("invalid RADIUS port number: \"%s\"", val),
+						 errcontext("line %d of configuration file \"%s\"",
+									line_num, HbaFileName)));
 
-				if (parsedline->addr.ss_family != parsedline->mask.ss_family)
-				{
-					ereport(elevel,
-							(errcode(ERRCODE_CONFIG_FILE_ERROR),
-							 errmsg("IP address and mask do not match"),
-							 errcontext("line %d of configuration file \"%s\"",
-										line_num, HbaFileName)));
-					*err_msg = "IP address and mask do not match";
-					return NULL;
-				}
+				return false;
 			}
 		}
-	}							/* != ctLocal */
-
-	/* Get the authentication method */
-	field = lnext(tok_line->fields, field);
-	if (!field)
-	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("end-of-line before authentication method"),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "end-of-line before authentication method";
-		return NULL;
+		hbaline->radiusports = parsed_ports;
+		hbaline->radiusports_s = pstrdup(val);
 	}
-	tokens = lfirst(field);
-	if (tokens->length > 1)
+	else if (strcmp(name, "radiussecrets") == 0)
 	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("multiple values specified for authentication type"),
-				 errhint("Specify exactly one authentication type per line."),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "multiple values specified for authentication type";
-		return NULL;
-	}
-	token = linitial(tokens);
+		List	   *parsed_secrets;
+		char	   *dupval = pstrdup(val);
 
-	unsupauth = NULL;
-	if (strcmp(token->string, "trust") == 0)
-		parsedline->auth_method = uaTrust;
-	else if (strcmp(token->string, "ident") == 0)
-		parsedline->auth_method = uaIdent;
-	else if (strcmp(token->string, "peer") == 0)
-		parsedline->auth_method = uaPeer;
-	else if (strcmp(token->string, "password") == 0)
-		parsedline->auth_method = uaPassword;
-	else if (strcmp(token->string, "gss") == 0)
-#ifdef ENABLE_GSS
-		parsedline->auth_method = uaGSS;
-#else
-		unsupauth = "gss";
-#endif
-	else if (strcmp(token->string, "sspi") == 0)
-#ifdef ENABLE_SSPI
-		parsedline->auth_method = uaSSPI;
-#else
-		unsupauth = "sspi";
-#endif
-	else if (strcmp(token->string, "reject") == 0)
-		parsedline->auth_method = uaReject;
-	else if (strcmp(token->string, "md5") == 0)
-	{
-		if (Db_user_namespace)
+		REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
+
+		if (!SplitGUCList(dupval, ',', &parsed_secrets))
 		{
+			/* syntax error in list */
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
+					 errmsg("could not parse RADIUS secret list \"%s\"",
+							val),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			*err_msg = "MD5 authentication is not supported when \"db_user_namespace\" is enabled";
-			return NULL;
+			return false;
 		}
-		parsedline->auth_method = uaMD5;
+
+		hbaline->radiussecrets = parsed_secrets;
+		hbaline->radiussecrets_s = pstrdup(val);
 	}
-	else if (strcmp(token->string, "scram-sha-256") == 0)
-		parsedline->auth_method = uaSCRAM;
-	else if (strcmp(token->string, "pam") == 0)
-#ifdef USE_PAM
-		parsedline->auth_method = uaPAM;
-#else
-		unsupauth = "pam";
-#endif
-	else if (strcmp(token->string, "bsd") == 0)
-#ifdef USE_BSD_AUTH
-		parsedline->auth_method = uaBSD;
-#else
-		unsupauth = "bsd";
-#endif
-	else if (strcmp(token->string, "ldap") == 0)
-#ifdef USE_LDAP
-		parsedline->auth_method = uaLDAP;
-#else
-		unsupauth = "ldap";
-#endif
-	else if (strcmp(token->string, "cert") == 0)
-#ifdef USE_SSL
-		parsedline->auth_method = uaCert;
-#else
-		unsupauth = "cert";
-#endif
-	else if (strcmp(token->string, "radius") == 0)
-		parsedline->auth_method = uaRADIUS;
-	else
+	else if (strcmp(name, "radiusidentifiers") == 0)
 	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("invalid authentication method \"%s\"",
-						token->string),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = psprintf("invalid authentication method \"%s\"",
-							token->string);
-		return NULL;
-	}
+		List	   *parsed_identifiers;
+		char	   *dupval = pstrdup(val);
 
-	if (unsupauth)
-	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("invalid authentication method \"%s\": not supported by this build",
-						token->string),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = psprintf("invalid authentication method \"%s\": not supported by this build",
-							token->string);
-		return NULL;
-	}
+		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
 
-	/*
-	 * XXX: When using ident on local connections, change it to peer, for
-	 * backwards compatibility.
-	 */
-	if (parsedline->conntype == ctLocal &&
-		parsedline->auth_method == uaIdent)
-		parsedline->auth_method = uaPeer;
+		if (!SplitGUCList(dupval, ',', &parsed_identifiers))
+		{
+			/* syntax error in list */
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("could not parse RADIUS identifiers list \"%s\"",
+							val),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			return false;
+		}
 
-	/* Invalid authentication combinations */
-	if (parsedline->conntype == ctLocal &&
-		parsedline->auth_method == uaGSS)
-	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("gssapi authentication is not supported on local sockets"),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "gssapi authentication is not supported on local sockets";
-		return NULL;
+		hbaline->radiusidentifiers = parsed_identifiers;
+		hbaline->radiusidentifiers_s = pstrdup(val);
 	}
-
-	if (parsedline->conntype != ctLocal &&
-		parsedline->auth_method == uaPeer)
+	else
 	{
 		ereport(elevel,
 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("peer authentication is only supported on local sockets"),
+				 errmsg("unrecognized authentication option name: \"%s\"",
+						name),
 				 errcontext("line %d of configuration file \"%s\"",
 							line_num, HbaFileName)));
-		*err_msg = "peer authentication is only supported on local sockets";
-		return NULL;
+		*err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+							name);
+		return false;
 	}
+	return true;
+}
 
-	/*
-	 * SSPI authentication can never be enabled on ctLocal connections,
-	 * because it's only supported on Windows, where ctLocal isn't supported.
-	 */
-
-
-	if (parsedline->conntype != ctHostSSL &&
-		parsedline->auth_method == uaCert)
-	{
-		ereport(elevel,
-				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("cert authentication is only supported on hostssl connections"),
-				 errcontext("line %d of configuration file \"%s\"",
-							line_num, HbaFileName)));
-		*err_msg = "cert authentication is only supported on hostssl connections";
-		return NULL;
-	}
+/*
+ *	Scan the pre-parsed hba file, looking for a match to the port's connection
+ *	request.
+ */
+static void
+check_hba(hbaPort *port)
+{
+	Oid			roleid;
+	ListCell   *line;
+	HbaLine    *hba;
 
-	/*
-	 * For GSS and SSPI, set the default value of include_realm to true.
-	 * Having include_realm set to false is dangerous in multi-realm
-	 * situations and is generally considered bad practice.  We keep the
-	 * capability around for backwards compatibility, but we might want to
-	 * remove it at some point in the future.  Users who still need to strip
-	 * the realm off would be better served by using an appropriate regex in a
-	 * pg_ident.conf mapping.
-	 */
-	if (parsedline->auth_method == uaGSS ||
-		parsedline->auth_method == uaSSPI)
-		parsedline->include_realm = true;
+	/* Get the target role's OID.  Note we do not error out for bad role. */
+	roleid = get_role_oid(port->user_name, true);
 
-	/*
-	 * For SSPI, include_realm defaults to the SAM-compatible domain (aka
-	 * NetBIOS name) and user names instead of the Kerberos principal name for
-	 * compatibility.
-	 */
-	if (parsedline->auth_method == uaSSPI)
+	foreach(line, parsed_hba_lines)
 	{
-		parsedline->compat_realm = true;
-		parsedline->upn_username = false;
-	}
+		hba = (HbaLine *) lfirst(line);
 
-	/* Parse remaining arguments */
-	while ((field = lnext(tok_line->fields, field)) != NULL)
-	{
-		tokens = lfirst(field);
-		foreach(tokencell, tokens)
+		/* Check connection type */
+		if (hba->conntype == ctLocal)
 		{
-			char	   *val;
-
-			token = lfirst(tokencell);
+			if (port->raddr.addr.ss_family != AF_UNIX)
+				continue;
+		}
+		else
+		{
+			if (port->raddr.addr.ss_family == AF_UNIX)
+				continue;
 
-			str = pstrdup(token->string);
-			val = strchr(str, '=');
-			if (val == NULL)
+			/* Check SSL state */
+			if (port->ssl_in_use)
 			{
-				/*
-				 * Got something that's not a name=value pair.
-				 */
-				ereport(elevel,
-						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("authentication option not in name=value format: %s", token->string),
-						 errcontext("line %d of configuration file \"%s\"",
-									line_num, HbaFileName)));
-				*err_msg = psprintf("authentication option not in name=value format: %s",
-									token->string);
-				return NULL;
+				/* Connection is SSL, match both "host" and "hostssl" */
+				if (hba->conntype == ctHostNoSSL)
+					continue;
+			}
+			else
+			{
+				/* Connection is not SSL, match both "host" and "hostnossl" */
+				if (hba->conntype == ctHostSSL)
+					continue;
 			}
 
-			*val++ = '\0';		/* str now holds "name", val holds "value" */
-			if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
-				/* parse_hba_auth_opt already logged the error message */
-				return NULL;
-			pfree(str);
-		}
-	}
-
-	/*
-	 * Check if the selected authentication method has any mandatory arguments
-	 * that are not set.
-	 */
-	if (parsedline->auth_method == uaLDAP)
-	{
-#ifndef HAVE_LDAP_INITIALIZE
-		/* Not mandatory for OpenLDAP, because it can use DNS SRV records */
-		MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
+			/* Check GSSAPI state */
+#ifdef ENABLE_GSS
+			if (port->gss && port->gss->enc &&
+				hba->conntype == ctHostNoGSS)
+				continue;
+			else if (!(port->gss && port->gss->enc) &&
+					 hba->conntype == ctHostGSS)
+				continue;
+#else
+			if (hba->conntype == ctHostGSS)
+				continue;
 #endif
 
-		/*
-		 * LDAP can operate in two modes: either with a direct bind, using
-		 * ldapprefix and ldapsuffix, or using a search+bind, using
-		 * ldapbasedn, ldapbinddn, ldapbindpasswd and one of
-		 * ldapsearchattribute or ldapsearchfilter.  Disallow mixing these
-		 * parameters.
-		 */
-		if (parsedline->ldapprefix || parsedline->ldapsuffix)
-		{
-			if (parsedline->ldapbasedn ||
-				parsedline->ldapbinddn ||
-				parsedline->ldapbindpasswd ||
-				parsedline->ldapsearchattribute ||
-				parsedline->ldapsearchfilter)
+			/* Check IP address */
+			switch (hba->ip_cmp_method)
 			{
-				ereport(elevel,
-						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix"),
-						 errcontext("line %d of configuration file \"%s\"",
-									line_num, HbaFileName)));
-				*err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix";
-				return NULL;
+				case ipCmpMask:
+					if (hba->hostname)
+					{
+						if (!check_hostname(port,
+											hba->hostname))
+							continue;
+					}
+					else
+					{
+						if (!check_ip(&port->raddr,
+									  (struct sockaddr *) &hba->addr,
+									  (struct sockaddr *) &hba->mask))
+							continue;
+					}
+					break;
+				case ipCmpAll:
+					break;
+				case ipCmpSameHost:
+				case ipCmpSameNet:
+					if (!check_same_host_or_net(&port->raddr,
+												hba->ip_cmp_method))
+						continue;
+					break;
+				default:
+					/* shouldn't get here, but deem it no-match if so */
+					continue;
 			}
-		}
-		else if (!parsedline->ldapbasedn)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
-			return NULL;
-		}
+		}						/* != ctLocal */
 
-		/*
-		 * When using search+bind, you can either use a simple attribute
-		 * (defaulting to "uid") or a fully custom search filter.  You can't
-		 * do both.
-		 */
-		if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter";
-			return NULL;
-		}
+		/* Check database and role */
+		if (!check_db(port->database_name, port->user_name, roleid,
+					  hba->databases))
+			continue;
+
+		if (!check_role(port->user_name, roleid, hba->roles))
+			continue;
+
+		/* Found a record that matched! */
+		port->hba = hba;
+		return;
 	}
 
-	if (parsedline->auth_method == uaRADIUS)
+	/* If no matching entry was found, then implicitly reject. */
+	hba = palloc0(sizeof(HbaLine));
+	hba->auth_method = uaImplicitReject;
+	port->hba = hba;
+}
+
+/*
+ * Read the config file and create a List of HbaLine records for the contents.
+ *
+ * The configuration is read into a temporary list, and if any parse error
+ * occurs the old list is kept in place and false is returned.  Only if the
+ * whole file parses OK is the list replaced, and the function returns true.
+ *
+ * On a false result, caller will take care of reporting a FATAL error in case
+ * this is the initial startup.  If it happens on reload, we just keep running
+ * with the old data.
+ */
+bool
+load_hba(void)
+{
+	FILE	   *file;
+	List	   *hba_lines = NIL;
+	ListCell   *line;
+	List	   *new_parsed_lines = NIL;
+	bool		ok = true;
+	MemoryContext linecxt;
+	MemoryContext oldcxt;
+	MemoryContext hbacxt;
+
+	file = AllocateFile(HbaFileName, "r");
+	if (file == NULL)
 	{
-		MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
-		MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not open configuration file \"%s\": %m",
+						HbaFileName)));
+		return false;
+	}
 
-		if (list_length(parsedline->radiusservers) < 1)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("list of RADIUS servers cannot be empty"),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = "list of RADIUS servers cannot be empty";
-			return NULL;
-		}
+	linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, LOG);
+	FreeFile(file);
 
-		if (list_length(parsedline->radiussecrets) < 1)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("list of RADIUS secrets cannot be empty"),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = "list of RADIUS secrets cannot be empty";
-			return NULL;
-		}
+	/* Now parse all the lines */
+	Assert(PostmasterContext);
+	hbacxt = AllocSetContextCreate(PostmasterContext,
+								   "hba parser context",
+								   ALLOCSET_SMALL_SIZES);
+	oldcxt = MemoryContextSwitchTo(hbacxt);
+	foreach(line, hba_lines)
+	{
+		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
+		HbaLine    *newline;
 
-		/*
-		 * Verify length of option lists - each can be 0 (except for secrets,
-		 * but that's already checked above), 1 (use the same value
-		 * everywhere) or the same as the number of servers.
-		 */
-		if (!(list_length(parsedline->radiussecrets) == 1 ||
-			  list_length(parsedline->radiussecrets) == list_length(parsedline->radiusservers)))
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
-							list_length(parsedline->radiussecrets),
-							list_length(parsedline->radiusservers)),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = psprintf("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
-								list_length(parsedline->radiussecrets),
-								list_length(parsedline->radiusservers));
-			return NULL;
-		}
-		if (!(list_length(parsedline->radiusports) == 0 ||
-			  list_length(parsedline->radiusports) == 1 ||
-			  list_length(parsedline->radiusports) == list_length(parsedline->radiusservers)))
+		/* don't parse lines that already have errors */
+		if (tok_line->err_msg != NULL)
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
-							list_length(parsedline->radiusports),
-							list_length(parsedline->radiusservers)),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = psprintf("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
-								list_length(parsedline->radiusports),
-								list_length(parsedline->radiusservers));
-			return NULL;
+			ok = false;
+			continue;
 		}
-		if (!(list_length(parsedline->radiusidentifiers) == 0 ||
-			  list_length(parsedline->radiusidentifiers) == 1 ||
-			  list_length(parsedline->radiusidentifiers) == list_length(parsedline->radiusservers)))
+
+		if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
-							list_length(parsedline->radiusidentifiers),
-							list_length(parsedline->radiusservers)),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = psprintf("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
-								list_length(parsedline->radiusidentifiers),
-								list_length(parsedline->radiusservers));
-			return NULL;
+			/* Parse error; remember there's trouble */
+			ok = false;
+
+			/*
+			 * Keep parsing the rest of the file so we can report errors on
+			 * more than the first line.  Error has already been logged, no
+			 * need for more chatter here.
+			 */
+			continue;
 		}
+
+		new_parsed_lines = lappend(new_parsed_lines, newline);
 	}
 
 	/*
-	 * Enforce any parameters implied by other settings.
+	 * A valid HBA file must have at least one entry; else there's no way to
+	 * connect to the postmaster.  But only complain about this if we didn't
+	 * already have parsing errors.
 	 */
-	if (parsedline->auth_method == uaCert)
+	if (ok && new_parsed_lines == NIL)
 	{
-		/*
-		 * For auth method cert, client certificate validation is mandatory, and it implies
-		 * the level of verify-full.
-		 */
-		parsedline->clientcert = clientCertFull;
+		ereport(LOG,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("configuration file \"%s\" contains no entries",
+						HbaFileName)));
+		ok = false;
 	}
 
-	return parsedline;
-}
+	/* Free tokenizer memory */
+	MemoryContextDelete(linecxt);
+	MemoryContextSwitchTo(oldcxt);
+
+	if (!ok)
+	{
+		/* File contained one or more errors, so bail out */
+		MemoryContextDelete(hbacxt);
+		return false;
+	}
+
+	/* Loaded new file successfully, replace the one we use */
+	if (parsed_hba_context != NULL)
+		MemoryContextDelete(parsed_hba_context);
+	parsed_hba_context = hbacxt;
+	parsed_hba_lines = new_parsed_lines;
 
+	return true;
+}
 
 /*
- * Parse one name-value pair as an authentication option into the given
- * HbaLine.  Return true if we successfully parse the option, false if we
- * encounter an error.  In the event of an error, also log a message at
- * ereport level elevel, and store a message string into *err_msg.
+ * Parse one tokenised line from the ident config file and store the result in
+ * an IdentLine structure.
+ *
+ * If parsing fails, log a message and return NULL.
+ *
+ * If ident_user is a regular expression (ie. begins with a slash), it is
+ * compiled and stored in IdentLine structure.
+ *
+ * Note: this function leaks memory when an error occurs.  Caller is expected
+ * to have set a memory context that will be reset if this function returns
+ * NULL.
  */
-static bool
-parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
-				   int elevel, char **err_msg)
+static IdentLine *
+parse_ident_line(TokenizedAuthLine *tok_line)
 {
-	int			line_num = hbaline->linenumber;
+	int			line_num = tok_line->line_num;
+	ListCell   *field;
+	List	   *tokens;
+	HbaToken   *token;
+	IdentLine  *parsedline;
 
-#ifdef USE_LDAP
-	hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
-#endif
+	Assert(tok_line->fields != NIL);
+	field = list_head(tok_line->fields);
 
-	if (strcmp(name, "map") == 0)
-	{
-		if (hbaline->auth_method != uaIdent &&
-			hbaline->auth_method != uaPeer &&
-			hbaline->auth_method != uaGSS &&
-			hbaline->auth_method != uaSSPI &&
-			hbaline->auth_method != uaCert)
-			INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
-		hbaline->usermap = pstrdup(val);
-	}
-	else if (strcmp(name, "clientcert") == 0)
-	{
-		if (hbaline->conntype != ctHostSSL)
-		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("clientcert can only be configured for \"hostssl\" rows"),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = "clientcert can only be configured for \"hostssl\" rows";
-			return false;
-		}
+	parsedline = palloc0(sizeof(IdentLine));
+	parsedline->linenumber = line_num;
 
-		if (strcmp(val, "verify-full") == 0)
+	/* Get the map token (must exist) */
+	tokens = lfirst(field);
+	IDENT_MULTI_VALUE(tokens);
+	token = linitial(tokens);
+	parsedline->usermap = pstrdup(token->string);
+
+	/* Get the ident user token */
+	field = lnext(tok_line->fields, field);
+	IDENT_FIELD_ABSENT(field);
+	tokens = lfirst(field);
+	IDENT_MULTI_VALUE(tokens);
+	token = linitial(tokens);
+	parsedline->ident_user = pstrdup(token->string);
+
+	/* Get the PG rolename token */
+	field = lnext(tok_line->fields, field);
+	IDENT_FIELD_ABSENT(field);
+	tokens = lfirst(field);
+	IDENT_MULTI_VALUE(tokens);
+	token = linitial(tokens);
+	parsedline->pg_role = pstrdup(token->string);
+
+	if (parsedline->ident_user[0] == '/')
+	{
+		/*
+		 * When system username starts with a slash, treat it as a regular
+		 * expression. Pre-compile it.
+		 */
+		int			r;
+		pg_wchar   *wstr;
+		int			wlen;
+
+		wstr = palloc((strlen(parsedline->ident_user + 1) + 1) * sizeof(pg_wchar));
+		wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1,
+									wstr, strlen(parsedline->ident_user + 1));
+
+		r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+		if (r)
 		{
-			hbaline->clientcert = clientCertFull;
+			char		errstr[100];
+
+			pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));
+			ereport(LOG,
+					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+					 errmsg("invalid regular expression \"%s\": %s",
+							parsedline->ident_user + 1, errstr)));
+
+			pfree(wstr);
+			return NULL;
 		}
-		else if (strcmp(val, "verify-ca") == 0)
+		pfree(wstr);
+	}
+
+	return parsedline;
+}
+
+/*
+ *	Process one line from the parsed ident config lines.
+ *
+ *	Compare input parsed ident line to the needed map, pg_role and ident_user.
+ *	*found_p and *error_p are set according to our results.
+ */
+static void
+check_ident_usermap(IdentLine *identLine, const char *usermap_name,
+					const char *pg_role, const char *ident_user,
+					bool case_insensitive, bool *found_p, bool *error_p)
+{
+	*found_p = false;
+	*error_p = false;
+
+	if (strcmp(identLine->usermap, usermap_name) != 0)
+		/* Line does not match the map name we're looking for, so just abort */
+		return;
+
+	/* Match? */
+	if (identLine->ident_user[0] == '/')
+	{
+		/*
+		 * When system username starts with a slash, treat it as a regular
+		 * expression. In this case, we process the system username as a
+		 * regular expression that returns exactly one match. This is replaced
+		 * for \1 in the database username string, if present.
+		 */
+		int			r;
+		regmatch_t	matches[2];
+		pg_wchar   *wstr;
+		int			wlen;
+		char	   *ofs;
+		char	   *regexp_pgrole;
+
+		wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar));
+		wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user));
+
+		r = pg_regexec(&identLine->re, wstr, wlen, 0, NULL, 2, matches, 0);
+		if (r)
 		{
-			if (hbaline->auth_method == uaCert)
+			char		errstr[100];
+
+			if (r != REG_NOMATCH)
 			{
-				ereport(elevel,
-						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("clientcert only accepts \"verify-full\" when using \"cert\" authentication"),
-						 errcontext("line %d of configuration file \"%s\"",
-									line_num, HbaFileName)));
-				*err_msg = "clientcert can only be set to \"verify-full\" when using \"cert\" authentication";
-				return false;
+				/* REG_NOMATCH is not an error, everything else is */
+				pg_regerror(r, &identLine->re, errstr, sizeof(errstr));
+				ereport(LOG,
+						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+						 errmsg("regular expression match for \"%s\" failed: %s",
+								identLine->ident_user + 1, errstr)));
+				*error_p = true;
 			}
 
-			hbaline->clientcert = clientCertCA;
+			pfree(wstr);
+			return;
 		}
-		else
+		pfree(wstr);
+
+		if ((ofs = strstr(identLine->pg_role, "\\1")) != NULL)
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("invalid value for clientcert: \"%s\"", val),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			return false;
+			int			offset;
+
+			/* substitution of the first argument requested */
+			if (matches[1].rm_so < 0)
+			{
+				ereport(LOG,
+						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+						 errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
+								identLine->ident_user + 1, identLine->pg_role)));
+				*error_p = true;
+				return;
+			}
+
+			/*
+			 * length: original length minus length of \1 plus length of match
+			 * plus null terminator
+			 */
+			regexp_pgrole = palloc0(strlen(identLine->pg_role) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
+			offset = ofs - identLine->pg_role;
+			memcpy(regexp_pgrole, identLine->pg_role, offset);
+			memcpy(regexp_pgrole + offset,
+				   ident_user + matches[1].rm_so,
+				   matches[1].rm_eo - matches[1].rm_so);
+			strcat(regexp_pgrole, ofs + 2);
 		}
-	}
-	else if (strcmp(name, "clientname") == 0)
-	{
-		if (hbaline->conntype != ctHostSSL)
+		else
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("clientname can only be configured for \"hostssl\" rows"),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = "clientname can only be configured for \"hostssl\" rows";
-			return false;
+			/* no substitution, so copy the match */
+			regexp_pgrole = pstrdup(identLine->pg_role);
 		}
 
-		if (strcmp(val, "CN") == 0)
-		{
-			hbaline->clientcertname = clientCertCN;
-		}
-		else if (strcmp(val, "DN") == 0)
+		/*
+		 * now check if the username actually matched what the user is trying
+		 * to connect as
+		 */
+		if (case_insensitive)
 		{
-			hbaline->clientcertname = clientCertDN;
+			if (pg_strcasecmp(regexp_pgrole, pg_role) == 0)
+				*found_p = true;
 		}
 		else
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("invalid value for clientname: \"%s\"", val),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			return false;
+			if (strcmp(regexp_pgrole, pg_role) == 0)
+				*found_p = true;
 		}
-	}
-	else if (strcmp(name, "pamservice") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
-		hbaline->pamservice = pstrdup(val);
-	}
-	else if (strcmp(name, "pam_use_hostname") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaPAM, "pam_use_hostname", "pam");
-		if (strcmp(val, "1") == 0)
-			hbaline->pam_use_hostname = true;
-		else
-			hbaline->pam_use_hostname = false;
+		pfree(regexp_pgrole);
 
+		return;
 	}
-	else if (strcmp(name, "ldapurl") == 0)
+	else
 	{
-#ifdef LDAP_API_FEATURE_X_OPENLDAP
-		LDAPURLDesc *urldata;
-		int			rc;
-#endif
-
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapurl", "ldap");
-#ifdef LDAP_API_FEATURE_X_OPENLDAP
-		rc = ldap_url_parse(val, &urldata);
-		if (rc != LDAP_SUCCESS)
+		/* Not regular expression, so make complete match */
+		if (case_insensitive)
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
-			*err_msg = psprintf("could not parse LDAP URL \"%s\": %s",
-								val, ldap_err2string(rc));
-			return false;
+			if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 &&
+				pg_strcasecmp(identLine->ident_user, ident_user) == 0)
+				*found_p = true;
 		}
-
-		if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
-			strcmp(urldata->lud_scheme, "ldaps") != 0)
+		else
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
-			*err_msg = psprintf("unsupported LDAP URL scheme: %s",
-								urldata->lud_scheme);
-			ldap_free_urldesc(urldata);
-			return false;
+			if (strcmp(identLine->pg_role, pg_role) == 0 &&
+				strcmp(identLine->ident_user, ident_user) == 0)
+				*found_p = true;
 		}
+	}
+}
 
-		if (urldata->lud_scheme)
-			hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
-		if (urldata->lud_host)
-			hbaline->ldapserver = pstrdup(urldata->lud_host);
-		hbaline->ldapport = urldata->lud_port;
-		if (urldata->lud_dn)
-			hbaline->ldapbasedn = pstrdup(urldata->lud_dn);
 
-		if (urldata->lud_attrs)
-			hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]);	/* only use first one */
-		hbaline->ldapscope = urldata->lud_scope;
-		if (urldata->lud_filter)
-			hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter);
-		ldap_free_urldesc(urldata);
-#else							/* not OpenLDAP */
-		ereport(elevel,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("LDAP URLs not supported on this platform")));
-		*err_msg = "LDAP URLs not supported on this platform";
-#endif							/* not OpenLDAP */
-	}
-	else if (strcmp(name, "ldaptls") == 0)
+/*
+ *	Scan the (pre-parsed) ident usermap file line by line, looking for a match
+ *
+ *	See if the user with ident username "auth_user" is allowed to act
+ *	as Postgres user "pg_role" according to usermap "usermap_name".
+ *
+ *	Special case: Usermap NULL, equivalent to what was previously called
+ *	"sameuser" or "samerole", means don't look in the usermap file.
+ *	That's an implied map wherein "pg_role" must be identical to
+ *	"auth_user" in order to be authorized.
+ *
+ *	Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
+ */
+int
+check_usermap(const char *usermap_name,
+			  const char *pg_role,
+			  const char *auth_user,
+			  bool case_insensitive)
+{
+	bool		found_entry = false,
+				error = false;
+
+	if (usermap_name == NULL || usermap_name[0] == '\0')
 	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
-		if (strcmp(val, "1") == 0)
-			hbaline->ldaptls = true;
+		if (case_insensitive)
+		{
+			if (pg_strcasecmp(pg_role, auth_user) == 0)
+				return STATUS_OK;
+		}
 		else
-			hbaline->ldaptls = false;
-	}
-	else if (strcmp(name, "ldapscheme") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
-		if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0)
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("invalid ldapscheme value: \"%s\"", val),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-		hbaline->ldapscheme = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapserver") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
-		hbaline->ldapserver = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapport") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
-		hbaline->ldapport = atoi(val);
-		if (hbaline->ldapport == 0)
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("invalid LDAP port number: \"%s\"", val),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = psprintf("invalid LDAP port number: \"%s\"", val);
-			return false;
+			if (strcmp(pg_role, auth_user) == 0)
+				return STATUS_OK;
 		}
+		ereport(LOG,
+				(errmsg("provided user name (%s) and authenticated user name (%s) do not match",
+						pg_role, auth_user)));
+		return STATUS_ERROR;
 	}
-	else if (strcmp(name, "ldapbinddn") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
-		hbaline->ldapbinddn = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapbindpasswd") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
-		hbaline->ldapbindpasswd = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapsearchattribute") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
-		hbaline->ldapsearchattribute = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapsearchfilter") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap");
-		hbaline->ldapsearchfilter = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapbasedn") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
-		hbaline->ldapbasedn = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapprefix") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
-		hbaline->ldapprefix = pstrdup(val);
-	}
-	else if (strcmp(name, "ldapsuffix") == 0)
-	{
-		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
-		hbaline->ldapsuffix = pstrdup(val);
-	}
-	else if (strcmp(name, "krb_realm") == 0)
-	{
-		if (hbaline->auth_method != uaGSS &&
-			hbaline->auth_method != uaSSPI)
-			INVALID_AUTH_OPTION("krb_realm", gettext_noop("gssapi and sspi"));
-		hbaline->krb_realm = pstrdup(val);
-	}
-	else if (strcmp(name, "include_realm") == 0)
+	else
 	{
-		if (hbaline->auth_method != uaGSS &&
-			hbaline->auth_method != uaSSPI)
-			INVALID_AUTH_OPTION("include_realm", gettext_noop("gssapi and sspi"));
-		if (strcmp(val, "1") == 0)
-			hbaline->include_realm = true;
-		else
-			hbaline->include_realm = false;
+		ListCell   *line_cell;
+
+		foreach(line_cell, parsed_ident_lines)
+		{
+			check_ident_usermap(lfirst(line_cell), usermap_name,
+								pg_role, auth_user, case_insensitive,
+								&found_entry, &error);
+			if (found_entry || error)
+				break;
+		}
 	}
-	else if (strcmp(name, "compat_realm") == 0)
+	if (!found_entry && !error)
 	{
-		if (hbaline->auth_method != uaSSPI)
-			INVALID_AUTH_OPTION("compat_realm", gettext_noop("sspi"));
-		if (strcmp(val, "1") == 0)
-			hbaline->compat_realm = true;
-		else
-			hbaline->compat_realm = false;
+		ereport(LOG,
+				(errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
+						usermap_name, pg_role, auth_user)));
 	}
-	else if (strcmp(name, "upn_username") == 0)
+	return found_entry ? STATUS_OK : STATUS_ERROR;
+}
+
+
+/*
+ * Read the ident config file and create a List of IdentLine records for
+ * the contents.
+ *
+ * This works the same as load_hba(), but for the user config file.
+ */
+bool
+load_ident(void)
+{
+	FILE	   *file;
+	List	   *ident_lines = NIL;
+	ListCell   *line_cell,
+			   *parsed_line_cell;
+	List	   *new_parsed_lines = NIL;
+	bool		ok = true;
+	MemoryContext linecxt;
+	MemoryContext oldcxt;
+	MemoryContext ident_context;
+	IdentLine  *newline;
+
+	file = AllocateFile(IdentFileName, "r");
+	if (file == NULL)
 	{
-		if (hbaline->auth_method != uaSSPI)
-			INVALID_AUTH_OPTION("upn_username", gettext_noop("sspi"));
-		if (strcmp(val, "1") == 0)
-			hbaline->upn_username = true;
-		else
-			hbaline->upn_username = false;
+		/* not fatal ... we just won't do any special ident maps */
+		ereport(LOG,
+				(errcode_for_file_access(),
+				 errmsg("could not open usermap file \"%s\": %m",
+						IdentFileName)));
+		return false;
 	}
-	else if (strcmp(name, "radiusservers") == 0)
-	{
-		struct addrinfo *gai_result;
-		struct addrinfo hints;
-		int			ret;
-		List	   *parsed_servers;
-		ListCell   *l;
-		char	   *dupval = pstrdup(val);
 
-		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius");
+	linecxt = tokenize_auth_file(IdentFileName, file, &ident_lines, LOG);
+	FreeFile(file);
 
-		if (!SplitGUCList(dupval, ',', &parsed_servers))
+	/* Now parse all the lines */
+	Assert(PostmasterContext);
+	ident_context = AllocSetContextCreate(PostmasterContext,
+										  "ident parser context",
+										  ALLOCSET_SMALL_SIZES);
+	oldcxt = MemoryContextSwitchTo(ident_context);
+	foreach(line_cell, ident_lines)
+	{
+		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line_cell);
+
+		/* don't parse lines that already have errors */
+		if (tok_line->err_msg != NULL)
 		{
-			/* syntax error in list */
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("could not parse RADIUS server list \"%s\"",
-							val),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			return false;
+			ok = false;
+			continue;
 		}
 
-		/* For each entry in the list, translate it */
-		foreach(l, parsed_servers)
+		if ((newline = parse_ident_line(tok_line)) == NULL)
 		{
-			MemSet(&hints, 0, sizeof(hints));
-			hints.ai_socktype = SOCK_DGRAM;
-			hints.ai_family = AF_UNSPEC;
-
-			ret = pg_getaddrinfo_all((char *) lfirst(l), NULL, &hints, &gai_result);
-			if (ret || !gai_result)
-			{
-				ereport(elevel,
-						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
-								(char *) lfirst(l), gai_strerror(ret)),
-						 errcontext("line %d of configuration file \"%s\"",
-									line_num, HbaFileName)));
-				if (gai_result)
-					pg_freeaddrinfo_all(hints.ai_family, gai_result);
+			/* Parse error; remember there's trouble */
+			ok = false;
 
-				list_free(parsed_servers);
-				return false;
-			}
-			pg_freeaddrinfo_all(hints.ai_family, gai_result);
+			/*
+			 * Keep parsing the rest of the file so we can report errors on
+			 * more than the first line.  Error has already been logged, no
+			 * need for more chatter here.
+			 */
+			continue;
 		}
 
-		/* All entries are OK, so store them */
-		hbaline->radiusservers = parsed_servers;
-		hbaline->radiusservers_s = pstrdup(val);
+		new_parsed_lines = lappend(new_parsed_lines, newline);
 	}
-	else if (strcmp(name, "radiusports") == 0)
-	{
-		List	   *parsed_ports;
-		ListCell   *l;
-		char	   *dupval = pstrdup(val);
 
-		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
+	/* Free tokenizer memory */
+	MemoryContextDelete(linecxt);
+	MemoryContextSwitchTo(oldcxt);
 
-		if (!SplitGUCList(dupval, ',', &parsed_ports))
+	if (!ok)
+	{
+		/*
+		 * File contained one or more errors, so bail out, first being careful
+		 * to clean up whatever we allocated.  Most stuff will go away via
+		 * MemoryContextDelete, but we have to clean up regexes explicitly.
+		 */
+		foreach(parsed_line_cell, new_parsed_lines)
 		{
-			ereport(elevel,
-					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("could not parse RADIUS port list \"%s\"",
-							val),
-					 errcontext("line %d of configuration file \"%s\"",
-								line_num, HbaFileName)));
-			*err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
-			return false;
+			newline = (IdentLine *) lfirst(parsed_line_cell);
+			if (newline->ident_user[0] == '/')
+				pg_regfree(&newline->re);
 		}
+		MemoryContextDelete(ident_context);
+		return false;
+	}
 
-		foreach(l, parsed_ports)
+	/* Loaded new file successfully, replace the one we use */
+	if (parsed_ident_lines != NIL)
+	{
+		foreach(parsed_line_cell, parsed_ident_lines)
 		{
-			if (atoi(lfirst(l)) == 0)
+			newline = (IdentLine *) lfirst(parsed_line_cell);
+			if (newline->ident_user[0] == '/')
+				pg_regfree(&newline->re);
+		}
+	}
+	if (parsed_ident_context != NULL)
+		MemoryContextDelete(parsed_ident_context);
+
+	parsed_ident_context = ident_context;
+	parsed_ident_lines = new_parsed_lines;
+
+	return true;
+}
+
+
+
+/*
+ *	Determine what authentication method should be used when accessing database
+ *	"database" from frontend "raddr", user "user".  Return the method and
+ *	an optional argument (stored in fields of *port), and STATUS_OK.
+ *
+ *	If the file does not contain any entry matching the request, we return
+ *	method = uaImplicitReject.
+ */
+void
+hba_getauthmethod(hbaPort *port)
+{
+	check_hba(port);
+}
+
+
+/*
+ * Return the name of the auth method in use ("gss", "md5", "trust", etc.).
+ *
+ * The return value is statically allocated (see the UserAuthName array) and
+ * should not be freed.
+ */
+const char *
+hba_authname(UserAuth auth_method)
+{
+	/*
+	 * Make sure UserAuthName[] tracks additions to the UserAuth enum
+	 */
+	StaticAssertStmt(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
+					 "UserAuthName[] must match the UserAuth enum");
+
+	return UserAuthName[auth_method];
+}
+
+/*
+ * Parse one tokenised line from the hba config file and store the result in a
+ * HbaLine structure.
+ *
+ * If parsing fails, log a message at ereport level elevel, store an error
+ * string in tok_line->err_msg, and return NULL.  (Some non-error conditions
+ * can also result in such messages.)
+ *
+ * Note: this function leaks memory when an error occurs.  Caller is expected
+ * to have set a memory context that will be reset if this function returns
+ * NULL.
+ */
+HbaLine *
+parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
+{
+	int			line_num = tok_line->line_num;
+	char	  **err_msg = &tok_line->err_msg;
+	char	   *str;
+	struct addrinfo *gai_result;
+	struct addrinfo hints;
+	int			ret;
+	char	   *cidr_slash;
+	char	   *unsupauth;
+	ListCell   *field;
+	List	   *tokens;
+	ListCell   *tokencell;
+	HbaToken   *token;
+	HbaLine    *parsedline;
+
+	parsedline = palloc0(sizeof(HbaLine));
+	parsedline->linenumber = line_num;
+	parsedline->rawline = pstrdup(tok_line->raw_line);
+
+	/* Check the record type. */
+	Assert(tok_line->fields != NIL);
+	field = list_head(tok_line->fields);
+	tokens = lfirst(field);
+	if (tokens->length > 1)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("multiple values specified for connection type"),
+				 errhint("Specify exactly one connection type per line."),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "multiple values specified for connection type";
+		return NULL;
+	}
+	token = linitial(tokens);
+	if (strcmp(token->string, "local") == 0)
+	{
+#ifdef HAVE_UNIX_SOCKETS
+		parsedline->conntype = ctLocal;
+#else
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("local connections are not supported by this build"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "local connections are not supported by this build";
+		return NULL;
+#endif
+	}
+	else if (strcmp(token->string, "host") == 0 ||
+			 strcmp(token->string, "hostssl") == 0 ||
+			 strcmp(token->string, "hostnossl") == 0 ||
+			 strcmp(token->string, "hostgssenc") == 0 ||
+			 strcmp(token->string, "hostnogssenc") == 0)
+	{
+
+		if (token->string[4] == 's')	/* "hostssl" */
+		{
+			parsedline->conntype = ctHostSSL;
+			/* Log a warning if SSL support is not active */
+#ifdef USE_SSL
+			if (!EnableSSL)
 			{
 				ereport(elevel,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
-						 errmsg("invalid RADIUS port number: \"%s\"", val),
+						 errmsg("hostssl record cannot match because SSL is disabled"),
+						 errhint("Set ssl = on in postgresql.conf."),
 						 errcontext("line %d of configuration file \"%s\"",
 									line_num, HbaFileName)));
-
-				return false;
+				*err_msg = "hostssl record cannot match because SSL is disabled";
 			}
-		}
-		hbaline->radiusports = parsed_ports;
-		hbaline->radiusports_s = pstrdup(val);
-	}
-	else if (strcmp(name, "radiussecrets") == 0)
-	{
-		List	   *parsed_secrets;
-		char	   *dupval = pstrdup(val);
-
-		REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
-
-		if (!SplitGUCList(dupval, ',', &parsed_secrets))
-		{
-			/* syntax error in list */
+#else
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("could not parse RADIUS secret list \"%s\"",
-							val),
+					 errmsg("hostssl record cannot match because SSL is not supported by this build"),
+					 errhint("Compile with --with-ssl to use SSL connections."),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			return false;
+			*err_msg = "hostssl record cannot match because SSL is not supported by this build";
+#endif
 		}
-
-		hbaline->radiussecrets = parsed_secrets;
-		hbaline->radiussecrets_s = pstrdup(val);
-	}
-	else if (strcmp(name, "radiusidentifiers") == 0)
-	{
-		List	   *parsed_identifiers;
-		char	   *dupval = pstrdup(val);
-
-		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
-
-		if (!SplitGUCList(dupval, ',', &parsed_identifiers))
+		else if (token->string[4] == 'g')	/* "hostgssenc" */
 		{
-			/* syntax error in list */
+			parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
 			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
-					 errmsg("could not parse RADIUS identifiers list \"%s\"",
-							val),
+					 errmsg("hostgssenc record cannot match because GSSAPI is not supported by this build"),
+					 errhint("Compile with --with-gssapi to use GSSAPI connections."),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
-			return false;
+			*err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
+#endif
 		}
-
-		hbaline->radiusidentifiers = parsed_identifiers;
-		hbaline->radiusidentifiers_s = pstrdup(val);
-	}
+		else if (token->string[4] == 'n' && token->string[6] == 's')
+			parsedline->conntype = ctHostNoSSL;
+		else if (token->string[4] == 'n' && token->string[6] == 'g')
+			parsedline->conntype = ctHostNoGSS;
+		else
+		{
+			/* "host" */
+			parsedline->conntype = ctHost;
+		}
+	}							/* record type */
 	else
 	{
 		ereport(elevel,
 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("unrecognized authentication option name: \"%s\"",
-						name),
+				 errmsg("invalid connection type \"%s\"",
+						token->string),
 				 errcontext("line %d of configuration file \"%s\"",
 							line_num, HbaFileName)));
-		*err_msg = psprintf("unrecognized authentication option name: \"%s\"",
-							name);
-		return false;
+		*err_msg = psprintf("invalid connection type \"%s\"", token->string);
+		return NULL;
 	}
-	return true;
-}
-
-/*
- *	Scan the pre-parsed hba file, looking for a match to the port's connection
- *	request.
- */
-static void
-check_hba(hbaPort *port)
-{
-	Oid			roleid;
-	ListCell   *line;
-	HbaLine    *hba;
-
-	/* Get the target role's OID.  Note we do not error out for bad role. */
-	roleid = get_role_oid(port->user_name, true);
 
-	foreach(line, parsed_hba_lines)
+	/* Get the databases. */
+	field = lnext(tok_line->fields, field);
+	if (!field)
 	{
-		hba = (HbaLine *) lfirst(line);
-
-		/* Check connection type */
-		if (hba->conntype == ctLocal)
-		{
-			if (port->raddr.addr.ss_family != AF_UNIX)
-				continue;
-		}
-		else
-		{
-			if (port->raddr.addr.ss_family == AF_UNIX)
-				continue;
-
-			/* Check SSL state */
-			if (port->ssl_in_use)
-			{
-				/* Connection is SSL, match both "host" and "hostssl" */
-				if (hba->conntype == ctHostNoSSL)
-					continue;
-			}
-			else
-			{
-				/* Connection is not SSL, match both "host" and "hostnossl" */
-				if (hba->conntype == ctHostSSL)
-					continue;
-			}
-
-			/* Check GSSAPI state */
-#ifdef ENABLE_GSS
-			if (port->gss && port->gss->enc &&
-				hba->conntype == ctHostNoGSS)
-				continue;
-			else if (!(port->gss && port->gss->enc) &&
-					 hba->conntype == ctHostGSS)
-				continue;
-#else
-			if (hba->conntype == ctHostGSS)
-				continue;
-#endif
-
-			/* Check IP address */
-			switch (hba->ip_cmp_method)
-			{
-				case ipCmpMask:
-					if (hba->hostname)
-					{
-						if (!check_hostname(port,
-											hba->hostname))
-							continue;
-					}
-					else
-					{
-						if (!check_ip(&port->raddr,
-									  (struct sockaddr *) &hba->addr,
-									  (struct sockaddr *) &hba->mask))
-							continue;
-					}
-					break;
-				case ipCmpAll:
-					break;
-				case ipCmpSameHost:
-				case ipCmpSameNet:
-					if (!check_same_host_or_net(&port->raddr,
-												hba->ip_cmp_method))
-						continue;
-					break;
-				default:
-					/* shouldn't get here, but deem it no-match if so */
-					continue;
-			}
-		}						/* != ctLocal */
-
-		/* Check database and role */
-		if (!check_db(port->database_name, port->user_name, roleid,
-					  hba->databases))
-			continue;
-
-		if (!check_role(port->user_name, roleid, hba->roles))
-			continue;
-
-		/* Found a record that matched! */
-		port->hba = hba;
-		return;
-	}
-
-	/* If no matching entry was found, then implicitly reject. */
-	hba = palloc0(sizeof(HbaLine));
-	hba->auth_method = uaImplicitReject;
-	port->hba = hba;
-}
-
-/*
- * Read the config file and create a List of HbaLine records for the contents.
- *
- * The configuration is read into a temporary list, and if any parse error
- * occurs the old list is kept in place and false is returned.  Only if the
- * whole file parses OK is the list replaced, and the function returns true.
- *
- * On a false result, caller will take care of reporting a FATAL error in case
- * this is the initial startup.  If it happens on reload, we just keep running
- * with the old data.
- */
-bool
-load_hba(void)
-{
-	FILE	   *file;
-	List	   *hba_lines = NIL;
-	ListCell   *line;
-	List	   *new_parsed_lines = NIL;
-	bool		ok = true;
-	MemoryContext linecxt;
-	MemoryContext oldcxt;
-	MemoryContext hbacxt;
-
-	file = AllocateFile(HbaFileName, "r");
-	if (file == NULL)
-	{
-		ereport(LOG,
-				(errcode_for_file_access(),
-				 errmsg("could not open configuration file \"%s\": %m",
-						HbaFileName)));
-		return false;
-	}
-
-	linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
-	FreeFile(file);
-
-	/* Now parse all the lines */
-	Assert(PostmasterContext);
-	hbacxt = AllocSetContextCreate(PostmasterContext,
-								   "hba parser context",
-								   ALLOCSET_SMALL_SIZES);
-	oldcxt = MemoryContextSwitchTo(hbacxt);
-	foreach(line, hba_lines)
-	{
-		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
-		HbaLine    *newline;
-
-		/* don't parse lines that already have errors */
-		if (tok_line->err_msg != NULL)
-		{
-			ok = false;
-			continue;
-		}
-
-		if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
-		{
-			/* Parse error; remember there's trouble */
-			ok = false;
-
-			/*
-			 * Keep parsing the rest of the file so we can report errors on
-			 * more than the first line.  Error has already been logged, no
-			 * need for more chatter here.
-			 */
-			continue;
-		}
-
-		new_parsed_lines = lappend(new_parsed_lines, newline);
-	}
-
-	/*
-	 * A valid HBA file must have at least one entry; else there's no way to
-	 * connect to the postmaster.  But only complain about this if we didn't
-	 * already have parsing errors.
-	 */
-	if (ok && new_parsed_lines == NIL)
-	{
-		ereport(LOG,
+		ereport(elevel,
 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
-				 errmsg("configuration file \"%s\" contains no entries",
-						HbaFileName)));
-		ok = false;
+				 errmsg("end-of-line before database specification"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "end-of-line before database specification";
+		return NULL;
 	}
-
-	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
-	MemoryContextSwitchTo(oldcxt);
-
-	if (!ok)
+	parsedline->databases = NIL;
+	tokens = lfirst(field);
+	foreach(tokencell, tokens)
 	{
-		/* File contained one or more errors, so bail out */
-		MemoryContextDelete(hbacxt);
-		return false;
+		parsedline->databases = lappend(parsedline->databases,
+										copy_hba_token(lfirst(tokencell)));
 	}
 
-	/* Loaded new file successfully, replace the one we use */
-	if (parsed_hba_context != NULL)
-		MemoryContextDelete(parsed_hba_context);
-	parsed_hba_context = hbacxt;
-	parsed_hba_lines = new_parsed_lines;
-
-	return true;
-}
-
-/*
- * This macro specifies the maximum number of authentication options
- * that are possible with any given authentication method that is supported.
- * Currently LDAP supports 11, and there are 3 that are not dependent on
- * the auth method here.  It may not actually be possible to set all of them
- * at the same time, but we'll set the macro value high enough to be
- * conservative and avoid warnings from static analysis tools.
- */
-#define MAX_HBA_OPTIONS 14
-
-/*
- * Create a text array listing the options specified in the HBA line.
- * Return NULL if no options are specified.
- */
-static ArrayType *
-gethba_options(HbaLine *hba)
-{
-	int			noptions;
-	Datum		options[MAX_HBA_OPTIONS];
-
-	noptions = 0;
-
-	if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+	/* Get the roles. */
+	field = lnext(tok_line->fields, field);
+	if (!field)
 	{
-		if (hba->include_realm)
-			options[noptions++] =
-				CStringGetTextDatum("include_realm=true");
-
-		if (hba->krb_realm)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm));
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("end-of-line before role specification"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "end-of-line before role specification";
+		return NULL;
 	}
-
-	if (hba->usermap)
-		options[noptions++] =
-			CStringGetTextDatum(psprintf("map=%s", hba->usermap));
-
-	if (hba->clientcert != clientCertOff)
-		options[noptions++] =
-			CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
-
-	if (hba->pamservice)
-		options[noptions++] =
-			CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice));
-
-	if (hba->auth_method == uaLDAP)
+	parsedline->roles = NIL;
+	tokens = lfirst(field);
+	foreach(tokencell, tokens)
 	{
-		if (hba->ldapserver)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver));
-
-		if (hba->ldapport)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport));
-
-		if (hba->ldaptls)
-			options[noptions++] =
-				CStringGetTextDatum("ldaptls=true");
-
-		if (hba->ldapprefix)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix));
-
-		if (hba->ldapsuffix)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix));
-
-		if (hba->ldapbasedn)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn));
-
-		if (hba->ldapbinddn)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn));
-
-		if (hba->ldapbindpasswd)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapbindpasswd=%s",
-											 hba->ldapbindpasswd));
-
-		if (hba->ldapsearchattribute)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
-											 hba->ldapsearchattribute));
-
-		if (hba->ldapsearchfilter)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
-											 hba->ldapsearchfilter));
-
-		if (hba->ldapscope)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
+		parsedline->roles = lappend(parsedline->roles,
+									copy_hba_token(lfirst(tokencell)));
 	}
 
-	if (hba->auth_method == uaRADIUS)
+	if (parsedline->conntype != ctLocal)
 	{
-		if (hba->radiusservers_s)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
-
-		if (hba->radiussecrets_s)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
-
-		if (hba->radiusidentifiers_s)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
+		/* Read the IP address field. (with or without CIDR netmask) */
+		field = lnext(tok_line->fields, field);
+		if (!field)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("end-of-line before IP address specification"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "end-of-line before IP address specification";
+			return NULL;
+		}
+		tokens = lfirst(field);
+		if (tokens->length > 1)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("multiple values specified for host address"),
+					 errhint("Specify one address range per line."),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "multiple values specified for host address";
+			return NULL;
+		}
+		token = linitial(tokens);
 
-		if (hba->radiusports_s)
-			options[noptions++] =
-				CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s));
-	}
+		if (token_is_keyword(token, "all"))
+		{
+			parsedline->ip_cmp_method = ipCmpAll;
+		}
+		else if (token_is_keyword(token, "samehost"))
+		{
+			/* Any IP on this host is allowed to connect */
+			parsedline->ip_cmp_method = ipCmpSameHost;
+		}
+		else if (token_is_keyword(token, "samenet"))
+		{
+			/* Any IP on the host's subnets is allowed to connect */
+			parsedline->ip_cmp_method = ipCmpSameNet;
+		}
+		else
+		{
+			/* IP and netmask are specified */
+			parsedline->ip_cmp_method = ipCmpMask;
 
-	/* If you add more options, consider increasing MAX_HBA_OPTIONS. */
-	Assert(noptions <= MAX_HBA_OPTIONS);
+			/* need a modifiable copy of token */
+			str = pstrdup(token->string);
 
-	if (noptions > 0)
-		return construct_array(options, noptions, TEXTOID, -1, false, TYPALIGN_INT);
-	else
-		return NULL;
-}
+			/* Check if it has a CIDR suffix and if so isolate it */
+			cidr_slash = strchr(str, '/');
+			if (cidr_slash)
+				*cidr_slash = '\0';
 
-/* Number of columns in pg_hba_file_rules view */
-#define NUM_PG_HBA_FILE_RULES_ATTS	 9
+			/* Get the IP address either way */
+			hints.ai_flags = AI_NUMERICHOST;
+			hints.ai_family = AF_UNSPEC;
+			hints.ai_socktype = 0;
+			hints.ai_protocol = 0;
+			hints.ai_addrlen = 0;
+			hints.ai_canonname = NULL;
+			hints.ai_addr = NULL;
+			hints.ai_next = NULL;
 
-/*
- * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore
- *
- * tuple_store: where to store data
- * tupdesc: tuple descriptor for the view
- * lineno: pg_hba.conf line number (must always be valid)
- * hba: parsed line data (can be NULL, in which case err_msg should be set)
- * err_msg: error message (NULL if none)
- *
- * Note: leaks memory, but we don't care since this is run in a short-lived
- * memory context.
- */
-static void
-fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
-			  int lineno, HbaLine *hba, const char *err_msg)
-{
-	Datum		values[NUM_PG_HBA_FILE_RULES_ATTS];
-	bool		nulls[NUM_PG_HBA_FILE_RULES_ATTS];
-	char		buffer[NI_MAXHOST];
-	HeapTuple	tuple;
-	int			index;
-	ListCell   *lc;
-	const char *typestr;
-	const char *addrstr;
-	const char *maskstr;
-	ArrayType  *options;
-
-	Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
-
-	memset(values, 0, sizeof(values));
-	memset(nulls, 0, sizeof(nulls));
-	index = 0;
-
-	/* line_number */
-	values[index++] = Int32GetDatum(lineno);
-
-	if (hba != NULL)
-	{
-		/* type */
-		/* Avoid a default: case so compiler will warn about missing cases */
-		typestr = NULL;
-		switch (hba->conntype)
-		{
-			case ctLocal:
-				typestr = "local";
-				break;
-			case ctHost:
-				typestr = "host";
-				break;
-			case ctHostSSL:
-				typestr = "hostssl";
-				break;
-			case ctHostNoSSL:
-				typestr = "hostnossl";
-				break;
-			case ctHostGSS:
-				typestr = "hostgssenc";
-				break;
-			case ctHostNoGSS:
-				typestr = "hostnogssenc";
-				break;
-		}
-		if (typestr)
-			values[index++] = CStringGetTextDatum(typestr);
-		else
-			nulls[index++] = true;
+			ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result);
+			if (ret == 0 && gai_result)
+			{
+				memcpy(&parsedline->addr, gai_result->ai_addr,
+					   gai_result->ai_addrlen);
+				parsedline->addrlen = gai_result->ai_addrlen;
+			}
+			else if (ret == EAI_NONAME)
+				parsedline->hostname = str;
+			else
+			{
+				ereport(elevel,
+						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+						 errmsg("invalid IP address \"%s\": %s",
+								str, gai_strerror(ret)),
+						 errcontext("line %d of configuration file \"%s\"",
+									line_num, HbaFileName)));
+				*err_msg = psprintf("invalid IP address \"%s\": %s",
+									str, gai_strerror(ret));
+				if (gai_result)
+					pg_freeaddrinfo_all(hints.ai_family, gai_result);
+				return NULL;
+			}
 
-		/* database */
-		if (hba->databases)
-		{
-			/*
-			 * Flatten HbaToken list to string list.  It might seem that we
-			 * should re-quote any quoted tokens, but that has been rejected
-			 * on the grounds that it makes it harder to compare the array
-			 * elements to other system catalogs.  That makes entries like
-			 * "all" or "samerole" formally ambiguous ... but users who name
-			 * databases/roles that way are inflicting their own pain.
-			 */
-			List	   *names = NIL;
+			pg_freeaddrinfo_all(hints.ai_family, gai_result);
 
-			foreach(lc, hba->databases)
+			/* Get the netmask */
+			if (cidr_slash)
 			{
-				HbaToken   *tok = lfirst(lc);
+				if (parsedline->hostname)
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
+									token->string),
+							 errcontext("line %d of configuration file \"%s\"",
+										line_num, HbaFileName)));
+					*err_msg = psprintf("specifying both host name and CIDR mask is invalid: \"%s\"",
+										token->string);
+					return NULL;
+				}
 
-				names = lappend(names, tok->string);
+				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
+										  parsedline->addr.ss_family) < 0)
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("invalid CIDR mask in address \"%s\"",
+									token->string),
+							 errcontext("line %d of configuration file \"%s\"",
+										line_num, HbaFileName)));
+					*err_msg = psprintf("invalid CIDR mask in address \"%s\"",
+										token->string);
+					return NULL;
+				}
+				parsedline->masklen = parsedline->addrlen;
+				pfree(str);
 			}
-			values[index++] = PointerGetDatum(strlist_to_textarray(names));
-		}
-		else
-			nulls[index++] = true;
+			else if (!parsedline->hostname)
+			{
+				/* Read the mask field. */
+				pfree(str);
+				field = lnext(tok_line->fields, field);
+				if (!field)
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("end-of-line before netmask specification"),
+							 errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
+							 errcontext("line %d of configuration file \"%s\"",
+										line_num, HbaFileName)));
+					*err_msg = "end-of-line before netmask specification";
+					return NULL;
+				}
+				tokens = lfirst(field);
+				if (tokens->length > 1)
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("multiple values specified for netmask"),
+							 errcontext("line %d of configuration file \"%s\"",
+										line_num, HbaFileName)));
+					*err_msg = "multiple values specified for netmask";
+					return NULL;
+				}
+				token = linitial(tokens);
 
-		/* user */
-		if (hba->roles)
-		{
-			/* Flatten HbaToken list to string list; see comment above */
-			List	   *roles = NIL;
+				ret = pg_getaddrinfo_all(token->string, NULL,
+										 &hints, &gai_result);
+				if (ret || !gai_result)
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("invalid IP mask \"%s\": %s",
+									token->string, gai_strerror(ret)),
+							 errcontext("line %d of configuration file \"%s\"",
+										line_num, HbaFileName)));
+					*err_msg = psprintf("invalid IP mask \"%s\": %s",
+										token->string, gai_strerror(ret));
+					if (gai_result)
+						pg_freeaddrinfo_all(hints.ai_family, gai_result);
+					return NULL;
+				}
 
-			foreach(lc, hba->roles)
-			{
-				HbaToken   *tok = lfirst(lc);
+				memcpy(&parsedline->mask, gai_result->ai_addr,
+					   gai_result->ai_addrlen);
+				parsedline->masklen = gai_result->ai_addrlen;
+				pg_freeaddrinfo_all(hints.ai_family, gai_result);
 
-				roles = lappend(roles, tok->string);
+				if (parsedline->addr.ss_family != parsedline->mask.ss_family)
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_CONFIG_FILE_ERROR),
+							 errmsg("IP address and mask do not match"),
+							 errcontext("line %d of configuration file \"%s\"",
+										line_num, HbaFileName)));
+					*err_msg = "IP address and mask do not match";
+					return NULL;
+				}
 			}
-			values[index++] = PointerGetDatum(strlist_to_textarray(roles));
 		}
-		else
-			nulls[index++] = true;
+	}							/* != ctLocal */
 
-		/* address and netmask */
-		/* Avoid a default: case so compiler will warn about missing cases */
-		addrstr = maskstr = NULL;
-		switch (hba->ip_cmp_method)
+	/* Get the authentication method */
+	field = lnext(tok_line->fields, field);
+	if (!field)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("end-of-line before authentication method"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "end-of-line before authentication method";
+		return NULL;
+	}
+	tokens = lfirst(field);
+	if (tokens->length > 1)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("multiple values specified for authentication type"),
+				 errhint("Specify exactly one authentication type per line."),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "multiple values specified for authentication type";
+		return NULL;
+	}
+	token = linitial(tokens);
+
+	unsupauth = NULL;
+	if (strcmp(token->string, "trust") == 0)
+		parsedline->auth_method = uaTrust;
+	else if (strcmp(token->string, "ident") == 0)
+		parsedline->auth_method = uaIdent;
+	else if (strcmp(token->string, "peer") == 0)
+		parsedline->auth_method = uaPeer;
+	else if (strcmp(token->string, "password") == 0)
+		parsedline->auth_method = uaPassword;
+	else if (strcmp(token->string, "gss") == 0)
+#ifdef ENABLE_GSS
+		parsedline->auth_method = uaGSS;
+#else
+		unsupauth = "gss";
+#endif
+	else if (strcmp(token->string, "sspi") == 0)
+#ifdef ENABLE_SSPI
+		parsedline->auth_method = uaSSPI;
+#else
+		unsupauth = "sspi";
+#endif
+	else if (strcmp(token->string, "reject") == 0)
+		parsedline->auth_method = uaReject;
+	else if (strcmp(token->string, "md5") == 0)
+	{
+		if (Db_user_namespace)
 		{
-			case ipCmpMask:
-				if (hba->hostname)
-				{
-					addrstr = hba->hostname;
-				}
-				else
-				{
-					/*
-					 * Note: if pg_getnameinfo_all fails, it'll set buffer to
-					 * "???", which we want to return.
-					 */
-					if (hba->addrlen > 0)
-					{
-						if (pg_getnameinfo_all(&hba->addr, hba->addrlen,
-											   buffer, sizeof(buffer),
-											   NULL, 0,
-											   NI_NUMERICHOST) == 0)
-							clean_ipv6_addr(hba->addr.ss_family, buffer);
-						addrstr = pstrdup(buffer);
-					}
-					if (hba->masklen > 0)
-					{
-						if (pg_getnameinfo_all(&hba->mask, hba->masklen,
-											   buffer, sizeof(buffer),
-											   NULL, 0,
-											   NI_NUMERICHOST) == 0)
-							clean_ipv6_addr(hba->mask.ss_family, buffer);
-						maskstr = pstrdup(buffer);
-					}
-				}
-				break;
-			case ipCmpAll:
-				addrstr = "all";
-				break;
-			case ipCmpSameHost:
-				addrstr = "samehost";
-				break;
-			case ipCmpSameNet:
-				addrstr = "samenet";
-				break;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "MD5 authentication is not supported when \"db_user_namespace\" is enabled";
+			return NULL;
 		}
-		if (addrstr)
-			values[index++] = CStringGetTextDatum(addrstr);
-		else
-			nulls[index++] = true;
-		if (maskstr)
-			values[index++] = CStringGetTextDatum(maskstr);
-		else
-			nulls[index++] = true;
-
-		/* auth_method */
-		values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method));
-
-		/* options */
-		options = gethba_options(hba);
-		if (options)
-			values[index++] = PointerGetDatum(options);
-		else
-			nulls[index++] = true;
+		parsedline->auth_method = uaMD5;
 	}
+	else if (strcmp(token->string, "scram-sha-256") == 0)
+		parsedline->auth_method = uaSCRAM;
+	else if (strcmp(token->string, "pam") == 0)
+#ifdef USE_PAM
+		parsedline->auth_method = uaPAM;
+#else
+		unsupauth = "pam";
+#endif
+	else if (strcmp(token->string, "bsd") == 0)
+#ifdef USE_BSD_AUTH
+		parsedline->auth_method = uaBSD;
+#else
+		unsupauth = "bsd";
+#endif
+	else if (strcmp(token->string, "ldap") == 0)
+#ifdef USE_LDAP
+		parsedline->auth_method = uaLDAP;
+#else
+		unsupauth = "ldap";
+#endif
+	else if (strcmp(token->string, "cert") == 0)
+#ifdef USE_SSL
+		parsedline->auth_method = uaCert;
+#else
+		unsupauth = "cert";
+#endif
+	else if (strcmp(token->string, "radius") == 0)
+		parsedline->auth_method = uaRADIUS;
 	else
 	{
-		/* no parsing result, so set relevant fields to nulls */
-		memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool));
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("invalid authentication method \"%s\"",
+						token->string),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = psprintf("invalid authentication method \"%s\"",
+							token->string);
+		return NULL;
 	}
 
-	/* error */
-	if (err_msg)
-		values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
-	else
-		nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
-
-	tuple = heap_form_tuple(tupdesc, values, nulls);
-	tuplestore_puttuple(tuple_store, tuple);
-}
-
-/*
- * Read the pg_hba.conf file and fill the tuplestore with view records.
- */
-static void
-fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
-{
-	FILE	   *file;
-	List	   *hba_lines = NIL;
-	ListCell   *line;
-	MemoryContext linecxt;
-	MemoryContext hbacxt;
-	MemoryContext oldcxt;
+	if (unsupauth)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("invalid authentication method \"%s\": not supported by this build",
+						token->string),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = psprintf("invalid authentication method \"%s\": not supported by this build",
+							token->string);
+		return NULL;
+	}
 
 	/*
-	 * In the unlikely event that we can't open pg_hba.conf, we throw an
-	 * error, rather than trying to report it via some sort of view entry.
-	 * (Most other error conditions should result in a message in a view
-	 * entry.)
+	 * XXX: When using ident on local connections, change it to peer, for
+	 * backwards compatibility.
 	 */
-	file = AllocateFile(HbaFileName, "r");
-	if (file == NULL)
-		ereport(ERROR,
-				(errcode_for_file_access(),
-				 errmsg("could not open configuration file \"%s\": %m",
-						HbaFileName)));
-
-	linecxt = tokenize_file(HbaFileName, file, &hba_lines, DEBUG3);
-	FreeFile(file);
+	if (parsedline->conntype == ctLocal &&
+		parsedline->auth_method == uaIdent)
+		parsedline->auth_method = uaPeer;
 
-	/* Now parse all the lines */
-	hbacxt = AllocSetContextCreate(CurrentMemoryContext,
-								   "hba parser context",
-								   ALLOCSET_SMALL_SIZES);
-	oldcxt = MemoryContextSwitchTo(hbacxt);
-	foreach(line, hba_lines)
+	/* Invalid authentication combinations */
+	if (parsedline->conntype == ctLocal &&
+		parsedline->auth_method == uaGSS)
 	{
-		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
-		HbaLine    *hbaline = NULL;
-
-		/* don't parse lines that already have errors */
-		if (tok_line->err_msg == NULL)
-			hbaline = parse_hba_line(tok_line, DEBUG3);
-
-		fill_hba_line(tuple_store, tupdesc, tok_line->line_num,
-					  hbaline, tok_line->err_msg);
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("gssapi authentication is not supported on local sockets"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "gssapi authentication is not supported on local sockets";
+		return NULL;
 	}
 
-	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
-	/* Free parse_hba_line memory */
-	MemoryContextSwitchTo(oldcxt);
-	MemoryContextDelete(hbacxt);
-}
-
-/*
- * SQL-accessible SRF to return all the entries in the pg_hba.conf file.
- */
-Datum
-pg_hba_file_rules(PG_FUNCTION_ARGS)
-{
-	Tuplestorestate *tuple_store;
-	TupleDesc	tupdesc;
-	MemoryContext old_cxt;
-	ReturnSetInfo *rsi;
+	if (parsedline->conntype != ctLocal &&
+		parsedline->auth_method == uaPeer)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("peer authentication is only supported on local sockets"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "peer authentication is only supported on local sockets";
+		return NULL;
+	}
 
 	/*
-	 * We must use the Materialize mode to be safe against HBA file changes
-	 * while the cursor is open. It's also more efficient than having to look
-	 * up our current position in the parsed list every time.
+	 * SSPI authentication can never be enabled on ctLocal connections,
+	 * because it's only supported on Windows, where ctLocal isn't supported.
 	 */
-	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
-
-	/* Check to see if caller supports us returning a tuplestore */
-	if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("set-valued function called in context that cannot accept a set")));
-	if (!(rsi->allowedModes & SFRM_Materialize))
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("materialize mode required, but it is not allowed in this context")));
-
-	rsi->returnMode = SFRM_Materialize;
-
-	/* Build a tuple descriptor for our result type */
-	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
-		elog(ERROR, "return type must be a row type");
-
-	/* Build tuplestore to hold the result rows */
-	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
-
-	tuple_store =
-		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
-							  false, work_mem);
-	rsi->setDesc = tupdesc;
-	rsi->setResult = tuple_store;
-
-	MemoryContextSwitchTo(old_cxt);
-
-	/* Fill the tuplestore */
-	fill_hba_view(tuple_store, tupdesc);
-
-	PG_RETURN_NULL();
-}
-
-
-/*
- * Parse one tokenised line from the ident config file and store the result in
- * an IdentLine structure.
- *
- * If parsing fails, log a message and return NULL.
- *
- * If ident_user is a regular expression (ie. begins with a slash), it is
- * compiled and stored in IdentLine structure.
- *
- * Note: this function leaks memory when an error occurs.  Caller is expected
- * to have set a memory context that will be reset if this function returns
- * NULL.
- */
-static IdentLine *
-parse_ident_line(TokenizedLine *tok_line)
-{
-	int			line_num = tok_line->line_num;
-	ListCell   *field;
-	List	   *tokens;
-	HbaToken   *token;
-	IdentLine  *parsedline;
-
-	Assert(tok_line->fields != NIL);
-	field = list_head(tok_line->fields);
-
-	parsedline = palloc0(sizeof(IdentLine));
-	parsedline->linenumber = line_num;
-
-	/* Get the map token (must exist) */
-	tokens = lfirst(field);
-	IDENT_MULTI_VALUE(tokens);
-	token = linitial(tokens);
-	parsedline->usermap = pstrdup(token->string);
 
-	/* Get the ident user token */
-	field = lnext(tok_line->fields, field);
-	IDENT_FIELD_ABSENT(field);
-	tokens = lfirst(field);
-	IDENT_MULTI_VALUE(tokens);
-	token = linitial(tokens);
-	parsedline->ident_user = pstrdup(token->string);
-
-	/* Get the PG rolename token */
-	field = lnext(tok_line->fields, field);
-	IDENT_FIELD_ABSENT(field);
-	tokens = lfirst(field);
-	IDENT_MULTI_VALUE(tokens);
-	token = linitial(tokens);
-	parsedline->pg_role = pstrdup(token->string);
 
-	if (parsedline->ident_user[0] == '/')
+	if (parsedline->conntype != ctHostSSL &&
+		parsedline->auth_method == uaCert)
 	{
-		/*
-		 * When system username starts with a slash, treat it as a regular
-		 * expression. Pre-compile it.
-		 */
-		int			r;
-		pg_wchar   *wstr;
-		int			wlen;
-
-		wstr = palloc((strlen(parsedline->ident_user + 1) + 1) * sizeof(pg_wchar));
-		wlen = pg_mb2wchar_with_len(parsedline->ident_user + 1,
-									wstr, strlen(parsedline->ident_user + 1));
-
-		r = pg_regcomp(&parsedline->re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
-		if (r)
-		{
-			char		errstr[100];
-
-			pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));
-			ereport(LOG,
-					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
-					 errmsg("invalid regular expression \"%s\": %s",
-							parsedline->ident_user + 1, errstr)));
-
-			pfree(wstr);
-			return NULL;
-		}
-		pfree(wstr);
-	}
-
-	return parsedline;
-}
-
-/*
- *	Process one line from the parsed ident config lines.
- *
- *	Compare input parsed ident line to the needed map, pg_role and ident_user.
- *	*found_p and *error_p are set according to our results.
- */
-static void
-check_ident_usermap(IdentLine *identLine, const char *usermap_name,
-					const char *pg_role, const char *ident_user,
-					bool case_insensitive, bool *found_p, bool *error_p)
-{
-	*found_p = false;
-	*error_p = false;
+		ereport(elevel,
+				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+				 errmsg("cert authentication is only supported on hostssl connections"),
+				 errcontext("line %d of configuration file \"%s\"",
+							line_num, HbaFileName)));
+		*err_msg = "cert authentication is only supported on hostssl connections";
+		return NULL;
+	}
 
-	if (strcmp(identLine->usermap, usermap_name) != 0)
-		/* Line does not match the map name we're looking for, so just abort */
-		return;
+	/*
+	 * For GSS and SSPI, set the default value of include_realm to true.
+	 * Having include_realm set to false is dangerous in multi-realm
+	 * situations and is generally considered bad practice.  We keep the
+	 * capability around for backwards compatibility, but we might want to
+	 * remove it at some point in the future.  Users who still need to strip
+	 * the realm off would be better served by using an appropriate regex in a
+	 * pg_ident.conf mapping.
+	 */
+	if (parsedline->auth_method == uaGSS ||
+		parsedline->auth_method == uaSSPI)
+		parsedline->include_realm = true;
 
-	/* Match? */
-	if (identLine->ident_user[0] == '/')
+	/*
+	 * For SSPI, include_realm defaults to the SAM-compatible domain (aka
+	 * NetBIOS name) and user names instead of the Kerberos principal name for
+	 * compatibility.
+	 */
+	if (parsedline->auth_method == uaSSPI)
 	{
-		/*
-		 * When system username starts with a slash, treat it as a regular
-		 * expression. In this case, we process the system username as a
-		 * regular expression that returns exactly one match. This is replaced
-		 * for \1 in the database username string, if present.
-		 */
-		int			r;
-		regmatch_t	matches[2];
-		pg_wchar   *wstr;
-		int			wlen;
-		char	   *ofs;
-		char	   *regexp_pgrole;
-
-		wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar));
-		wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user));
+		parsedline->compat_realm = true;
+		parsedline->upn_username = false;
+	}
 
-		r = pg_regexec(&identLine->re, wstr, wlen, 0, NULL, 2, matches, 0);
-		if (r)
+	/* Parse remaining arguments */
+	while ((field = lnext(tok_line->fields, field)) != NULL)
+	{
+		tokens = lfirst(field);
+		foreach(tokencell, tokens)
 		{
-			char		errstr[100];
+			char	   *val;
 
-			if (r != REG_NOMATCH)
+			token = lfirst(tokencell);
+
+			str = pstrdup(token->string);
+			val = strchr(str, '=');
+			if (val == NULL)
 			{
-				/* REG_NOMATCH is not an error, everything else is */
-				pg_regerror(r, &identLine->re, errstr, sizeof(errstr));
-				ereport(LOG,
-						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
-						 errmsg("regular expression match for \"%s\" failed: %s",
-								identLine->ident_user + 1, errstr)));
-				*error_p = true;
+				/*
+				 * Got something that's not a name=value pair.
+				 */
+				ereport(elevel,
+						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+						 errmsg("authentication option not in name=value format: %s", token->string),
+						 errcontext("line %d of configuration file \"%s\"",
+									line_num, HbaFileName)));
+				*err_msg = psprintf("authentication option not in name=value format: %s",
+									token->string);
+				return NULL;
 			}
 
-			pfree(wstr);
-			return;
+			*val++ = '\0';		/* str now holds "name", val holds "value" */
+			if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
+				/* parse_hba_auth_opt already logged the error message */
+				return NULL;
+			pfree(str);
 		}
-		pfree(wstr);
+	}
 
-		if ((ofs = strstr(identLine->pg_role, "\\1")) != NULL)
-		{
-			int			offset;
+	/*
+	 * Check if the selected authentication method has any mandatory arguments
+	 * that are not set.
+	 */
+	if (parsedline->auth_method == uaLDAP)
+	{
+#ifndef HAVE_LDAP_INITIALIZE
+		/* Not mandatory for OpenLDAP, because it can use DNS SRV records */
+		MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
+#endif
 
-			/* substitution of the first argument requested */
-			if (matches[1].rm_so < 0)
+		/*
+		 * LDAP can operate in two modes: either with a direct bind, using
+		 * ldapprefix and ldapsuffix, or using a search+bind, using
+		 * ldapbasedn, ldapbinddn, ldapbindpasswd and one of
+		 * ldapsearchattribute or ldapsearchfilter.  Disallow mixing these
+		 * parameters.
+		 */
+		if (parsedline->ldapprefix || parsedline->ldapsuffix)
+		{
+			if (parsedline->ldapbasedn ||
+				parsedline->ldapbinddn ||
+				parsedline->ldapbindpasswd ||
+				parsedline->ldapsearchattribute ||
+				parsedline->ldapsearchfilter)
 			{
-				ereport(LOG,
-						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
-						 errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
-								identLine->ident_user + 1, identLine->pg_role)));
-				*error_p = true;
-				return;
+				ereport(elevel,
+						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix"),
+						 errcontext("line %d of configuration file \"%s\"",
+									line_num, HbaFileName)));
+				*err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix";
+				return NULL;
 			}
-
-			/*
-			 * length: original length minus length of \1 plus length of match
-			 * plus null terminator
-			 */
-			regexp_pgrole = palloc0(strlen(identLine->pg_role) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
-			offset = ofs - identLine->pg_role;
-			memcpy(regexp_pgrole, identLine->pg_role, offset);
-			memcpy(regexp_pgrole + offset,
-				   ident_user + matches[1].rm_so,
-				   matches[1].rm_eo - matches[1].rm_so);
-			strcat(regexp_pgrole, ofs + 2);
 		}
-		else
+		else if (!parsedline->ldapbasedn)
 		{
-			/* no substitution, so copy the match */
-			regexp_pgrole = pstrdup(identLine->pg_role);
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
+			return NULL;
 		}
 
 		/*
-		 * now check if the username actually matched what the user is trying
-		 * to connect as
+		 * When using search+bind, you can either use a simple attribute
+		 * (defaulting to "uid") or a fully custom search filter.  You can't
+		 * do both.
 		 */
-		if (case_insensitive)
-		{
-			if (pg_strcasecmp(regexp_pgrole, pg_role) == 0)
-				*found_p = true;
-		}
-		else
+		if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter)
 		{
-			if (strcmp(regexp_pgrole, pg_role) == 0)
-				*found_p = true;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter";
+			return NULL;
 		}
-		pfree(regexp_pgrole);
-
-		return;
 	}
-	else
+
+	if (parsedline->auth_method == uaRADIUS)
 	{
-		/* Not regular expression, so make complete match */
-		if (case_insensitive)
+		MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
+		MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
+
+		if (list_length(parsedline->radiusservers) < 1)
 		{
-			if (pg_strcasecmp(identLine->pg_role, pg_role) == 0 &&
-				pg_strcasecmp(identLine->ident_user, ident_user) == 0)
-				*found_p = true;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("list of RADIUS servers cannot be empty"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "list of RADIUS servers cannot be empty";
+			return NULL;
 		}
-		else
+
+		if (list_length(parsedline->radiussecrets) < 1)
 		{
-			if (strcmp(identLine->pg_role, pg_role) == 0 &&
-				strcmp(identLine->ident_user, ident_user) == 0)
-				*found_p = true;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("list of RADIUS secrets cannot be empty"),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = "list of RADIUS secrets cannot be empty";
+			return NULL;
 		}
-	}
-}
-
-
-/*
- *	Scan the (pre-parsed) ident usermap file line by line, looking for a match
- *
- *	See if the user with ident username "auth_user" is allowed to act
- *	as Postgres user "pg_role" according to usermap "usermap_name".
- *
- *	Special case: Usermap NULL, equivalent to what was previously called
- *	"sameuser" or "samerole", means don't look in the usermap file.
- *	That's an implied map wherein "pg_role" must be identical to
- *	"auth_user" in order to be authorized.
- *
- *	Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
- */
-int
-check_usermap(const char *usermap_name,
-			  const char *pg_role,
-			  const char *auth_user,
-			  bool case_insensitive)
-{
-	bool		found_entry = false,
-				error = false;
 
-	if (usermap_name == NULL || usermap_name[0] == '\0')
-	{
-		if (case_insensitive)
+		/*
+		 * Verify length of option lists - each can be 0 (except for secrets,
+		 * but that's already checked above), 1 (use the same value
+		 * everywhere) or the same as the number of servers.
+		 */
+		if (!(list_length(parsedline->radiussecrets) == 1 ||
+			  list_length(parsedline->radiussecrets) == list_length(parsedline->radiusservers)))
 		{
-			if (pg_strcasecmp(pg_role, auth_user) == 0)
-				return STATUS_OK;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+							list_length(parsedline->radiussecrets),
+							list_length(parsedline->radiusservers)),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = psprintf("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+								list_length(parsedline->radiussecrets),
+								list_length(parsedline->radiusservers));
+			return NULL;
 		}
-		else
+		if (!(list_length(parsedline->radiusports) == 0 ||
+			  list_length(parsedline->radiusports) == 1 ||
+			  list_length(parsedline->radiusports) == list_length(parsedline->radiusservers)))
 		{
-			if (strcmp(pg_role, auth_user) == 0)
-				return STATUS_OK;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+							list_length(parsedline->radiusports),
+							list_length(parsedline->radiusservers)),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = psprintf("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+								list_length(parsedline->radiusports),
+								list_length(parsedline->radiusservers));
+			return NULL;
 		}
-		ereport(LOG,
-				(errmsg("provided user name (%s) and authenticated user name (%s) do not match",
-						pg_role, auth_user)));
-		return STATUS_ERROR;
-	}
-	else
-	{
-		ListCell   *line_cell;
-
-		foreach(line_cell, parsed_ident_lines)
+		if (!(list_length(parsedline->radiusidentifiers) == 0 ||
+			  list_length(parsedline->radiusidentifiers) == 1 ||
+			  list_length(parsedline->radiusidentifiers) == list_length(parsedline->radiusservers)))
 		{
-			check_ident_usermap(lfirst(line_cell), usermap_name,
-								pg_role, auth_user, case_insensitive,
-								&found_entry, &error);
-			if (found_entry || error)
-				break;
+			ereport(elevel,
+					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+					 errmsg("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+							list_length(parsedline->radiusidentifiers),
+							list_length(parsedline->radiusservers)),
+					 errcontext("line %d of configuration file \"%s\"",
+								line_num, HbaFileName)));
+			*err_msg = psprintf("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+								list_length(parsedline->radiusidentifiers),
+								list_length(parsedline->radiusservers));
+			return NULL;
 		}
 	}
-	if (!found_entry && !error)
+
+	/*
+	 * Enforce any parameters implied by other settings.
+	 */
+	if (parsedline->auth_method == uaCert)
 	{
-		ereport(LOG,
-				(errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
-						usermap_name, pg_role, auth_user)));
+		/*
+		 * For auth method cert, client certificate validation is mandatory, and it implies
+		 * the level of verify-full.
+		 */
+		parsedline->clientcert = clientCertFull;
 	}
-	return found_entry ? STATUS_OK : STATUS_ERROR;
-}
 
+	return parsedline;
+}
 
 /*
- * Read the ident config file and create a List of IdentLine records for
- * the contents.
+ * Tokenize the given file.
  *
- * This works the same as load_hba(), but for the user config file.
+ * The output is a list of TokenizedAuthLine structs; see struct definition
+ * above.
+ *
+ * filename: the absolute path to the target file
+ * file: the already-opened target file
+ * tok_lines: receives output list
+ * elevel: message logging level
+ *
+ * Errors are reported by logging messages at ereport level elevel and by
+ * adding TokenizedAuthLine structs containing non-null err_msg fields to the
+ * output list.
+ *
+ * Return value is a memory context which contains all memory allocated by
+ * this function (it's a child of caller's context).
  */
-bool
-load_ident(void)
+MemoryContext
+tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, int elevel)
 {
-	FILE	   *file;
-	List	   *ident_lines = NIL;
-	ListCell   *line_cell,
-			   *parsed_line_cell;
-	List	   *new_parsed_lines = NIL;
-	bool		ok = true;
+	int			line_number = 1;
+	StringInfoData buf;
 	MemoryContext linecxt;
 	MemoryContext oldcxt;
-	MemoryContext ident_context;
-	IdentLine  *newline;
 
-	file = AllocateFile(IdentFileName, "r");
-	if (file == NULL)
-	{
-		/* not fatal ... we just won't do any special ident maps */
-		ereport(LOG,
-				(errcode_for_file_access(),
-				 errmsg("could not open usermap file \"%s\": %m",
-						IdentFileName)));
-		return false;
-	}
+	linecxt = AllocSetContextCreate(CurrentMemoryContext,
+									"tokenize_auth_file",
+									ALLOCSET_SMALL_SIZES);
+	oldcxt = MemoryContextSwitchTo(linecxt);
 
-	linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
-	FreeFile(file);
+	initStringInfo(&buf);
 
-	/* Now parse all the lines */
-	Assert(PostmasterContext);
-	ident_context = AllocSetContextCreate(PostmasterContext,
-										  "ident parser context",
-										  ALLOCSET_SMALL_SIZES);
-	oldcxt = MemoryContextSwitchTo(ident_context);
-	foreach(line_cell, ident_lines)
+	*tok_lines = NIL;
+
+	while (!feof(file) && !ferror(file))
 	{
-		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);
+		char	   *lineptr;
+		List	   *current_line = NIL;
+		char	   *err_msg = NULL;
+		int			last_backslash_buflen = 0;
+		int			continuations = 0;
 
-		/* don't parse lines that already have errors */
-		if (tok_line->err_msg != NULL)
-		{
-			ok = false;
-			continue;
-		}
+		/* Collect the next input line, handling backslash continuations */
+		resetStringInfo(&buf);
 
-		if ((newline = parse_ident_line(tok_line)) == NULL)
+		while (pg_get_line_append(file, &buf, NULL))
 		{
-			/* Parse error; remember there's trouble */
-			ok = false;
+			/* Strip trailing newline, including \r in case we're on Windows */
+			buf.len = pg_strip_crlf(buf.data);
 
 			/*
-			 * Keep parsing the rest of the file so we can report errors on
-			 * more than the first line.  Error has already been logged, no
-			 * need for more chatter here.
+			 * Check for backslash continuation.  The backslash must be after
+			 * the last place we found a continuation, else two backslashes
+			 * followed by two \n's would behave surprisingly.
 			 */
-			continue;
-		}
-
-		new_parsed_lines = lappend(new_parsed_lines, newline);
-	}
-
-	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
-	MemoryContextSwitchTo(oldcxt);
+			if (buf.len > last_backslash_buflen &&
+				buf.data[buf.len - 1] == '\\')
+			{
+				/* Continuation, so strip it and keep reading */
+				buf.data[--buf.len] = '\0';
+				last_backslash_buflen = buf.len;
+				continuations++;
+				continue;
+			}
 
-	if (!ok)
-	{
-		/*
-		 * File contained one or more errors, so bail out, first being careful
-		 * to clean up whatever we allocated.  Most stuff will go away via
-		 * MemoryContextDelete, but we have to clean up regexes explicitly.
-		 */
-		foreach(parsed_line_cell, new_parsed_lines)
-		{
-			newline = (IdentLine *) lfirst(parsed_line_cell);
-			if (newline->ident_user[0] == '/')
-				pg_regfree(&newline->re);
+			/* Nope, so we have the whole line */
+			break;
 		}
-		MemoryContextDelete(ident_context);
-		return false;
-	}
 
-	/* Loaded new file successfully, replace the one we use */
-	if (parsed_ident_lines != NIL)
-	{
-		foreach(parsed_line_cell, parsed_ident_lines)
+		if (ferror(file))
 		{
-			newline = (IdentLine *) lfirst(parsed_line_cell);
-			if (newline->ident_user[0] == '/')
-				pg_regfree(&newline->re);
-		}
-	}
-	if (parsed_ident_context != NULL)
-		MemoryContextDelete(parsed_ident_context);
+			/* I/O error! */
+			int			save_errno = errno;
 
-	parsed_ident_context = ident_context;
-	parsed_ident_lines = new_parsed_lines;
+			ereport(elevel,
+					(errcode_for_file_access(),
+					 errmsg("could not read file \"%s\": %m", filename)));
+			err_msg = psprintf("could not read file \"%s\": %s",
+							   filename, strerror(save_errno));
+			break;
+		}
 
-	return true;
-}
+		/* Parse fields */
+		lineptr = buf.data;
+		while (*lineptr && err_msg == NULL)
+		{
+			List	   *current_field;
 
+			current_field = next_field_expand(filename, &lineptr,
+											  elevel, &err_msg);
+			/* add field to line, unless we are at EOL or comment start */
+			if (current_field != NIL)
+				current_line = lappend(current_line, current_field);
+		}
 
+		/*
+		 * Reached EOL; emit line to TokenizedAuthLine list unless it's boring
+		 * */
+		if (current_line != NIL || err_msg != NULL)
+		{
+			TokenizedAuthLine *tok_line;
 
-/*
- *	Determine what authentication method should be used when accessing database
- *	"database" from frontend "raddr", user "user".  Return the method and
- *	an optional argument (stored in fields of *port), and STATUS_OK.
- *
- *	If the file does not contain any entry matching the request, we return
- *	method = uaImplicitReject.
- */
-void
-hba_getauthmethod(hbaPort *port)
-{
-	check_hba(port);
-}
+			tok_line = (TokenizedAuthLine *) palloc(sizeof(TokenizedAuthLine));
+			tok_line->fields = current_line;
+			tok_line->line_num = line_number;
+			tok_line->raw_line = pstrdup(buf.data);
+			tok_line->err_msg = err_msg;
+			*tok_lines = lappend(*tok_lines, tok_line);
+		}
 
+		line_number += continuations + 1;
+	}
 
-/*
- * Return the name of the auth method in use ("gss", "md5", "trust", etc.).
- *
- * The return value is statically allocated (see the UserAuthName array) and
- * should not be freed.
- */
-const char *
-hba_authname(UserAuth auth_method)
-{
-	/*
-	 * Make sure UserAuthName[] tracks additions to the UserAuth enum
-	 */
-	StaticAssertStmt(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
-					 "UserAuthName[] must match the UserAuth enum");
+	MemoryContextSwitchTo(oldcxt);
 
-	return UserAuthName[auth_method];
+	return linecxt;
 }
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..7c722ea2ce 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -42,6 +42,7 @@ OBJS = \
 	geo_ops.o \
 	geo_selfuncs.o \
 	geo_spgist.o \
+	hbafuncs.o \
 	inet_cidr_ntop.o \
 	inet_net_pton.o \
 	int.o \
diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c
new file mode 100644
index 0000000000..2bcaebe99e
--- /dev/null
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -0,0 +1,452 @@
+/*-------------------------------------------------------------------------
+ *
+ * hbafuncs.c
+ *	  Support functions for authentication files SQL views.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/utils/adt/hbafuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/objectaddress.h"
+#include "common/ip.h"
+#include "funcapi.h"
+#include "libpq/hba.h"
+#include "miscadmin.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+//#include "utils/tuplestore.h"
+
+
+static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+						  int lineno, HbaLine *hba, const char *err_msg);
+static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
+static ArrayType *gethba_options(HbaLine *hba);
+
+
+/* Number of columns in pg_hba_file_rules view */
+#define NUM_PG_HBA_FILE_RULES_ATTS	 9
+
+/*
+ * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore
+ *
+ * tuple_store: where to store data
+ * tupdesc: tuple descriptor for the view
+ * lineno: pg_hba.conf line number (must always be valid)
+ * hba: parsed line data (can be NULL, in which case err_msg should be set)
+ * err_msg: error message (NULL if none)
+ *
+ * Note: leaks memory, but we don't care since this is run in a short-lived
+ * memory context.
+ */
+static void
+fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+			  int lineno, HbaLine *hba, const char *err_msg)
+{
+	Datum		values[NUM_PG_HBA_FILE_RULES_ATTS];
+	bool		nulls[NUM_PG_HBA_FILE_RULES_ATTS];
+	char		buffer[NI_MAXHOST];
+	HeapTuple	tuple;
+	int			index;
+	ListCell   *lc;
+	const char *typestr;
+	const char *addrstr;
+	const char *maskstr;
+	ArrayType  *options;
+
+	Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
+
+	memset(values, 0, sizeof(values));
+	memset(nulls, 0, sizeof(nulls));
+	index = 0;
+
+	/* line_number */
+	values[index++] = Int32GetDatum(lineno);
+
+	if (hba != NULL)
+	{
+		/* type */
+		/* Avoid a default: case so compiler will warn about missing cases */
+		typestr = NULL;
+		switch (hba->conntype)
+		{
+			case ctLocal:
+				typestr = "local";
+				break;
+			case ctHost:
+				typestr = "host";
+				break;
+			case ctHostSSL:
+				typestr = "hostssl";
+				break;
+			case ctHostNoSSL:
+				typestr = "hostnossl";
+				break;
+			case ctHostGSS:
+				typestr = "hostgssenc";
+				break;
+			case ctHostNoGSS:
+				typestr = "hostnogssenc";
+				break;
+		}
+		if (typestr)
+			values[index++] = CStringGetTextDatum(typestr);
+		else
+			nulls[index++] = true;
+
+		/* database */
+		if (hba->databases)
+		{
+			/*
+			 * Flatten HbaToken list to string list.  It might seem that we
+			 * should re-quote any quoted tokens, but that has been rejected
+			 * on the grounds that it makes it harder to compare the array
+			 * elements to other system catalogs.  That makes entries like
+			 * "all" or "samerole" formally ambiguous ... but users who name
+			 * databases/roles that way are inflicting their own pain.
+			 */
+			List	   *names = NIL;
+
+			foreach(lc, hba->databases)
+			{
+				HbaToken   *tok = lfirst(lc);
+
+				names = lappend(names, tok->string);
+			}
+			values[index++] = PointerGetDatum(strlist_to_textarray(names));
+		}
+		else
+			nulls[index++] = true;
+
+		/* user */
+		if (hba->roles)
+		{
+			/* Flatten HbaToken list to string list; see comment above */
+			List	   *roles = NIL;
+
+			foreach(lc, hba->roles)
+			{
+				HbaToken   *tok = lfirst(lc);
+
+				roles = lappend(roles, tok->string);
+			}
+			values[index++] = PointerGetDatum(strlist_to_textarray(roles));
+		}
+		else
+			nulls[index++] = true;
+
+		/* address and netmask */
+		/* Avoid a default: case so compiler will warn about missing cases */
+		addrstr = maskstr = NULL;
+		switch (hba->ip_cmp_method)
+		{
+			case ipCmpMask:
+				if (hba->hostname)
+				{
+					addrstr = hba->hostname;
+				}
+				else
+				{
+					/*
+					 * Note: if pg_getnameinfo_all fails, it'll set buffer to
+					 * "???", which we want to return.
+					 */
+					if (hba->addrlen > 0)
+					{
+						if (pg_getnameinfo_all(&hba->addr, hba->addrlen,
+											   buffer, sizeof(buffer),
+											   NULL, 0,
+											   NI_NUMERICHOST) == 0)
+							clean_ipv6_addr(hba->addr.ss_family, buffer);
+						addrstr = pstrdup(buffer);
+					}
+					if (hba->masklen > 0)
+					{
+						if (pg_getnameinfo_all(&hba->mask, hba->masklen,
+											   buffer, sizeof(buffer),
+											   NULL, 0,
+											   NI_NUMERICHOST) == 0)
+							clean_ipv6_addr(hba->mask.ss_family, buffer);
+						maskstr = pstrdup(buffer);
+					}
+				}
+				break;
+			case ipCmpAll:
+				addrstr = "all";
+				break;
+			case ipCmpSameHost:
+				addrstr = "samehost";
+				break;
+			case ipCmpSameNet:
+				addrstr = "samenet";
+				break;
+		}
+		if (addrstr)
+			values[index++] = CStringGetTextDatum(addrstr);
+		else
+			nulls[index++] = true;
+		if (maskstr)
+			values[index++] = CStringGetTextDatum(maskstr);
+		else
+			nulls[index++] = true;
+
+		/* auth_method */
+		values[index++] = CStringGetTextDatum(hba_authname(hba->auth_method));
+
+		/* options */
+		options = gethba_options(hba);
+		if (options)
+			values[index++] = PointerGetDatum(options);
+		else
+			nulls[index++] = true;
+	}
+	else
+	{
+		/* no parsing result, so set relevant fields to nulls */
+		memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool));
+	}
+
+	/* error */
+	if (err_msg)
+		values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
+	else
+		nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
+
+	tuple = heap_form_tuple(tupdesc, values, nulls);
+	tuplestore_puttuple(tuple_store, tuple);
+}
+
+/*
+ * Read the pg_hba.conf file and fill the tuplestore with view records.
+ */
+static void
+fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
+{
+	FILE	   *file;
+	List	   *hba_lines = NIL;
+	ListCell   *line;
+	MemoryContext linecxt;
+	MemoryContext hbacxt;
+	MemoryContext oldcxt;
+
+	/*
+	 * In the unlikely event that we can't open pg_hba.conf, we throw an
+	 * error, rather than trying to report it via some sort of view entry.
+	 * (Most other error conditions should result in a message in a view
+	 * entry.)
+	 */
+	file = AllocateFile(HbaFileName, "r");
+	if (file == NULL)
+		ereport(ERROR,
+				(errcode_for_file_access(),
+				 errmsg("could not open configuration file \"%s\": %m",
+						HbaFileName)));
+
+	linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3);
+	FreeFile(file);
+
+	/* Now parse all the lines */
+	hbacxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "hba parser context",
+								   ALLOCSET_SMALL_SIZES);
+	oldcxt = MemoryContextSwitchTo(hbacxt);
+	foreach(line, hba_lines)
+	{
+		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
+		HbaLine    *hbaline = NULL;
+
+		/* don't parse lines that already have errors */
+		if (tok_line->err_msg == NULL)
+			hbaline = parse_hba_line(tok_line, DEBUG3);
+
+		fill_hba_line(tuple_store, tupdesc, tok_line->line_num,
+					  hbaline, tok_line->err_msg);
+	}
+
+	/* Free tokenizer memory */
+	MemoryContextDelete(linecxt);
+	/* Free parse_hba_line memory */
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(hbacxt);
+}
+
+/*
+ * This macro specifies the maximum number of authentication options
+ * that are possible with any given authentication method that is supported.
+ * Currently LDAP supports 11, and there are 3 that are not dependent on
+ * the auth method here.  It may not actually be possible to set all of them
+ * at the same time, but we'll set the macro value high enough to be
+ * conservative and avoid warnings from static analysis tools.
+ */
+#define MAX_HBA_OPTIONS 14
+
+/*
+ * Create a text array listing the options specified in the HBA line.
+ * Return NULL if no options are specified.
+ */
+static ArrayType *
+gethba_options(HbaLine *hba)
+{
+	int			noptions;
+	Datum		options[MAX_HBA_OPTIONS];
+
+	noptions = 0;
+
+	if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+	{
+		if (hba->include_realm)
+			options[noptions++] =
+				CStringGetTextDatum("include_realm=true");
+
+		if (hba->krb_realm)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm));
+	}
+
+	if (hba->usermap)
+		options[noptions++] =
+			CStringGetTextDatum(psprintf("map=%s", hba->usermap));
+
+	if (hba->clientcert != clientCertOff)
+		options[noptions++] =
+			CStringGetTextDatum(psprintf("clientcert=%s", (hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
+
+	if (hba->pamservice)
+		options[noptions++] =
+			CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice));
+
+	if (hba->auth_method == uaLDAP)
+	{
+		if (hba->ldapserver)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver));
+
+		if (hba->ldapport)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport));
+
+		if (hba->ldaptls)
+			options[noptions++] =
+				CStringGetTextDatum("ldaptls=true");
+
+		if (hba->ldapprefix)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix));
+
+		if (hba->ldapsuffix)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix));
+
+		if (hba->ldapbasedn)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn));
+
+		if (hba->ldapbinddn)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn));
+
+		if (hba->ldapbindpasswd)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapbindpasswd=%s",
+											 hba->ldapbindpasswd));
+
+		if (hba->ldapsearchattribute)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
+											 hba->ldapsearchattribute));
+
+		if (hba->ldapsearchfilter)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
+											 hba->ldapsearchfilter));
+
+		if (hba->ldapscope)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
+	}
+
+	if (hba->auth_method == uaRADIUS)
+	{
+		if (hba->radiusservers_s)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
+
+		if (hba->radiussecrets_s)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
+
+		if (hba->radiusidentifiers_s)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
+
+		if (hba->radiusports_s)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiusports=%s", hba->radiusports_s));
+	}
+
+	/* If you add more options, consider increasing MAX_HBA_OPTIONS. */
+	Assert(noptions <= MAX_HBA_OPTIONS);
+
+	if (noptions > 0)
+		return construct_array(options, noptions, TEXTOID, -1, false, TYPALIGN_INT);
+	else
+		return NULL;
+}
+
+/*
+ * SQL-accessible SRF to return all the entries in the pg_hba.conf file.
+ */
+Datum
+pg_hba_file_rules(PG_FUNCTION_ARGS)
+{
+	Tuplestorestate *tuple_store;
+	TupleDesc	tupdesc;
+	MemoryContext old_cxt;
+	ReturnSetInfo *rsi;
+
+	/*
+	 * We must use the Materialize mode to be safe against HBA file changes
+	 * while the cursor is open. It's also more efficient than having to look
+	 * up our current position in the parsed list every time.
+	 */
+	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	/* Check to see if caller supports us returning a tuplestore */
+	if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsi->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not allowed in this context")));
+
+	rsi->returnMode = SFRM_Materialize;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+	tuple_store =
+		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+							  false, work_mem);
+	rsi->setDesc = tupdesc;
+	rsi->setResult = tuple_store;
+
+	MemoryContextSwitchTo(old_cxt);
+
+	/* Fill the tuplestore */
+	fill_hba_view(tuple_store, tupdesc);
+
+	PG_RETURN_NULL();
+}
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8d9f3821b1..19924dca67 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -122,6 +122,16 @@ typedef struct HbaLine
 	char	   *radiusports_s;
 } HbaLine;
 
+/*
+ * A single string token lexed from a config file, together with whether
+ * the token had been quoted.
+ */
+typedef struct HbaToken
+{
+	char	   *string;
+	bool		quoted;
+} HbaToken;
+
 typedef struct IdentLine
 {
 	int			linenumber;
@@ -132,6 +142,22 @@ typedef struct IdentLine
 	regex_t		re;
 } IdentLine;
 
+/*
+ * TokenizedAuthLine represents one line lexed from a config file.
+ * Each item in the "fields" list is a sub-list of HbaTokens.
+ * We don't emit a TokenizedAuthLine for empty or all-comment lines,
+ * so "fields" is never NIL (nor are any of its sub-lists).
+ * Exception: if an error occurs during tokenization, we might
+ * have fields == NIL, in which case err_msg != NULL.
+ */
+typedef struct TokenizedAuthLine
+{
+	List	   *fields;			/* List of lists of HbaTokens */
+	int			line_num;		/* Line number */
+	char	   *raw_line;		/* Raw line text */
+	char	   *err_msg;		/* Error message if any */
+} TokenizedAuthLine;
+
 /* kluge to avoid including libpq/libpq-be.h here */
 typedef struct Port hbaPort;
 
@@ -142,6 +168,9 @@ extern void hba_getauthmethod(hbaPort *port);
 extern int	check_usermap(const char *usermap_name,
 						  const char *pg_role, const char *auth_user,
 						  bool case_sensitive);
+extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel);
 extern bool pg_isblank(const char c);
+extern MemoryContext tokenize_auth_file(const char *filename, FILE *file,
+										List **tok_lines, int elevel);
 
 #endif							/* HBA_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c6b302c7b2..f3a577652f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2694,7 +2694,7 @@ ToastTupleContext
 ToastedAttribute
 TocEntry
 TokenAuxData
-TokenizedLine
+TokenizedAuthLine
 TrackItem
 TransInvalidationInfo
 TransState
-- 
2.33.1

