I wrote:
> I spent awhile hacking on this, and made a lot of things better, but
> I'm still very unhappy about the state of the comments.

I made another pass over this, working on the comments and the docs,
and changing the view name to "pg_hba_file_rules".  I think this version
is committable if people are satisfied with that name.

One loose end is what to do about testing.  I did not much like the
proposed TAP tests.  We could just put "select count(*) > 0 from
pg_hba_file_rules" into the main regression tests, which would provide
some code coverage there, if not very much guarantee that what the view
outputs is sane.

                        regards, tom lane

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,7814 ****
--- 7809,7819 ----
       </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,8413 ****
--- 8413,8526 ----
  
   </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
*************** hostnossl  <replaceable>database</replac
*** 597,602 ****
--- 597,620 ----
     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
*************** CREATE VIEW pg_file_settings AS
*** 459,464 ****
--- 459,470 ----
  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..7a0f1ce 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,39 ****
--- 25,44 ----
  #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"
*************** typedef struct HbaToken
*** 80,91 ****
--- 85,99 ----
   * 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;
  
  /*
*************** static MemoryContext parsed_hba_context 
*** 106,118 ****
  static List *parsed_ident_lines = NIL;
  static MemoryContext parsed_ident_context = NULL;
  
  
  static MemoryContext tokenize_file(const char *filename, FILE *file,
! 			  List **tok_lines);
  static List *tokenize_inc_file(List *tokens, const char *outer_filename,
! 				  const char *inc_filename);
  static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
! 				   int line_num);
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
--- 114,155 ----
  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, 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);
! 
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
*************** pg_isblank(const char c)
*** 126,157 ****
  
  
  /*
!  * 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).
   *
-  * 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.)
   *
   * 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.
   */
  static bool
! next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
! 		   bool *terminating_comma)
  {
  	int			c;
  	char	   *start_buf = buf;
--- 163,199 ----
  
  
  /*
!  * 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.
   *
   * 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).
!  *
!  * 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.
!  * 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,
! 		   int elevel, char **err_msg)
  {
  	int			c;
  	char	   *start_buf = buf;
*************** next_token(char **lineptr, char *buf, in
*** 197,210 ****
  		if (buf >= end_buf)
  		{
  			*buf = '\0';
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  			   errmsg("authentication file token too long, skipping: \"%s\"",
  					  start_buf)));
  			/* Discard remainder of line */
  			while ((c = (*(*lineptr)++)) != '\0' && c != '\n')
  				;
! 			break;
  		}
  
  		/* we do not pass back the comma in the token */
--- 239,253 ----
  		if (buf >= end_buf)
  		{
  			*buf = '\0';
! 			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')
  				;
! 			return false;
  		}
  
  		/* we do not pass back the comma in the token */
*************** next_token(char **lineptr, char *buf, in
*** 245,257 ****
  	return (saw_quote || buf > start_buf);
  }
  
  static HbaToken *
! make_hba_token(char *token, bool quoted)
  {
  	HbaToken   *hbatoken;
  	int			toklen;
  
  	toklen = strlen(token);
  	hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1);
  	hbatoken->string = (char *) hbatoken + sizeof(HbaToken);
  	hbatoken->quoted = quoted;
--- 288,304 ----
  	return (saw_quote || buf > start_buf);
  }
  
+ /*
+  * Construct a palloc'd HbaToken struct, copying the given string.
+  */
  static HbaToken *
! 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;
*************** copy_hba_token(HbaToken *in)
*** 275,285 ****
  /*
   * 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,
   * or NIL if we reached EOL.
   */
  static List *
