Haribabu Kommi <kommi.harib...@gmail.com> writes:
> [ pg_hba_rules_13.patch ]

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.  You changed
the APIs of a bunch of functions, often into fairly subtle things,
and you did not touch even one of their API-specification comments.
As an example, next_token() now needs something like

"On error, log a message at ereport level elevel and set *err_msg to
an error string.  Note that the return value might be either true or
false after an error; *err_msg must be checked to determine that.
Hence, *err_msg had better be NULL on entry, or you won't be able
to tell."

Having to write such a thing might even convince you that you should
try a little harder to make the behavior less confusing.  Just adding
arguments, and not changing the result-value specification, is not
necessarily the best way to do this.

I haven't looked at the docs yet.

I'm still not very happy about the choice of view name ...

                        regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 086fafc..3f4724c 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7804,7809 ****
--- 7804,7814 ----
       </row>
  
       <row>
+       <entry><link linkend="view-pg-hba-rules"><structname>pg_hba_rules</structname></link></entry>
+       <entry>summary of client authentication configuration file contents</entry>
+      </row>
+ 
+      <row>
        <entry><link linkend="view-pg-group"><structname>pg_group</structname></link></entry>
        <entry>groups of database users</entry>
       </row>
***************
*** 8352,8357 ****
--- 8357,8481 ----
  
  </sect1>
  
+  <sect1 id="view-pg-hba-rules">
+   <title><structname>pg_hba_rules</structname></title>
+ 
+   <indexterm zone="view-pg-hba-rules">
+    <primary>pg_hba_rules</primary>
+   </indexterm>
+ 
+   <para>
+    The view <structname>pg_hba_rules</structname> provides a summary of
+    the contents of the client authentication configuration file.  A row 
+    appears in this view for each entry appearing in the file, with annotations
+    indicating whether the rule could be applied successfully.
+   </para>
+ 
+   <table>
+    <title><structname>pg_hba_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 the client authentication rule in
+       pg_hba.conf file
+      </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 names</entry>
+     </row>
+     <row>
+      <entry><structfield>user_name</structfield></entry>
+      <entry><structfield>text[]</structfield></entry>
+      <entry>List of user names</entry>
+     </row>
+     <row>
+      <entry><structfield>address</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>
+       Address specifies the set of hosts the record matches.
+       It can be a host name, or it is made up of an IP address
+       or keywords such as (<literal>all</literal>, 
+       <literal>samehost</literal> and <literal>samenet</literal>).
+      </entry>
+     </row>
+     <row>
+      <entry><structfield>netmask</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>Address mask if exist</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>Configuration options set for authentication method</entry>
+     </row>
+     <row>
+      <entry><structfield>error</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>
+       If not null, an error message indicates why this
+       rule could not be loaded.
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+   </table>
+ 
+   <para>
+    <structfield>error</structfield> field, if not NULL, describes problem
+    in the rule on the line <structfield>line_number</structfield>.
+    Following is the sample output of the view.
+   </para>
+   
+ <programlisting>
+ SELECT line_number, type, database, user_name, auth_method FROM pg_hba_rules;
+ </programlisting>
+ 
+ <screen>
+  line_number | type  |  database  | user_name  |   address    | auth_method 
+ -------------+-------+------------+------------+--------------+-------------
+           84 | local | {all}      | {all}      |              | trust
+           86 | host  | {sameuser} | {postgres} | all          | trust
+           88 | host  | {postgres} | {postgres} | ::1          | trust
+          111 | host  | {all}      | {all}      | 127.0.0.1    | trust
+          121 | host  | {all}      | {all}      | localhost    | trust
+          128 | host  | {postgres} | {all}      | samenet      | ident
+          134 | host  | {postgres} | {all}      | samehost     | md5
+          140 | host  | {db1,db2}  | {all}      | .example.com | md5
+          149 | host  | {test}     | {test}     | 192.168.54.1 | reject
+          159 | host  | {all}      | {+support} | 192.168.0.0  | ident
+          169 | local | {sameuser} | {all}      |              | md5
+ (11 rows)
+ </screen>
+ 
+   <para>
+    See <xref linkend="client-authentication"> for more information about the various
+    ways to change client authentication configuration.
+   </para>
+  </sect1>
+ 
   <sect1 id="view-pg-group">
    <title><structname>pg_group</structname></title>
  
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index dda5891..f20486c 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
***************
*** 54,59 ****
--- 54,66 ----
    database user names and OS user names.
   </para>
  
