diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 086fafc..204b8cf 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -7809,6 +7809,11 @@
      </row>
 
      <row>
+      <entry><link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link></entry>
+      <entry>summary of client authentication configuration file contents</entry>
+     </row>
+
+     <row>
       <entry><link linkend="view-pg-indexes"><structname>pg_indexes</structname></link></entry>
       <entry>indexes</entry>
      </row>
@@ -8408,6 +8413,114 @@
 
  </sect1>
 
+ <sect1 id="view-pg-hba-file-rules">
+  <title><structname>pg_hba_file_rules</structname></title>
+
+  <indexterm zone="view-pg-hba-file-rules">
+   <primary>pg_hba_file_rules</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_hba_file_rules</structname> provides a summary of
+   the contents of the client authentication configuration
+   file, <filename>pg_hba.conf</>.  A row appears in this view for each
+   non-empty, non-comment line in the file, with annotations indicating
+   whether the rule could be applied successfully.
+  </para>
+
+  <para>
+   This view can be helpful for checking whether planned changes in the
+   authentication configuration file will work, or for diagnosing a previous
+   failure.  Note that this view reports on the <emphasis>current</> contents
+   of the file, not on what was last loaded by the server.
+  </para>
+
+  <para>
+   By default, the <structname>pg_hba_file_rules</structname> view can be read
+   only by superusers.
+  </para>
+
+  <table>
+   <title><structname>pg_hba_file_rules</> Columns</title>
+
+  <tgroup cols="3">
+   <thead>
+    <row>
+     <entry>Name</entry>
+     <entry>Type</entry>
+     <entry>Description</entry>
+    </row>
+   </thead>
+   <tbody>
+    <row>
+     <entry><structfield>line_number</structfield></entry>
+     <entry><structfield>integer</structfield></entry>
+     <entry>
+      Line number of this rule in <filename>pg_hba.conf</>
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>type</structfield></entry>
+     <entry><structfield>text</structfield></entry>
+     <entry>Type of connection</entry>
+    </row>
+    <row>
+     <entry><structfield>database</structfield></entry>
+     <entry><structfield>text[]</structfield></entry>
+     <entry>List of database name(s) to which this rule applies</entry>
+    </row>
+    <row>
+     <entry><structfield>user_name</structfield></entry>
+     <entry><structfield>text[]</structfield></entry>
+     <entry>List of user and group name(s) to which this rule applies</entry>
+    </row>
+    <row>
+     <entry><structfield>address</structfield></entry>
+     <entry><structfield>text</structfield></entry>
+     <entry>
+      Host name or IP address, or one
+      of <literal>all</literal>, <literal>samehost</literal>,
+      or <literal>samenet</literal>, or null for local connections
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>netmask</structfield></entry>
+     <entry><structfield>text</structfield></entry>
+     <entry>IP address mask, or null if not applicable</entry>
+    </row>
+    <row>
+     <entry><structfield>auth_method</structfield></entry>
+     <entry><type>text</type></entry>
+     <entry>Authentication method</entry>
+    </row>
+    <row>
+     <entry><structfield>options</structfield></entry>
+     <entry><type>text[]</type></entry>
+     <entry>Options specified for authentication method, if any</entry>
+    </row>
+    <row>
+     <entry><structfield>error</structfield></entry>
+     <entry><structfield>text</structfield></entry>
+     <entry>
+      If not null, an error message indicating why this
+      line could not be processed
+     </entry>
+    </row>
+   </tbody>
+  </tgroup>
+  </table>
+
+  <para>
+   Usually, a row reflecting an incorrect entry will have values for only
+   the <structfield>line_number</> and <structfield>error</> fields.
+  </para>
+
+  <para>
+   See <xref linkend="client-authentication"> for more information about
+   client authentication configuration.
+  </para>
+ </sect1>
+
  <sect1 id="view-pg-indexes">
   <title><structname>pg_indexes</structname></title>
 
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index dda5891..231fc40 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -597,6 +597,24 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
    re-read the file.
   </para>
 
+  <note>
+   <para>
+    The preceding statement is not true on Microsoft Windows: there, any
+    changes in the <filename>pg_hba.conf</filename> file are immediately
+    applied by subsequent new connections.
+   </para>
+  </note>
+
+  <para>
+   The system view
+   <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link>
+   can be helpful for pre-testing changes to the <filename>pg_hba.conf</>
+   file, or for diagnosing problems if loading of the file did not have the
+   desired effects.  Rows in the view with
+   non-null <structfield>error</structfield> fields indicate problems in the
+   corresponding lines of the file.
+  </para>
+ 
   <tip>
    <para>
     To connect to a particular database, a user must not only pass the
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4dfedf8..28be27a 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -459,6 +459,12 @@ CREATE VIEW pg_file_settings AS
 REVOKE ALL on pg_file_settings FROM PUBLIC;
 REVOKE EXECUTE ON FUNCTION pg_show_all_file_settings() FROM PUBLIC;
 