! next_field_expand(const char *filename, char **lineptr)
  {
  	char		buf[MAX_TOKEN];
  	bool		trailing_comma;
--- 322,341 ----
  /*
   * Tokenize one HBA field from a line, handling file inclusion and comma lists.
   *
!  * 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,
! 				  int elevel, char **err_msg)
  {
  	char		buf[MAX_TOKEN];
  	bool		trailing_comma;
*************** next_field_expand(const char *filename, 
*** 288,302 ****
  
  	do
  	{
! 		if (!next_token(lineptr, buf, sizeof(buf), &initial_quote, &trailing_comma))
  			break;
  
  		/* Is this referencing a file? */
  		if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! 			tokens = tokenize_inc_file(tokens, filename, buf + 1);
  		else
  			tokens = lappend(tokens, make_hba_token(buf, initial_quote));
! 	} while (trailing_comma);
  
  	return tokens;
  }
--- 344,361 ----
  
  	do
  	{
! 		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,
! 									   elevel, err_msg);
  		else
  			tokens = lappend(tokens, make_hba_token(buf, initial_quote));
! 	} while (trailing_comma && (*err_msg == NULL));
  
  	return tokens;
  }
*************** next_field_expand(const char *filename, 
*** 307,319 ****
   *
   * 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).
   */
  static List *
  tokenize_inc_file(List *tokens,
  				  const char *outer_filename,
! 				  const char *inc_filename)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
--- 366,386 ----
   *
   * 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 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,
! 				  int elevel,
! 				  char **err_msg)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
*************** tokenize_inc_file(List *tokens,
*** 340,355 ****
  	inc_file = AllocateFile(inc_fullname, "r");
  	if (inc_file == NULL)
  	{
! 		ereport(LOG,
  				(errcode_for_file_access(),
  				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
  						inc_filename, inc_fullname)));
  		pfree(inc_fullname);
  		return tokens;
  	}
  
  	/* There is possible recursion here if the file contains @ */
! 	linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines);
  
  	FreeFile(inc_file);
  	pfree(inc_fullname);
--- 407,426 ----
  	inc_file = AllocateFile(inc_fullname, "r");
  	if (inc_file == NULL)
  	{
! 		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, elevel);
  
  	FreeFile(inc_file);
  	pfree(inc_fullname);
*************** tokenize_inc_file(List *tokens,
*** 360,365 ****
--- 431,443 ----
  		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);