+  <para>
+   The system view
+   <link linkend="view-pg-hba-rules"><structname>pg_hba_rules</structname></link>
+   can be helpful for pre-testing changes to the client authentication configuration file, or for
+   diagnosing problems if loading of file did not have the desired effects.
+  </para>
+ 
   <sect1 id="auth-pg-hba-conf">
    <title>The <filename>pg_hba.conf</filename> File</title>
  
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4dfedf8..d920a72 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_rules AS
+    SELECT * FROM pg_hba_rules() AS A;
+ 
+ REVOKE ALL on pg_hba_rules FROM PUBLIC;
+ REVOKE EXECUTE ON FUNCTION pg_hba_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..289bd9d 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,39 ****
--- 25,46 ----
  #include <arpa/inet.h>
  #include <unistd.h>
  
+ #include "access/htup_details.h"
+ #include "catalog/objectaddress.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 "storage/ipc.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 ****
--- 87,101 ----
   * 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,
--- 116,157 ----
  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(Tuplestorestate *tuple_store, TupleDesc tupdesc);
! 
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
*************** pg_isblank(const char c)
*** 151,157 ****
   */
  static bool
  next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
! 		   bool *terminating_comma)
  {
  	int			c;
  	char	   *start_buf = buf;
--- 190,196 ----
   */
  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,206 ****
  		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')
  				;
--- 236,246 ----
  		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')
  				;
*************** copy_hba_token(HbaToken *in)
*** 279,285 ****
   * or NIL if we reached EOL.
   */
  static List *
! next_field_expand(const char *filename, char **lineptr)
  {
  	char		buf[MAX_TOKEN];
  	bool		trailing_comma;
--- 319,325 ----
   * 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;
  }
--- 328,342 ----
  
  	do
  	{
! 		if (!next_token(lineptr, buf, sizeof(buf), &initial_quote, &trailing_comma, elevel, err_msg) || (*err_msg != NULL))
  			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, 
*** 313,319 ****
  static List *
  tokenize_inc_file(List *tokens,
  				  const char *outer_filename,
! 				  const char *inc_filename)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
--- 353,361 ----
  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);
--- 382,401 ----
  	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,
*** 389,395 ****
   * 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;
--- 435,441 ----
   * 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;
--- 453,472 ----
  		char		rawline[MAX_LINE];
  		char	   *lineptr;
  		List	   *current_line = NIL;
+ 		char	   *err_msg = NULL;
  
  		if (!fgets(rawline, sizeof(rawline), file))
  			break;
  		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;
  
--- 475,493 ----
  
  		/* 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 ****
--- 495,501 ----
  			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 ****
--- 798,807 ----
  
  /*
   * 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), \
--- 810,865 ----
   * 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), \
--- 867,876 ----
  						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;
--- 879,904 ----
  							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);
--- 921,933 ----
  	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
  	}
--- 936,947 ----
  #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" */
--- 956,978 ----
  			/* 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;
  	}
  
--- 987,999 ----
  	}							/* 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;
--- 1001,1012 ----
  	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;
--- 1021,1032 ----
  	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);
--- 1043,1066 ----
  		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;
--- 1110,1123 ----
  				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);
--- 1130,1157 ----
  			{
  				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);
--- 1163,1186 ----
  				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;
--- 1189,1202 ----
  										 &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;
  				}
  			}
--- 1208,1219 ----
  
  				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);
--- 1224,1247 ----
  	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;
--- 1273,1284 ----
  	{
  		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;
  	}
  
--- 1311,1337 ----
  		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;
  	}
  
--- 1347,1370 ----
  	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;
  	}
  
--- 1377,1388 ----
  	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);
--- 1427,1444 ----
  				/*
  				 * 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;
  		}
  	}
--- 1466,1488 ----
  				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
--- 1507,1521 ----
  /*
   * 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)
--- 1534,1545 ----
  	{
  		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;
--- 1550,1561 ----
  		{
  			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;
  		}
--- 1587,1607 ----
  		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)
--- 1615,1633 ----
  		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;
  		}
  	}
--- 1649,1660 ----
  		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;
--- 1738,1751 ----
  		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;
  		}
  	}
--- 1759,1770 ----
  		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;
--- 1780,1793 ----
  	}
  	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 */
--- 1920,1926 ----
  		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;
  		}