+CREATE VIEW pg_hba_file_rules AS
+   SELECT * FROM pg_hba_file_rules() AS A;
+
+REVOKE ALL on pg_hba_file_rules FROM PUBLIC;
+REVOKE EXECUTE ON FUNCTION pg_hba_file_rules() FROM PUBLIC;
+
 CREATE VIEW pg_timezone_abbrevs AS
     SELECT * FROM pg_timezone_abbrevs();
 
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index bbe0a88..9672028 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -25,15 +25,20 @@
 #include <arpa/inet.h>
 #include <unistd.h>
 
+#include "access/htup_details.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "common/ip.h"
+#include "funcapi.h"
 #include "libpq/ifaddr.h"
 #include "libpq/libpq.h"
+#include "miscadmin.h"
 #include "postmaster/postmaster.h"
 #include "regex/regex.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -80,12 +85,15 @@ typedef struct HbaToken
  * 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;
 
 /*
@@ -106,13 +114,42 @@ static MemoryContext parsed_hba_context = NULL;
 static List *parsed_ident_lines = NIL;
 static MemoryContext parsed_ident_context = NULL;
 
+/*
+ * The following character array represents the names of the authentication
+ * methods that are supported by PostgreSQL.
+ *
+ * Note: keep this in sync with the UserAuth enum in hba.h.
+ */
+static const char *const UserAuthName[] =
+{
+	"reject",
+	"implicit reject",			/* Not a user-visible option */
+	"trust",
+	"ident",
+	"password",
+	"md5",
+	"gss",
+	"sspi",
+	"pam",
+	"bsd",
+	"ldap",
+	"cert",
+	"radius",
+	"peer"
+};
+
 
 static MemoryContext tokenize_file(const char *filename, FILE *file,
-			  List **tok_lines);
+			  List **tok_lines, int elevel);
 static List *tokenize_inc_file(List *tokens, const char *outer_filename,
-				  const char *inc_filename);
+				  const char *inc_filename, int elevel, char **err_msg);
 static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
-				   int line_num);
+				   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);
+
 
 /*
  * isblank() exists in the ISO C99 spec, but it's not very portable yet,
@@ -126,32 +163,37 @@ pg_isblank(const char c)
 
 
 /*
- * Grab one token out of the string pointed to by lineptr.
+ * Grab one token out of the string pointed to by *lineptr.
+ *
  * Tokens are strings of non-blank
  * characters bounded by blank characters, commas, beginning of line, and
  * end of line. Blank means space or tab. Tokens can be delimited by
  * double quotes (this allows the inclusion of blanks, but not newlines).
+ * Comments (started by an unquoted '#') are skipped.
+ *
+ * The token, if any, is returned at *buf (a buffer of size bufsz), and
+ * *lineptr is advanced past the token.
  *
- * The token, if any, is returned at *buf (a buffer of size bufsz).
  * Also, we set *initial_quote to indicate whether there was quoting before
  * the first character.  (We use that to prevent "@x" from being treated
  * as a file inclusion request.  Note that @"x" should be so treated;
  * we want to allow that to support embedded spaces in file paths.)
+ *
  * We set *terminating_comma to indicate whether the token is terminated by a
- * comma (which is not returned.)
+ * comma (which is not returned).
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error.  Currently the only
+ * possible error is token too long for buf.
  *
  * If successful: store null-terminated token at *buf and return TRUE.
  * If no more tokens on line: set *buf = '\0' and return FALSE.
- *
- * Leave file positioned at the character immediately after the token or EOF,
- * whichever comes first. If no more tokens on line, position the file to the
- * beginning of the next line or EOF, whichever comes first.
- *
- * Handle comments.
+ * If error: fill buf with truncated or misformatted token and return FALSE.
  */
 static bool