*************** tokenize_inc_file(List *tokens,
*** 383,395 ****
   *
   * The output is a list of TokenizedLine structs; see struct definition above.
   *
!  * filename must be the absolute path to the target file.
   *
   * 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			line_number = 1;
  	MemoryContext linecxt;
--- 461,480 ----
   *
   * 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;
  	MemoryContext linecxt;
*************** tokenize_file(const char *filename, FILE
*** 407,422 ****
  		char		rawline[MAX_LINE];
  		char	   *lineptr;
  		List	   *current_line = NIL;
  
  		if (!fgets(rawline, sizeof(rawline), file))
! 			break;
  		if (strlen(rawline) == MAX_LINE - 1)
  			/* Line too long! */
! 			ereport(ERROR,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("authentication file line too long"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_number, filename)));
  
  		/* Strip trailing linebreak from rawline */
  		lineptr = rawline + strlen(rawline) - 1;
--- 492,523 ----
  		char		rawline[MAX_LINE];
  		char	   *lineptr;
  		List	   *current_line = NIL;
+ 		char	   *err_msg = NULL;
  
  		if (!fgets(rawline, sizeof(rawline), file))
! 		{
! 			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(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;
*************** tokenize_file(const char *filename, FILE
*** 425,442 ****
  
  		/* Parse fields */
  		lineptr = rawline;
! 		while (*lineptr)
  		{
  			List	   *current_field;
  
! 			current_field = next_field_expand(filename, &lineptr);
  			/* 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)
  		{
  			TokenizedLine *tok_line;
  
--- 526,544 ----
  
  		/* Parse fields */
  		lineptr = rawline;
! 		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;
  
*************** tokenize_file(const char *filename, FILE
*** 444,449 ****
--- 546,552 ----
  			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);
  		}
  
*************** check_same_host_or_net(SockAddr *raddr, 
*** 746,751 ****
--- 849,858 ----
  
  /*
   * 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
*************** check_same_host_or_net(SockAddr *raddr, 
*** 754,797 ****
   * 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, \
  			(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))); \
  	return false; \
! } while (0);
  
! #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
  	if (hbaline->auth_method != methodval) \
  		INVALID_AUTH_OPTION(optname, validmethods); \
! } while (0);
  
! #define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\
! 	if (argvar == NULL) {\
! 		ereport(LOG, \
  				(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))); \
  		return NULL; \
  	} \
! } while (0);
  
  /*
   * IDENT_FIELD_ABSENT:
!  * Throw an error 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
   * than one element.
   */
! #define IDENT_FIELD_ABSENT(field) do {\
  	if (!field) { \
  		ereport(LOG, \
  				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
--- 861,916 ----
   * 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(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)
  
! #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
! do { \
  	if (hbaline->auth_method != methodval) \
  		INVALID_AUTH_OPTION(optname, validmethods); \
! } while (0)
  
! #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)
  
  /*
+  * 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:
!  * Log a message and exit the function if the given ident field ListCell is
   * not populated.
   *
   * IDENT_MULTI_VALUE:
!  * Log a message and exit the function if the given ident token List has more
   * than one element.
   */
! #define IDENT_FIELD_ABSENT(field) \
! do { \
  	if (!field) { \
  		ereport(LOG, \
  				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
*************** check_same_host_or_net(SockAddr *raddr, 
*** 799,807 ****
  						IdentFileName, line_num))); \
  		return NULL; \
  	} \
! } while (0);
  
! #define IDENT_MULTI_VALUE(tokens) do {\
  	if (tokens->length > 1) { \
  		ereport(LOG, \
  				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
--- 918,927 ----
  						IdentFileName, line_num))); \
  		return NULL; \
  	} \
! } while (0)
  
! #define IDENT_MULTI_VALUE(tokens) \
! do { \
  	if (tokens->length > 1) { \
  		ereport(LOG, \
  				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
*************** check_same_host_or_net(SockAddr *raddr, 
*** 810,832 ****
  							line_num, IdentFileName))); \
  		return NULL; \
  	} \
! } while (0);
  
  
  /*
   * Parse one tokenised line from the hba config file and store the result in a
   * HbaLine structure.
   *
!  * Return NULL if parsing fails.
   *
   * 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)
  {
  	int			line_num = tok_line->line_num;
  	char	   *str;
  	struct addrinfo *gai_result;
  	struct addrinfo hints;
--- 930,955 ----
  							line_num, IdentFileName))); \
  		return NULL; \
  	} \
! } 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.
   */
  static HbaLine *
! 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;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 849,860 ****
  	tokens = lfirst(field);
  	if (tokens->length > 1)
  	{
! 		ereport(LOG,
  				(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)));
  		return NULL;
  	}
  	token = linitial(tokens);
--- 972,984 ----
  	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);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 863,873 ****
  #ifdef HAVE_UNIX_SOCKETS
  		parsedline->conntype = ctLocal;
  #else
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("local connections are not supported by this build"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  #endif
  	}
--- 987,998 ----
  #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
  	}
*************** parse_hba_line(TokenizedLine *tok_line)
*** 882,900 ****
  			/* Log a warning if SSL support is not active */
  #ifdef USE_SSL
  			if (!EnableSSL)
! 				ereport(LOG,
  						(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)));
  #else
! 			ereport(LOG,
  					(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)));
  #endif
  		}
  		else if (token->string[4] == 'n')		/* "hostnossl" */
--- 1007,1029 ----
  			/* Log a warning if SSL support is not active */
  #ifdef USE_SSL
  			if (!EnableSSL)
! 			{
! 				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(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" */
*************** parse_hba_line(TokenizedLine *tok_line)
*** 909,920 ****
  	}							/* record type */
  	else
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid connection type \"%s\"",
  						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  
--- 1038,1050 ----
  	}							/* record type */
  	else
  	{
! 		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;
  	}
  