--- 1934,1955 ----
  		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.
--- 1992,2393 ----
  }
  
  /*
+  * 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_rules view */
+ #define NUM_PG_HBA_RULES_ATTS	 9
+ 
+ /*
+  * fill_hba_line: construct one row of pg_hba_rules view, add it to tuplestore
+  *
+  * tuple_store: where to store data
+  * tupdesc: tuple descriptor for the view
+  * lineno: pg_hba 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_RULES_ATTS];
+ 	bool		nulls[NUM_PG_HBA_RULES_ATTS];
+ 	char		buffer[NI_MAXHOST];
+ 	HeapTuple	tuple;
+ 	int			index;
+ 	ListCell   *lc;
+ 	ArrayType  *options;
+ 
+ 	memset(values, 0, sizeof(values));
+ 	memset(nulls, 0, sizeof(nulls));
+ 	index = 0;
+ 
+ 	/* line_number */
+ 	values[index++] = Int32GetDatum(lineno);
+ 
+ 	if (hba != NULL)
+ 	{
+ 		/* type */
+ 		switch (hba->conntype)
+ 		{
+ 			case ctLocal:
+ 				values[index++] = CStringGetTextDatum("local");
+ 				break;
+ 			case ctHost:
+ 				values[index++] = CStringGetTextDatum("host");
+ 				break;
+ 			case ctHostSSL:
+ 				values[index++] = CStringGetTextDatum("hostssl");
+ 				break;
+ 			case ctHostNoSSL:
+ 				values[index++] = CStringGetTextDatum("hostnossl");
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized conntype: %d", (int) hba->conntype);
+ 				break;
+ 		}
+ 
+ 		/* database */
+ 		if (hba->databases)
+ 		{
+ 			/* flatten HbaToken list to string list */
+ 			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 */
+ 			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 */
+ 		switch (hba->ip_cmp_method)
+ 		{
+ 			case ipCmpMask:
+ 				if (hba->hostname)
+ 				{
+ 					values[index++] = CStringGetTextDatum(hba->hostname);
+ 					nulls[index++] = true;
+ 				}
+ 				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);
+ 						values[index++] = CStringGetTextDatum(buffer);
+ 					}
+ 					else
+ 						nulls[index++] = true;
+ 
+ 					/* netmask */
+ 					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);
+ 						values[index++] = CStringGetTextDatum(buffer);
+ 					}
+ 					else
+ 						nulls[index++] = true;
+ 				}
+ 				break;
+ 			case ipCmpAll:
+ 				values[index++] = CStringGetTextDatum("all");
+ 				nulls[index++] = true;
+ 				break;
+ 			case ipCmpSameHost:
+ 				values[index++] = CStringGetTextDatum("samehost");
+ 				nulls[index++] = true;
+ 				break;
+ 			case ipCmpSameNet:
+ 				values[index++] = CStringGetTextDatum("samenet");
+ 				nulls[index++] = true;
+ 				break;
+ 			default:
+ 				elog(ERROR, "unrecognized ip_cmp_method: %d",
+ 					 (int) hba->ip_cmp_method);
+ 				break;
+ 		}
+ 
+ 		/* 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_RULES_ATTS - 2) * sizeof(bool));
+ 	}
+ 
+ 	/* error */
+ 	if (err_msg)
+ 		values[NUM_PG_HBA_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
+ 	else
+ 		nulls[NUM_PG_HBA_RULES_ATTS - 1] = true;
+ 
+ 	tuple = heap_form_tuple(tupdesc, values, nulls);
+ 	tuplestore_puttuple(tuple_store, tuple);
+ }
+ 
+ /*
+  * Read the config file and fill the tuplestore with view records.
+  */
+ static void
+ fill_hba(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 from the pg_hba.conf file.
+  */
+ Datum
+ pg_hba_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 reloads
+ 	 * 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");
+ 	Assert(tupdesc->natts == NUM_PG_HBA_RULES_ATTS);
+ 
+ 	/* 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(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 */
--- 2689,2695 ----
  		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;
  		}
--- 2702,2723 ----
  	{
  		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);
--- 2731,2741 ----
  
  	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..dd1568d 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_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_rules _null_ _null_ _null_ ));
+ DESCR("show pg_hba config 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..de7860a 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_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_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