-next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
-		   bool *terminating_comma)
+next_token(char **lineptr, char *buf, int bufsz,
+		   bool *initial_quote, bool *terminating_comma,
+		   int elevel, char **err_msg)
 {
 	int			c;
 	char	   *start_buf = buf;
@@ -197,14 +239,15 @@ next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
 		if (buf >= end_buf)
 		{
 			*buf = '\0';
-			ereport(LOG,
+			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 			   errmsg("authentication file token too long, skipping: \"%s\"",
 					  start_buf)));
+			*err_msg = "authentication file token too long";
 			/* Discard remainder of line */
 			while ((c = (*(*lineptr)++)) != '\0' && c != '\n')
 				;
-			break;
+			return false;
 		}
 
 		/* we do not pass back the comma in the token */
@@ -245,13 +288,17 @@ next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
 	return (saw_quote || buf > start_buf);
 }
 
+/*
+ * Construct a palloc'd HbaToken struct, copying the given string.
+ */
 static HbaToken *
-make_hba_token(char *token, bool quoted)
+make_hba_token(const char *token, bool quoted)
 {
 	HbaToken   *hbatoken;
 	int			toklen;
 
 	toklen = strlen(token);
+	/* we copy string into same palloc block as the struct */
 	hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1);
 	hbatoken->string = (char *) hbatoken + sizeof(HbaToken);
 	hbatoken->quoted = quoted;
@@ -275,11 +322,20 @@ copy_hba_token(HbaToken *in)
 /*
  * Tokenize one HBA field from a line, handling file inclusion and comma lists.
  *
- * The result is a List of HbaToken structs for each individual token,
+ * filename: current file's pathname (needed to resolve relative pathnames)
+ * *lineptr: current line pointer, which will be advanced past field
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error.  Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
+ *
+ * The result is a List of HbaToken structs, one for each token in the field,
  * or NIL if we reached EOL.
  */
 static List *
-next_field_expand(const char *filename, char **lineptr)
+next_field_expand(const char *filename, char **lineptr,
+				  int elevel, char **err_msg)
 {
 	char		buf[MAX_TOKEN];
 	bool		trailing_comma;
@@ -288,15 +344,18 @@ next_field_expand(const char *filename, char **lineptr)
 
 	do
 	{
-		if (!next_token(lineptr, buf, sizeof(buf), &initial_quote, &trailing_comma))
+		if (!next_token(lineptr, buf, sizeof(buf),
+						&initial_quote, &trailing_comma,
+						elevel, err_msg))
 			break;
 
 		/* Is this referencing a file? */
 		if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
-			tokens = tokenize_inc_file(tokens, filename, buf + 1);
+			tokens = tokenize_inc_file(tokens, filename, buf + 1,
+									   elevel, err_msg);
 		else
 			tokens = lappend(tokens, make_hba_token(buf, initial_quote));
-	} while (trailing_comma);
+	} while (trailing_comma && (*err_msg == NULL));
 
 	return tokens;
 }
@@ -307,13 +366,21 @@ next_field_expand(const char *filename, char **lineptr)
  *
  * Opens and tokenises a file included from another HBA config file with @,
  * and returns all values found therein as a flat list of HbaTokens.  If a
- * @-token is found, recursively expand it.  The given token list is used as
- * initial contents of list (so foo,bar,@baz does what you expect).
+ * @-token is found, recursively expand it.  The newly read tokens are
+ * appended to "tokens" (so that foo,bar,@baz does what you expect).
+ * All new tokens are allocated in caller's memory context.
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error.  Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
  */
 static List *
 tokenize_inc_file(List *tokens,
 				  const char *outer_filename,
-				  const char *inc_filename)
+				  const char *inc_filename,
+				  int elevel,
+				  char **err_msg)
 {
 	char	   *inc_fullname;
 	FILE	   *inc_file;
@@ -340,16 +407,20 @@ tokenize_inc_file(List *tokens,
 	inc_file = AllocateFile(inc_fullname, "r");
 	if (inc_file == NULL)
 	{
-		ereport(LOG,
+		int			save_errno = errno;
+
+		ereport(elevel,
 				(errcode_for_file_access(),
 				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
 						inc_filename, inc_fullname)));
+		*err_msg = psprintf("could not open secondary authentication file \"@%s\" as \"%s\": %s",
+							inc_filename, inc_fullname, strerror(save_errno));
 		pfree(inc_fullname);
 		return tokens;
 	}
 
 	/* There is possible recursion here if the file contains @ */
-	linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines);
+	linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, elevel);
 
 	FreeFile(inc_file);
 	pfree(inc_fullname);