*************** parse_hba_line(TokenizedLine *tok_line)
*** 922,932 ****
  	field = lnext(field);
  	if (!field)
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before database specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  	parsedline->databases = NIL;
--- 1052,1063 ----
  	field = lnext(field);
  	if (!field)
  	{
! 		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;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 941,951 ****
  	field = lnext(field);
  	if (!field)
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before role specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  	parsedline->roles = NIL;
--- 1072,1083 ----
  	field = lnext(field);
  	if (!field)
  	{
! 		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;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 962,983 ****
  		field = lnext(field);
  		if (!field)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("end-of-line before IP address specification"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
  			return NULL;
  		}
  		tokens = lfirst(field);
  		if (tokens->length > 1)
  		{
! 			ereport(LOG,
  					(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)));
  			return NULL;
  		}
  		token = linitial(tokens);
--- 1094,1117 ----
  		field = lnext(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);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1027,1038 ****
  				parsedline->hostname = str;
  			else
  			{
! 				ereport(LOG,
  						(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)));
  				if (gai_result)
  					pg_freeaddrinfo_all(hints.ai_family, gai_result);
  				return NULL;
--- 1161,1174 ----
  				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;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1045,1068 ****
  			{
  				if (parsedline->hostname)
  				{
! 					ereport(LOG,
  							(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)));
  					return NULL;
  				}
  
  				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
  										  parsedline->addr.ss_family) < 0)
  				{
! 					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid CIDR mask in address \"%s\"",
  									token->string),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					return NULL;
  				}
  				pfree(str);
--- 1181,1208 ----
  			{
  				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;
  				}
  
  				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;
  				}
  				pfree(str);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1074,1095 ****
  				field = lnext(field);
  				if (!field)
  				{
! 					ereport(LOG,
  							(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)));
  					return NULL;
  				}
  				tokens = lfirst(field);
  				if (tokens->length > 1)
  				{
! 					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("multiple values specified for netmask"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					return NULL;
  				}
  				token = linitial(tokens);
--- 1214,1237 ----
  				field = lnext(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);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1098,1109 ****
  										 &hints, &gai_result);
  				if (ret || !gai_result)
  				{
! 					ereport(LOG,
  							(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)));
  					if (gai_result)
  						pg_freeaddrinfo_all(hints.ai_family, gai_result);
  					return NULL;
--- 1240,1253 ----
  										 &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;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1115,1125 ****
  
  				if (parsedline->addr.ss_family != parsedline->mask.ss_family)
  				{
! 					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("IP address and mask do not match"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					return NULL;
  				}
  			}
--- 1259,1270 ----
  
  				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;
  				}
  			}
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1130,1151 ****
  	field = lnext(field);
  	if (!field)
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before authentication method"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  	tokens = lfirst(field);
  	if (tokens->length > 1)
  	{
! 		ereport(LOG,
  				(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)));
  		return NULL;
  	}
  	token = linitial(tokens);
--- 1275,1298 ----
  	field = lnext(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);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1177,1187 ****
  	{
  		if (Db_user_namespace)
  		{
! 			ereport(LOG,
  					(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)));
  			return NULL;
  		}
  		parsedline->auth_method = uaMD5;
--- 1324,1335 ----
  	{
  		if (Db_user_namespace)
  		{
! 			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;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1214,1236 ****
  		parsedline->auth_method = uaRADIUS;
  	else
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\"",
  						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  
  	if (unsupauth)
  	{
! 		ereport(LOG,
  				(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)));
  		return NULL;
  	}
  
--- 1362,1388 ----
  		parsedline->auth_method = uaRADIUS;
  	else
  	{
! 		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(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;
  	}
  
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1246,1267 ****
  	if (parsedline->conntype == ctLocal &&
  		parsedline->auth_method == uaGSS)
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  		   errmsg("gssapi authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  
  	if (parsedline->conntype != ctLocal &&
  		parsedline->auth_method == uaPeer)
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  			errmsg("peer authentication is only supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  
--- 1398,1421 ----
  	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;
  	}
  
  	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;
  	}
  
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1274,1284 ****
  	if (parsedline->conntype != ctHostSSL &&
  		parsedline->auth_method == uaCert)
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("cert authentication is only supported on hostssl connections"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return NULL;
  	}
  
--- 1428,1439 ----
  	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;
  	}
  
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1323,1338 ****
  				/*
  				 * Got something that's not a name=value pair.
  				 */
! 				ereport(LOG,
  						(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)));
  				return NULL;
  			}
  
  			*val++ = '\0';		/* str now holds "name", val holds "value" */
