Re: [HACKERS] pg_hba_file_settings view patch - Mailing list pgsql-hackers

From Tom Lane
Subject Re: [HACKERS] pg_hba_file_settings view patch
Date
Msg-id 30141.1485559686@sss.pgh.pa.us
Whole thread Raw
In response to Re: [HACKERS] pg_hba_file_settings view patch  (Haribabu Kommi <kommi.haribabu@gmail.com>)
Responses Re: [HACKERS] pg_hba_file_settings view patch  (Tom Lane <tgl@sss.pgh.pa.us>)
Re: [HACKERS] pg_hba_file_settings view patch  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
Haribabu Kommi <kommi.haribabu@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
togetherwith 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
togetherwith ldapprefix"), 
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl
togetherwith 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

pgsql-hackers by date:

Previous
From: Andres Freund
Date:
Subject: Re: [HACKERS] [PATCH] Rename pg_switch_xlog to pg_switch_wal
Next
From: Michael Paquier
Date:
Subject: Re: [HACKERS] [PATCH] Rename pg_switch_xlog to pg_switch_wal