@@ -360,6 +431,13 @@ tokenize_inc_file(List *tokens,
 		TokenizedLine *tok_line = (TokenizedLine *) lfirst(inc_line);
 		ListCell   *inc_field;
 
+		/* If any line has an error, propagate that up to caller */
+		if (tok_line->err_msg)
+		{
+			*err_msg = pstrdup(tok_line->err_msg);
+			break;
+		}
+
 		foreach(inc_field, tok_line->fields)
 		{
 			List	   *inc_tokens = lfirst(inc_field);
@@ -383,13 +461,20 @@ tokenize_inc_file(List *tokens,
  *
  * The output is a list of TokenizedLine structs; see struct definition above.
  *
- * filename must be the absolute path to the target file.
+ * 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)
+tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
 {
 	int			line_number = 1;
 	MemoryContext linecxt;
@@ -407,16 +492,32 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines)
 		char		rawline[MAX_LINE];
 		char	   *lineptr;
 		List	   *current_line = NIL;
+		char	   *err_msg = NULL;
 
 		if (!fgets(rawline, sizeof(rawline), file))
-			break;
+		{
+			int			save_errno = errno;
+
+			if (!ferror(file))
+				break;			/* normal EOF */
+			/* I/O error! */
+			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));
+			rawline[0] = '\0';
+		}
 		if (strlen(rawline) == MAX_LINE - 1)
+		{
 			/* Line too long! */
-			ereport(ERROR,
+			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 					 errmsg("authentication file line too long"),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_number, filename)));
+			err_msg = "authentication file line too long";
+		}
 
 		/* Strip trailing linebreak from rawline */
 		lineptr = rawline + strlen(rawline) - 1;
@@ -425,18 +526,19 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines)
 
 		/* Parse fields */
 		lineptr = rawline;
-		while (*lineptr)
+		while (*lineptr && err_msg == NULL)
 		{
 			List	   *current_field;
 
-			current_field = next_field_expand(filename, &lineptr);
+			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)
+		if (current_line != NIL || err_msg != NULL)
 		{
 			TokenizedLine *tok_line;
 
@@ -444,6 +546,7 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines)
 			tok_line->fields = current_line;
 			tok_line->line_num = line_number;
 			tok_line->raw_line = pstrdup(rawline);
+			tok_line->err_msg = err_msg;
 			*tok_lines = lappend(*tok_lines, tok_line);
 		}
 
@@ -746,6 +849,10 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
 
 /*
  * Macros used to check and report on invalid configuration options.
+ * On error: log a message at level elevel, set *err_msg, and exit the function.
+ * These macros are not as general-purpose as they look, because they know
+ * what the calling function's error-exit value is.
+ *
  * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
  *						 not supported.
  * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
@@ -754,44 +861,56 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
  * MANDATORY_AUTH_ARG  = check if a required option is set for an authentication method,
  *						 reporting error if it's not.
  */
-#define INVALID_AUTH_OPTION(optname, validmethods) do {\
-	ereport(LOG, \
+#define INVALID_AUTH_OPTION(optname, validmethods) \
+do { \
+	ereport(elevel, \
 			(errcode(ERRCODE_CONFIG_FILE_ERROR), \
 			 /* translator: the second %s is a list of auth methods */ \
 			 errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
 					optname, _(validmethods)), \
 			 errcontext("line %d of configuration file \"%s\"", \
 					line_num, HbaFileName))); \
+	*err_msg = psprintf("authentication option \"%s\" is only valid for authentication methods %s", \
+						optname, validmethods); \
 	return false; \
-} while (0);
+} while (0)
 
-#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
+#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
+do { \
 	if (hbaline->auth_method != methodval) \
 		INVALID_AUTH_OPTION(optname, validmethods); \
-} while (0);
+} while (0)
 
-#define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\
-	if (argvar == NULL) {\
-		ereport(LOG, \
+#define MANDATORY_AUTH_ARG(argvar, argname, authname) \
+do { \
+	if (argvar == NULL) { \
+		ereport(elevel, \
 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
 				 errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \
 						authname, argname), \
 				 errcontext("line %d of configuration file \"%s\"", \
 						line_num, HbaFileName))); \
+		*err_msg = psprintf("authentication method \"%s\" requires argument \"%s\" to be set", \
+							authname, argname); \
 		return NULL; \
 	} \
-} while (0);
+} while (0)
 
 /*
+ * Macros for handling pg_ident problems.
+ * Much as above, but currently the message level is hardwired as LOG
+ * and there is no provision for an err_msg string.
+ *
  * IDENT_FIELD_ABSENT:
- * Throw an error and exit the function if the given ident field ListCell is
+ * Log a message and exit the function if the given ident field ListCell is
  * not populated.
  *
  * IDENT_MULTI_VALUE:
- * Throw an error and exit the function if the given ident token List has more
+ * Log a message and exit the function if the given ident token List has more
  * than one element.
  */