! 			if (!parse_hba_auth_opt(str, val, parsedline, line_num))
  				/* parse_hba_auth_opt already logged the error message */
  				return NULL;
  			pfree(str);
--- 1478,1495 ----
  				/*
  				 * 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;
  			}
  
  			*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);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1360,1380 ****
  				parsedline->ldapbindpasswd ||
  				parsedline->ldapsearchattribute)
  			{
! 				ereport(LOG,
  						(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)));
  				return NULL;
  			}
  		}
  		else if (!parsedline->ldapbasedn)
  		{
! 			ereport(LOG,
  					(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)));
  			return NULL;
  		}
  	}
--- 1517,1539 ----
  				parsedline->ldapbindpasswd ||
  				parsedline->ldapsearchattribute)
  			{
! 				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(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;
  		}
  	}
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1399,1409 ****
  /*
   * 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.
   */
  static bool
! parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
  {
  #ifdef USE_LDAP
  	hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
  #endif
--- 1558,1572 ----
  /*
   * 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 bool
! 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
*************** parse_hba_auth_opt(char *name, char *val
*** 1422,1432 ****
  	{
  		if (hbaline->conntype != ctHostSSL)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  			errmsg("clientcert can only be configured for \"hostssl\" rows"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
  			return false;
  		}
  		if (strcmp(val, "1") == 0)
--- 1585,1596 ----
  	{
  		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 (strcmp(val, "1") == 0)
*************** parse_hba_auth_opt(char *name, char *val
*** 1437,1447 ****
  		{
  			if (hbaline->auth_method == uaCert)
  			{
! 				ereport(LOG,
  						(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)));
  				return false;
  			}
  			hbaline->clientcert = false;
--- 1601,1612 ----
  		{
  			if (hbaline->auth_method == uaCert)
  			{
! 				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;
*************** parse_hba_auth_opt(char *name, char *val
*** 1473,1489 ****
  		rc = ldap_url_parse(val, &urldata);
  		if (rc != LDAP_SUCCESS)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
  			return false;
  		}
  
  		if (strcmp(urldata->lud_scheme, "ldap") != 0)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  			errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
  			ldap_free_urldesc(urldata);
  			return false;
  		}
--- 1638,1658 ----
  		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)
  		{
! 			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;
  		}
*************** parse_hba_auth_opt(char *name, char *val
*** 1497,1513 ****
  		hbaline->ldapscope = urldata->lud_scope;
  		if (urldata->lud_filter)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("filters not supported in LDAP URLs")));
  			ldap_free_urldesc(urldata);
  			return false;
  		}
  		ldap_free_urldesc(urldata);
  #else							/* not OpenLDAP */
! 		ereport(LOG,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("LDAP URLs not supported on this platform")));
  #endif   /* not OpenLDAP */
  	}
  	else if (strcmp(name, "ldaptls") == 0)
--- 1666,1684 ----
  		hbaline->ldapscope = urldata->lud_scope;
  		if (urldata->lud_filter)
  		{
! 			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(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)
*************** parse_hba_auth_opt(char *name, char *val
*** 1529,1539 ****
  		hbaline->ldapport = atoi(val);
  		if (hbaline->ldapport == 0)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("invalid LDAP port number: \"%s\"", val),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
  			return false;
  		}
  	}
--- 1700,1711 ----
  		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;
  		}
  	}