-#define IDENT_FIELD_ABSENT(field) do {\
+#define IDENT_FIELD_ABSENT(field) \
+do { \
 	if (!field) { \
 		ereport(LOG, \
 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
@@ -799,9 +918,10 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
 						IdentFileName, line_num))); \
 		return NULL; \
 	} \
-} while (0);
+} while (0)
 
-#define IDENT_MULTI_VALUE(tokens) do {\
+#define IDENT_MULTI_VALUE(tokens) \
+do { \
 	if (tokens->length > 1) { \
 		ereport(LOG, \
 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
@@ -810,23 +930,26 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
 							line_num, IdentFileName))); \
 		return NULL; \
 	} \
-} while (0);
+} while (0)
 
 
 /*
  * Parse one tokenised line from the hba config file and store the result in a
  * HbaLine structure.
  *
- * Return NULL if parsing fails.
+ * 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.
  */
 static HbaLine *
-parse_hba_line(TokenizedLine *tok_line)
+parse_hba_line(TokenizedLine *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;
@@ -849,12 +972,13 @@ parse_hba_line(TokenizedLine *tok_line)
 	tokens = lfirst(field);
 	if (tokens->length > 1)
 	{
-		ereport(LOG,
+		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);
@@ -863,11 +987,12 @@ parse_hba_line(TokenizedLine *tok_line)
 #ifdef HAVE_UNIX_SOCKETS
 		parsedline->conntype = ctLocal;
 #else
-		ereport(LOG,
+		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
 	}
@@ -882,19 +1007,23 @@ parse_hba_line(TokenizedLine *tok_line)
 			/* Log a warning if SSL support is not active */
 #ifdef USE_SSL
 			if (!EnableSSL)
-				ereport(LOG,
+			{
+				ereport(elevel,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
 				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)));
+				*err_msg = "hostssl record cannot match because SSL is disabled";
+			}
 #else
-			ereport(LOG,
+			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 					 errmsg("hostssl record cannot match because SSL is not supported by this build"),
 			  errhint("Compile with --with-openssl to use SSL connections."),
 					 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
 		}
 		else if (token->string[4] == 'n')		/* "hostnossl" */
@@ -909,12 +1038,13 @@ parse_hba_line(TokenizedLine *tok_line)
 	}							/* record type */
 	else
 	{
-		ereport(LOG,
+		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;
 	}
 
@@ -922,11 +1052,12 @@ parse_hba_line(TokenizedLine *tok_line)
 	field = lnext(field);
 	if (!field)
 	{
-		ereport(LOG,
+		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;
 	}
 	parsedline->databases = NIL;
@@ -941,11 +1072,12 @@ parse_hba_line(TokenizedLine *tok_line)
 	field = lnext(field);
 	if (!field)
 	{
-		ereport(LOG,
+		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;
 	}
 	parsedline->roles = NIL;
@@ -962,22 +1094,24 @@ parse_hba_line(TokenizedLine *tok_line)
 		field = lnext(field);
 		if (!field)
 		{
-			ereport(LOG,
+			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(LOG,
+			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);
@@ -1027,12 +1161,14 @@ parse_hba_line(TokenizedLine *tok_line)
 				parsedline->hostname = str;
 			else
 			{
-				ereport(LOG,
+				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;
@@ -1045,24 +1181,28 @@ parse_hba_line(TokenizedLine *tok_line)
 			{
 				if (parsedline->hostname)
 				{
-					ereport(LOG,
+					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;
 				}
 
 				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
 										  parsedline->addr.ss_family) < 0)
 				{
-					ereport(LOG,
+					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;
 				}
 				pfree(str);
@@ -1074,22 +1214,24 @@ parse_hba_line(TokenizedLine *tok_line)
 				field = lnext(field);
 				if (!field)
 				{
-					ereport(LOG,
+					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(LOG,
+					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);
@@ -1098,12 +1240,14 @@ parse_hba_line(TokenizedLine *tok_line)
 										 &hints, &gai_result);
 				if (ret || !gai_result)
 				{
-					ereport(LOG,
+					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;
@@ -1115,11 +1259,12 @@ parse_hba_line(TokenizedLine *tok_line)
 
 				if (parsedline->addr.ss_family != parsedline->mask.ss_family)
 				{
-					ereport(LOG,
+					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;
 				}
 			}
@@ -1130,22 +1275,24 @@ parse_hba_line(TokenizedLine *tok_line)
 	field = lnext(field);
 	if (!field)
 	{
-		ereport(LOG,
+		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(LOG,
+		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);
@@ -1177,11 +1324,12 @@ parse_hba_line(TokenizedLine *tok_line)
 	{
 		if (Db_user_namespace)
 		{
-			ereport(LOG,
+			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;
 		}
 		parsedline->auth_method = uaMD5;
@@ -1214,23 +1362,27 @@ parse_hba_line(TokenizedLine *tok_line)
 		parsedline->auth_method = uaRADIUS;
 	else
 	{
-		ereport(LOG,
+		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;
 	}
 
 	if (unsupauth)
 	{
-		ereport(LOG,
+		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;
 	}
 
@@ -1246,22 +1398,24 @@ parse_hba_line(TokenizedLine *tok_line)
 	if (parsedline->conntype == ctLocal &&
 		parsedline->auth_method == uaGSS)
 	{
-		ereport(LOG,
+		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;
 	}
 
 	if (parsedline->conntype != ctLocal &&
 		parsedline->auth_method == uaPeer)
 	{
-		ereport(LOG,
+		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;
 	}
 
@@ -1274,11 +1428,12 @@ parse_hba_line(TokenizedLine *tok_line)
 	if (parsedline->conntype != ctHostSSL &&
 		parsedline->auth_method == uaCert)
 	{
-		ereport(LOG,
+		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;
 	}
 
@@ -1323,16 +1478,18 @@ parse_hba_line(TokenizedLine *tok_line)
 				/*
 				 * Got something that's not a name=value pair.
 				 */
-				ereport(LOG,
+				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;
 			}
 
 			*val++ = '\0';		/* str now holds "name", val holds "value" */
-			if (!parse_hba_auth_opt(str, val, parsedline, line_num))
+			if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
 				/* parse_hba_auth_opt already logged the error message */
 				return NULL;
 			pfree(str);
@@ -1360,21 +1517,23 @@ parse_hba_line(TokenizedLine *tok_line)
 				parsedline->ldapbindpasswd ||
 				parsedline->ldapsearchattribute)
 			{
-				ereport(LOG,
+				ereport(elevel,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
 						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"),
 						 errcontext("line %d of configuration file \"%s\"",
 									line_num, HbaFileName)));
+				*err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix";
 				return NULL;
 			}
 		}
 		else if (!parsedline->ldapbasedn)
 		{
-			ereport(LOG,
+			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;
 		}
 	}
@@ -1399,11 +1558,15 @@ parse_hba_line(TokenizedLine *tok_line)
 /*
  * 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.
+ * 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 bool
-parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
+parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
+				   int elevel, char **err_msg)
 {
+	int			line_num = hbaline->linenumber;
+
 #ifdef USE_LDAP
 	hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
 #endif
@@ -1422,11 +1585,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 	{
 		if (hbaline->conntype != ctHostSSL)
 		{
-			ereport(LOG,
+			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 (strcmp(val, "1") == 0)
@@ -1437,11 +1601,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 		{
 			if (hbaline->auth_method == uaCert)
 			{
-				ereport(LOG,
+				ereport(elevel,
 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
 						 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
 						 errcontext("line %d of configuration file \"%s\"",
 									line_num, HbaFileName)));
+				*err_msg = "clientcert can not be set to 0 when using \"cert\" authentication";
 				return false;
 			}
 			hbaline->clientcert = false;
@@ -1473,17 +1638,21 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 		rc = ldap_url_parse(val, &urldata);
 		if (rc != LDAP_SUCCESS)
 		{
-			ereport(LOG,
+			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)
 		{
-			ereport(LOG,
+			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;
 		}
@@ -1497,17 +1666,19 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 		hbaline->ldapscope = urldata->lud_scope;
 		if (urldata->lud_filter)
 		{
-			ereport(LOG,
+			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 					 errmsg("filters not supported in LDAP URLs")));
+			*err_msg = "filters not supported in LDAP URLs";
 			ldap_free_urldesc(urldata);
 			return false;
 		}
 		ldap_free_urldesc(urldata);
 #else							/* not OpenLDAP */
-		ereport(LOG,
+		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)
@@ -1529,11 +1700,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 		hbaline->ldapport = atoi(val);
 		if (hbaline->ldapport == 0)
 		{
-			ereport(LOG,
+			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;
 		}
 	}
@@ -1617,12 +1789,14 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 		ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
 		if (ret || !gai_result)
 		{
-			ereport(LOG,
+			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 					 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
 							val, gai_strerror(ret)),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
+			*err_msg = psprintf("could not translate RADIUS server name \"%s\" to address: %s",
+								val, gai_strerror(ret));
 			if (gai_result)
 				pg_freeaddrinfo_all(hints.ai_family, gai_result);
 			return false;
@@ -1636,11 +1810,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 		hbaline->radiusport = atoi(val);
 		if (hbaline->radiusport == 0)
 		{
-			ereport(LOG,
+			ereport(elevel,
 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
 					 errmsg("invalid RADIUS port number: \"%s\"", val),
 					 errcontext("line %d of configuration file \"%s\"",
 								line_num, HbaFileName)));
+			*err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
 			return false;
 		}
 	}
@@ -1656,12 +1831,14 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
 	}
 	else
 	{
-		ereport(LOG,
+		ereport(elevel,
 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
 				 errmsg("unrecognized authentication option name: \"%s\"",
 						name),
 				 errcontext("line %d of configuration file \"%s\"",
 							line_num, HbaFileName)));
+		*err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+							name);
 		return false;
 	}
 	return true;
@@ -1794,7 +1971,7 @@ load_hba(void)
 		return false;
 	}
 
-	linecxt = tokenize_file(HbaFileName, file, &hba_lines);
+	linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
 	FreeFile(file);
 
 	/* Now parse all the lines */
@@ -1808,21 +1985,22 @@ load_hba(void)
 		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
 		HbaLine    *newline;
 
-		if ((newline = parse_hba_line(tok_line)) == NULL)
+		/* don't parse lines that already have errors */
+		if (tok_line->err_msg != NULL)
 		{
-			/*
-			 * Parse error in the file, so indicate there's a problem.  NB: a
-			 * problem in a line will free the memory for all previous lines
-			 * as well!
-			 */
-			MemoryContextReset(hbacxt);
-			new_parsed_lines = NIL;
+			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 row. Error has already been reported in the
-			 * parsing function, so no need to log it here.
+			 * more than the first line.  Error has already been logged, no
+			 * need for more chatter here.
 			 */
 			continue;
 		}
@@ -1865,10 +2043,418 @@ load_hba(void)
 }
 
 /*
+ * This macro specifies the maximum number of authentication options
+ * that are possible with any given authentication method that is supported.
+ * Currently LDAP supports 10, so the macro value is well above the most any
+ * method needs.
+ */
+#define MAX_HBA_OPTIONS 12
+
+/*
+ * 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)
+		options[noptions++] =
+			CStringGetTextDatum("clientcert=true");
+
+	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->ldapscope)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
+	}
+
+	if (hba->auth_method == uaRADIUS)
+	{
+		if (hba->radiusserver)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiusserver=%s", hba->radiusserver));
+
+		if (hba->radiussecret)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiussecret=%s", hba->radiussecret));
+
+		if (hba->radiusidentifier)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiusidentifier=%s", hba->radiusidentifier));
+
+		if (hba->radiusport)
+			options[noptions++] =
+				CStringGetTextDatum(psprintf("radiusport=%d", hba->radiusport));
+	}
+
+	Assert(noptions <= MAX_HBA_OPTIONS);
+
+	if (noptions > 0)
+		return construct_array(options, noptions, TEXTOID, -1, false, 'i');
+	else
+		return NULL;
+}
+
+/* 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;
+		}
+		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
+				{
+					if (pg_getnameinfo_all(&hba->addr, sizeof(hba->addr),
+										   buffer, sizeof(buffer),
+										   NULL, 0,
+										   NI_NUMERICHOST) == 0)
+					{
+						clean_ipv6_addr(hba->addr.ss_family, buffer);
+						addrstr = pstrdup(buffer);
+					}
+					if (pg_getnameinfo_all(&hba->mask, sizeof(hba->mask),
+										   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;
+
+		/*
+		 * Make sure UserAuthName[] is kept up to date with the UserAuth enum.
+		 */
+		StaticAssertStmt(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
+		    "UserAuthName[] must include all UserAuth authentication names");
+
+		/* auth_method */
+		values[index++] = CStringGetTextDatum(UserAuthName[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_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)
+	{
+		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);
+	}
+
+	/* 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;
+
+	/*
+	 * 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();
+}
+
+
+/*
  * Parse one tokenised line from the ident config file and store the result in
  * an IdentLine structure.
  *
- * Return NULL if parsing fails.
+ * 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.
@@ -2170,7 +2756,7 @@ load_ident(void)
 		return false;
 	}
 
-	linecxt = tokenize_file(IdentFileName, file, &ident_lines);
+	linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
 	FreeFile(file);
 
 	/* Now parse all the lines */
@@ -2183,26 +2769,22 @@ load_ident(void)
 	{
 		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);
 
+		/* don't parse lines that already have errors */
+		if (tok_line->err_msg != NULL)
+		{
+			ok = false;
+			continue;
+		}
+
 		if ((newline = parse_ident_line(tok_line)) == NULL)
 		{
-			/*
-			 * Parse error in the file, so indicate there's a problem.  Free
-			 * all the memory and regular expressions of lines parsed so far.
-			 */
-			foreach(parsed_line_cell, new_parsed_lines)
-			{
-				newline = (IdentLine *) lfirst(parsed_line_cell);
-				if (newline->ident_user[0] == '/')
-					pg_regfree(&newline->re);
-			}
-			MemoryContextReset(ident_context);
-			new_parsed_lines = NIL;
+			/* 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 row. Error has already been reported in the
-			 * parsing function, so no need to log it here.
+			 * more than the first line.  Error has already been logged, no
+			 * need for more chatter here.
 			 */
 			continue;
 		}
@@ -2216,7 +2798,11 @@ load_ident(void)
 
 	if (!ok)
 	{
-		/* File contained one or more errors, so bail out */
+		/*
+		 * 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);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 31c828a..05652e8 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -3076,6 +3076,8 @@ DATA(insert OID = 2084 (  pg_show_all_settings	PGNSP PGUID 12 1 1000 0 0 f f f f
 DESCR("SHOW ALL as a function");
 DATA(insert OID = 3329 (  pg_show_all_file_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,23,23,25,25,16,25}" "{o,o,o,o,o,o,o}" "{sourcefile,sourceline,seqno,name,setting,applied,error}" _null_ _null_ show_all_file_settings _null_ _null_ _null_ ));
 DESCR("show config file settings");
+DATA(insert OID = 3401 (  pg_hba_file_rules PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{23,25,1009,1009,25,25,25,1009,25}" "{o,o,o,o,o,o,o,o,o}" "{line_number,type,database,user_name,address,netmask,auth_method,options,error}" _null_ _null_ pg_hba_file_rules _null_ _null_ _null_ ));
+DESCR("show pg_hba.conf rules");
 DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ _null_ pg_lock_status _null_ _null_ _null_ ));
 DESCR("view system lock information");
 DATA(insert OID = 2561 (  pg_blocking_pids PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 1007 "23" _null_ _null_ _null_ _null_ _null_ pg_blocking_pids _null_ _null_ _null_ ));
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index dc7d257..bf945c5 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -16,10 +16,16 @@
 #include "regex/regex.h"
 
 
+/*
+ * The following enum represents the authentication methods that
+ * are supported by PostgreSQL.
+ *
+ * Note: keep this in sync with the UserAuthName array in hba.c.
+ */
 typedef enum UserAuth
 {
 	uaReject,
-	uaImplicitReject,
+	uaImplicitReject,			/* Not a user-visible option */
 	uaTrust,
 	uaIdent,
 	uaPassword,
@@ -34,6 +40,8 @@ typedef enum UserAuth
 	uaPeer
 } UserAuth;
 
+#define USER_AUTH_LAST uaPeer
+
 typedef enum IPCompareMethod
 {
 	ipCmpMask,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 60abcad..de5ae00 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1338,6 +1338,16 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_hba_file_rules| SELECT a.line_number,
+    a.type,
+    a.database,
+    a.user_name,
+    a.address,
+    a.netmask,
+    a.auth_method,
+    a.options,
+    a.error
+   FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     i.relname AS indexname,
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 852a7c3..d48abd7 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -39,6 +39,13 @@ select count(*) >= 0 as ok from pg_file_settings;
  t
 (1 row)
 
+-- There will surely be at least one rule
+select count(*) > 0 as ok from pg_hba_file_rules;
+ ok 
+----
+ t
+(1 row)
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;
  ok 
diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql
index 0941b6b..28e412b 100644
--- a/src/test/regress/sql/sysviews.sql
+++ b/src/test/regress/sql/sysviews.sql
@@ -20,6 +20,9 @@ select count(*) = 0 as ok from pg_cursors;
 
 select count(*) >= 0 as ok from pg_file_settings;
 
+-- There will surely be at least one rule
+select count(*) > 0 as ok from pg_hba_file_rules;
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;
 