*************** parse_hba_auth_opt(char *name, char *val
*** 1617,1628 ****
  		ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
  		if (ret || !gai_result)
  		{
! 			ereport(LOG,
  					(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)));
  			if (gai_result)
  				pg_freeaddrinfo_all(hints.ai_family, gai_result);
  			return false;
--- 1789,1802 ----
  		ret = pg_getaddrinfo_all(val, 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",
  							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;
*************** parse_hba_auth_opt(char *name, char *val
*** 1636,1646 ****
  		hbaline->radiusport = atoi(val);
  		if (hbaline->radiusport == 0)
  		{
! 			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("invalid RADIUS port number: \"%s\"", val),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
  			return false;
  		}
  	}
--- 1810,1821 ----
  		hbaline->radiusport = atoi(val);
  		if (hbaline->radiusport == 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)));
+ 			*err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
  			return false;
  		}
  	}
*************** parse_hba_auth_opt(char *name, char *val
*** 1656,1667 ****
  	}
  	else
  	{
! 		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("unrecognized authentication option name: \"%s\"",
  						name),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
  		return false;
  	}
  	return true;
--- 1831,1844 ----
  	}
  	else
  	{
! 		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;
*************** load_hba(void)
*** 1794,1800 ****
  		return false;
  	}
  
! 	linecxt = tokenize_file(HbaFileName, file, &hba_lines);
  	FreeFile(file);
  
  	/* Now parse all the lines */
--- 1971,1977 ----
  		return false;
  	}
  
! 	linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
  	FreeFile(file);
  
  	/* Now parse all the lines */
*************** load_hba(void)
*** 1808,1828 ****
  		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
  		HbaLine    *newline;
  
! 		if ((newline = parse_hba_line(tok_line)) == 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;
  
  			/*
  			 * 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.
  			 */
  			continue;
  		}
--- 1985,2006 ----
  		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;
  		}
*************** load_hba(void)
*** 1865,1874 ****
  }
  
  /*
   * Parse one tokenised line from the ident config file and store the result in
   * an IdentLine structure.
   *
!  * Return NULL if parsing fails.
   *
   * If ident_user is a regular expression (ie. begins with a slash), it is
   * compiled and stored in IdentLine structure.
--- 2043,2454 ----
  }
  
  /*
+  * 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;
+ 
+ 		/* 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.
   *
!  * 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.
*************** load_ident(void)
*** 2170,2176 ****
  		return false;
  	}
  
! 	linecxt = tokenize_file(IdentFileName, file, &ident_lines);
  	FreeFile(file);
  
  	/* Now parse all the lines */
--- 2750,2756 ----
  		return false;
  	}
  
! 	linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
  	FreeFile(file);
  
  	/* Now parse all the lines */
*************** load_ident(void)
*** 2183,2208 ****
  	{
  		TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);
  
  		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;
  			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.
  			 */
  			continue;
  		}
--- 2763,2784 ----
  	{
  		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; 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;
  		}
*************** load_ident(void)
*** 2216,2222 ****
  
  	if (!ok)
  	{
! 		/* File contained one or more errors, so bail out */
  		foreach(parsed_line_cell, new_parsed_lines)
  		{
  			newline = (IdentLine *) lfirst(parsed_line_cell);
--- 2792,2802 ----
  
  	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);
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
*************** DATA(insert OID = 2084 (  pg_show_all_se
*** 3076,3081 ****
--- 3076,3083 ----
  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..893767f 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 16,25 ****
  #include "regex/regex.h"
  
  
  typedef enum UserAuth
  {
  	uaReject,
! 	uaImplicitReject,
  	uaTrust,
  	uaIdent,
  	uaPassword,
--- 16,31 ----
  #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,			/* Not a user-visible option */
  	uaTrust,
  	uaIdent,
  	uaPassword,
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
*************** pg_group| SELECT pg_authid.rolname AS gr
*** 1338,1343 ****
--- 1338,1353 ----
            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,
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to