Re: [HACKERS] pg_hba_file_settings view patch

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: [HACKERS] pg_hba_file_settings view patch
Дата
Msg-id 28497.1485641928@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Re: [HACKERS] pg_hba_file_settings view patch  (Tom Lane <tgl@sss.pgh.pa.us>)
Ответы Re: [HACKERS] pg_hba_file_settings view patch  (Haribabu Kommi <kommi.haribabu@gmail.com>)
Список pgsql-hackers
I wrote:
> I spent awhile hacking on this, and made a lot of things better, but
> I'm still very unhappy about the state of the comments.

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

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

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 086fafc..204b8cf 100644
*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 7809,7814 ****
--- 7809,7819 ----
       </row>

       <row>
+       <entry><link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link></entry>
+       <entry>summary of client authentication configuration file contents</entry>
+      </row>
+
+      <row>
        <entry><link linkend="view-pg-indexes"><structname>pg_indexes</structname></link></entry>
        <entry>indexes</entry>
       </row>
***************
*** 8408,8413 ****
--- 8413,8526 ----

   </sect1>

+  <sect1 id="view-pg-hba-file-rules">
+   <title><structname>pg_hba_file_rules</structname></title>
+
+   <indexterm zone="view-pg-hba-file-rules">
+    <primary>pg_hba_file_rules</primary>
+   </indexterm>
+
+   <para>
+    The view <structname>pg_hba_file_rules</structname> provides a summary of
+    the contents of the client authentication configuration
+    file, <filename>pg_hba.conf</>.  A row appears in this view for each
+    non-empty, non-comment line in the file, with annotations indicating
+    whether the rule could be applied successfully.
+   </para>
+
+   <para>
+    This view can be helpful for checking whether planned changes in the
+    authentication configuration file will work, or for diagnosing a previous
+    failure.  Note that this view reports on the <emphasis>current</> contents
+    of the file, not on what was last loaded by the server.
+   </para>
+
+   <para>
+    By default, the <structname>pg_hba_file_rules</structname> view can be read
+    only by superusers.
+   </para>
+
+   <table>
+    <title><structname>pg_hba_file_rules</> Columns</title>
+
+   <tgroup cols="3">
+    <thead>
+     <row>
+      <entry>Name</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+    <tbody>
+     <row>
+      <entry><structfield>line_number</structfield></entry>
+      <entry><structfield>integer</structfield></entry>
+      <entry>
+       Line number of this rule in <filename>pg_hba.conf</>
+      </entry>
+     </row>
+     <row>
+      <entry><structfield>type</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>Type of connection</entry>
+     </row>
+     <row>
+      <entry><structfield>database</structfield></entry>
+      <entry><structfield>text[]</structfield></entry>
+      <entry>List of database name(s) to which this rule applies</entry>
+     </row>
+     <row>
+      <entry><structfield>user_name</structfield></entry>
+      <entry><structfield>text[]</structfield></entry>
+      <entry>List of user and group name(s) to which this rule applies</entry>
+     </row>
+     <row>
+      <entry><structfield>address</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>
+       Host name or IP address, or one
+       of <literal>all</literal>, <literal>samehost</literal>,
+       or <literal>samenet</literal>, or null for local connections
+      </entry>
+     </row>
+     <row>
+      <entry><structfield>netmask</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>IP address mask, or null if not applicable</entry>
+     </row>
+     <row>
+      <entry><structfield>auth_method</structfield></entry>
+      <entry><type>text</type></entry>
+      <entry>Authentication method</entry>
+     </row>
+     <row>
+      <entry><structfield>options</structfield></entry>
+      <entry><type>text[]</type></entry>
+      <entry>Options specified for authentication method, if any</entry>
+     </row>
+     <row>
+      <entry><structfield>error</structfield></entry>
+      <entry><structfield>text</structfield></entry>
+      <entry>
+       If not null, an error message indicating why this
+       line could not be processed
+      </entry>
+     </row>
+    </tbody>
+   </tgroup>
+   </table>
+
+   <para>
+    Usually, a row reflecting an incorrect entry will have values for only
+    the <structfield>line_number</> and <structfield>error</> fields.
+   </para>
+
+   <para>
+    See <xref linkend="client-authentication"> for more information about
+    client authentication configuration.
+   </para>
+  </sect1>
+
   <sect1 id="view-pg-indexes">
    <title><structname>pg_indexes</structname></title>

diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index dda5891..231fc40 100644
*** a/doc/src/sgml/client-auth.sgml
--- b/doc/src/sgml/client-auth.sgml
*************** hostnossl  <replaceable>database</replac
*** 597,602 ****
--- 597,620 ----
     re-read the file.
    </para>

+   <note>
+    <para>
+     The preceding statement is not true on Microsoft Windows: there, any
+     changes in the <filename>pg_hba.conf</filename> file are immediately
+     applied by subsequent new connections.
+    </para>
+   </note>
+
+   <para>
+    The system view
+    <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link>
+    can be helpful for pre-testing changes to the <filename>pg_hba.conf</>
+    file, or for diagnosing problems if loading of the file did not have the
+    desired effects.  Rows in the view with
+    non-null <structfield>error</structfield> fields indicate problems in the
+    corresponding lines of the file.
+   </para>
+
    <tip>
     <para>
      To connect to a particular database, a user must not only pass the
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 4dfedf8..28be27a 100644
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
*************** CREATE VIEW pg_file_settings AS
*** 459,464 ****
--- 459,470 ----
  REVOKE ALL on pg_file_settings FROM PUBLIC;
  REVOKE EXECUTE ON FUNCTION pg_show_all_file_settings() FROM PUBLIC;

+ CREATE VIEW pg_hba_file_rules AS
+    SELECT * FROM pg_hba_file_rules() AS A;
+
+ REVOKE ALL on pg_hba_file_rules FROM PUBLIC;
+ REVOKE EXECUTE ON FUNCTION pg_hba_file_rules() FROM PUBLIC;
+
  CREATE VIEW pg_timezone_abbrevs AS
      SELECT * FROM pg_timezone_abbrevs();

diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index bbe0a88..7a0f1ce 100644
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 25,39 ****
--- 25,44 ----
  #include <arpa/inet.h>
  #include <unistd.h>

+ #include "access/htup_details.h"
  #include "catalog/pg_collation.h"
+ #include "catalog/pg_type.h"
  #include "common/ip.h"
+ #include "funcapi.h"
  #include "libpq/ifaddr.h"
  #include "libpq/libpq.h"
+ #include "miscadmin.h"
  #include "postmaster/postmaster.h"
  #include "regex/regex.h"
  #include "replication/walsender.h"
  #include "storage/fd.h"
  #include "utils/acl.h"
+ #include "utils/builtins.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  #include "utils/memutils.h"
*************** typedef struct HbaToken
*** 80,91 ****
--- 85,99 ----
   * Each item in the "fields" list is a sub-list of HbaTokens.
   * We don't emit a TokenizedLine for empty or all-comment lines,
   * so "fields" is never NIL (nor are any of its sub-lists).
+  * Exception: if an error occurs during tokenization, we might
+  * have fields == NIL, in which case err_msg != NULL.
   */
  typedef struct TokenizedLine
  {
      List       *fields;            /* List of lists of HbaTokens */
      int            line_num;        /* Line number */
      char       *raw_line;        /* Raw line text */
+     char       *err_msg;        /* Error message if any */
  } TokenizedLine;

  /*
*************** static MemoryContext parsed_hba_context
*** 106,118 ****
  static List *parsed_ident_lines = NIL;
  static MemoryContext parsed_ident_context = NULL;


  static MemoryContext tokenize_file(const char *filename, FILE *file,
!               List **tok_lines);
  static List *tokenize_inc_file(List *tokens, const char *outer_filename,
!                   const char *inc_filename);
  static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
!                    int line_num);

  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
--- 114,155 ----
  static List *parsed_ident_lines = NIL;
  static MemoryContext parsed_ident_context = NULL;

+ /*
+  * The following character array represents the names of the authentication
+  * methods that are supported by PostgreSQL.
+  *
+  * Note: keep this in sync with the UserAuth enum in hba.h.
+  */
+ static const char *const UserAuthName[] =
+ {
+     "reject",
+     "implicit reject",            /* Not a user-visible option */
+     "trust",
+     "ident",
+     "password",
+     "md5",
+     "gss",
+     "sspi",
+     "pam",
+     "bsd",
+     "ldap",
+     "cert",
+     "radius",
+     "peer"
+ };
+

  static MemoryContext tokenize_file(const char *filename, FILE *file,
!               List **tok_lines, int elevel);
  static List *tokenize_inc_file(List *tokens, const char *outer_filename,
!                   const char *inc_filename, int elevel, char **err_msg);
  static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
!                    int elevel, char **err_msg);
! static ArrayType *gethba_options(HbaLine *hba);
! static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
!               int lineno, HbaLine *hba, const char *err_msg);
! static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
!

  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
*************** pg_isblank(const char c)
*** 126,157 ****


  /*
!  * Grab one token out of the string pointed to by lineptr.
   * Tokens are strings of non-blank
   * characters bounded by blank characters, commas, beginning of line, and
   * end of line. Blank means space or tab. Tokens can be delimited by
   * double quotes (this allows the inclusion of blanks, but not newlines).
   *
-  * The token, if any, is returned at *buf (a buffer of size bufsz).
   * Also, we set *initial_quote to indicate whether there was quoting before
   * the first character.  (We use that to prevent "@x" from being treated
   * as a file inclusion request.  Note that @"x" should be so treated;
   * we want to allow that to support embedded spaces in file paths.)
   * We set *terminating_comma to indicate whether the token is terminated by a
!  * comma (which is not returned.)
   *
   * If successful: store null-terminated token at *buf and return TRUE.
   * If no more tokens on line: set *buf = '\0' and return FALSE.
!  *
!  * Leave file positioned at the character immediately after the token or EOF,
!  * whichever comes first. If no more tokens on line, position the file to the
!  * beginning of the next line or EOF, whichever comes first.
!  *
!  * Handle comments.
   */
  static bool
! next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
!            bool *terminating_comma)
  {
      int            c;
      char       *start_buf = buf;
--- 163,199 ----


  /*
!  * Grab one token out of the string pointed to by *lineptr.
!  *
   * Tokens are strings of non-blank
   * characters bounded by blank characters, commas, beginning of line, and
   * end of line. Blank means space or tab. Tokens can be delimited by
   * double quotes (this allows the inclusion of blanks, but not newlines).
+  * Comments (started by an unquoted '#') are skipped.
+  *
+  * The token, if any, is returned at *buf (a buffer of size bufsz), and
+  * *lineptr is advanced past the token.
   *
   * Also, we set *initial_quote to indicate whether there was quoting before
   * the first character.  (We use that to prevent "@x" from being treated
   * as a file inclusion request.  Note that @"x" should be so treated;
   * we want to allow that to support embedded spaces in file paths.)
+  *
   * We set *terminating_comma to indicate whether the token is terminated by a
!  * comma (which is not returned).
!  *
!  * In event of an error, log a message at ereport level elevel, and also
!  * set *err_msg to a string describing the error.  Currently the only
!  * possible error is token too long for buf.
   *
   * If successful: store null-terminated token at *buf and return TRUE.
   * If no more tokens on line: set *buf = '\0' and return FALSE.
!  * If error: fill buf with truncated or misformatted token and return FALSE.
   */
  static bool
! next_token(char **lineptr, char *buf, int bufsz,
!            bool *initial_quote, bool *terminating_comma,
!            int elevel, char **err_msg)
  {
      int            c;
      char       *start_buf = buf;
*************** next_token(char **lineptr, char *buf, in
*** 197,210 ****
          if (buf >= end_buf)
          {
              *buf = '\0';
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                 errmsg("authentication file token too long, skipping: \"%s\"",
                        start_buf)));
              /* Discard remainder of line */
              while ((c = (*(*lineptr)++)) != '\0' && c != '\n')
                  ;
!             break;
          }

          /* we do not pass back the comma in the token */
--- 239,253 ----
          if (buf >= end_buf)
          {
              *buf = '\0';
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                 errmsg("authentication file token too long, skipping: \"%s\"",
                        start_buf)));
+             *err_msg = "authentication file token too long";
              /* Discard remainder of line */
              while ((c = (*(*lineptr)++)) != '\0' && c != '\n')
                  ;
!             return false;
          }

          /* we do not pass back the comma in the token */
*************** next_token(char **lineptr, char *buf, in
*** 245,257 ****
      return (saw_quote || buf > start_buf);
  }

  static HbaToken *
! make_hba_token(char *token, bool quoted)
  {
      HbaToken   *hbatoken;
      int            toklen;

      toklen = strlen(token);
      hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1);
      hbatoken->string = (char *) hbatoken + sizeof(HbaToken);
      hbatoken->quoted = quoted;
--- 288,304 ----
      return (saw_quote || buf > start_buf);
  }

+ /*
+  * Construct a palloc'd HbaToken struct, copying the given string.
+  */
  static HbaToken *
! make_hba_token(const char *token, bool quoted)
  {
      HbaToken   *hbatoken;
      int            toklen;

      toklen = strlen(token);
+     /* we copy string into same palloc block as the struct */
      hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1);
      hbatoken->string = (char *) hbatoken + sizeof(HbaToken);
      hbatoken->quoted = quoted;
*************** copy_hba_token(HbaToken *in)
*** 275,285 ****
  /*
   * Tokenize one HBA field from a line, handling file inclusion and comma lists.
   *
!  * The result is a List of HbaToken structs for each individual token,
   * or NIL if we reached EOL.
   */
  static List *
! next_field_expand(const char *filename, char **lineptr)
  {
      char        buf[MAX_TOKEN];
      bool        trailing_comma;
--- 322,341 ----
  /*
   * Tokenize one HBA field from a line, handling file inclusion and comma lists.
   *
!  * filename: current file's pathname (needed to resolve relative pathnames)
!  * *lineptr: current line pointer, which will be advanced past field
!  *
!  * In event of an error, log a message at ereport level elevel, and also
!  * set *err_msg to a string describing the error.  Note that the result
!  * may be non-NIL anyway, so *err_msg must be tested to determine whether
!  * there was an error.
!  *
!  * The result is a List of HbaToken structs, one for each token in the field,
   * or NIL if we reached EOL.
   */
  static List *
! next_field_expand(const char *filename, char **lineptr,
!                   int elevel, char **err_msg)
  {
      char        buf[MAX_TOKEN];
      bool        trailing_comma;
*************** next_field_expand(const char *filename,
*** 288,302 ****

      do
      {
!         if (!next_token(lineptr, buf, sizeof(buf), &initial_quote, &trailing_comma))
              break;

          /* Is this referencing a file? */
          if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
!             tokens = tokenize_inc_file(tokens, filename, buf + 1);
          else
              tokens = lappend(tokens, make_hba_token(buf, initial_quote));
!     } while (trailing_comma);

      return tokens;
  }
--- 344,361 ----

      do
      {
!         if (!next_token(lineptr, buf, sizeof(buf),
!                         &initial_quote, &trailing_comma,
!                         elevel, err_msg))
              break;

          /* Is this referencing a file? */
          if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
!             tokens = tokenize_inc_file(tokens, filename, buf + 1,
!                                        elevel, err_msg);
          else
              tokens = lappend(tokens, make_hba_token(buf, initial_quote));
!     } while (trailing_comma && (*err_msg == NULL));

      return tokens;
  }
*************** next_field_expand(const char *filename,
*** 307,319 ****
   *
   * Opens and tokenises a file included from another HBA config file with @,
   * and returns all values found therein as a flat list of HbaTokens.  If a
!  * @-token is found, recursively expand it.  The given token list is used as
!  * initial contents of list (so foo,bar,@baz does what you expect).
   */
  static List *
  tokenize_inc_file(List *tokens,
                    const char *outer_filename,
!                   const char *inc_filename)
  {
      char       *inc_fullname;
      FILE       *inc_file;
--- 366,386 ----
   *
   * Opens and tokenises a file included from another HBA config file with @,
   * and returns all values found therein as a flat list of HbaTokens.  If a
!  * @-token is found, recursively expand it.  The newly read tokens are
!  * appended to "tokens" (so that foo,bar,@baz does what you expect).
!  * All new tokens are allocated in caller's memory context.
!  *
!  * In event of an error, log a message at ereport level elevel, and also
!  * set *err_msg to a string describing the error.  Note that the result
!  * may be non-NIL anyway, so *err_msg must be tested to determine whether
!  * there was an error.
   */
  static List *
  tokenize_inc_file(List *tokens,
                    const char *outer_filename,
!                   const char *inc_filename,
!                   int elevel,
!                   char **err_msg)
  {
      char       *inc_fullname;
      FILE       *inc_file;
*************** tokenize_inc_file(List *tokens,
*** 340,355 ****
      inc_file = AllocateFile(inc_fullname, "r");
      if (inc_file == NULL)
      {
!         ereport(LOG,
                  (errcode_for_file_access(),
                   errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
                          inc_filename, inc_fullname)));
          pfree(inc_fullname);
          return tokens;
      }

      /* There is possible recursion here if the file contains @ */
!     linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines);

      FreeFile(inc_file);
      pfree(inc_fullname);
--- 407,426 ----
      inc_file = AllocateFile(inc_fullname, "r");
      if (inc_file == NULL)
      {
!         int            save_errno = errno;
!
!         ereport(elevel,
                  (errcode_for_file_access(),
                   errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
                          inc_filename, inc_fullname)));
+         *err_msg = psprintf("could not open secondary authentication file \"@%s\" as \"%s\": %s",
+                             inc_filename, inc_fullname, strerror(save_errno));
          pfree(inc_fullname);
          return tokens;
      }

      /* There is possible recursion here if the file contains @ */
!     linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, elevel);

      FreeFile(inc_file);
      pfree(inc_fullname);
*************** tokenize_inc_file(List *tokens,
*** 360,365 ****
--- 431,443 ----
          TokenizedLine *tok_line = (TokenizedLine *) lfirst(inc_line);
          ListCell   *inc_field;

+         /* If any line has an error, propagate that up to caller */
+         if (tok_line->err_msg)
+         {
+             *err_msg = pstrdup(tok_line->err_msg);
+             break;
+         }
+
          foreach(inc_field, tok_line->fields)
          {
              List       *inc_tokens = lfirst(inc_field);
*************** tokenize_inc_file(List *tokens,
*** 383,395 ****
   *
   * The output is a list of TokenizedLine structs; see struct definition above.
   *
!  * filename must be the absolute path to the target file.
   *
   * Return value is a memory context which contains all memory allocated by
   * this function (it's a child of caller's context).
   */
  static MemoryContext
! tokenize_file(const char *filename, FILE *file, List **tok_lines)
  {
      int            line_number = 1;
      MemoryContext linecxt;
--- 461,480 ----
   *
   * The output is a list of TokenizedLine structs; see struct definition above.
   *
!  * filename: the absolute path to the target file
!  * file: the already-opened target file
!  * tok_lines: receives output list
!  * elevel: message logging level
!  *
!  * Errors are reported by logging messages at ereport level elevel and by
!  * adding TokenizedLine structs containing non-null err_msg fields to the
!  * output list.
   *
   * Return value is a memory context which contains all memory allocated by
   * this function (it's a child of caller's context).
   */
  static MemoryContext
! tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
  {
      int            line_number = 1;
      MemoryContext linecxt;
*************** tokenize_file(const char *filename, FILE
*** 407,422 ****
          char        rawline[MAX_LINE];
          char       *lineptr;
          List       *current_line = NIL;

          if (!fgets(rawline, sizeof(rawline), file))
!             break;
          if (strlen(rawline) == MAX_LINE - 1)
              /* Line too long! */
!             ereport(ERROR,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("authentication file line too long"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_number, filename)));

          /* Strip trailing linebreak from rawline */
          lineptr = rawline + strlen(rawline) - 1;
--- 492,523 ----
          char        rawline[MAX_LINE];
          char       *lineptr;
          List       *current_line = NIL;
+         char       *err_msg = NULL;

          if (!fgets(rawline, sizeof(rawline), file))
!         {
!             int            save_errno = errno;
!
!             if (!ferror(file))
!                 break;            /* normal EOF */
!             /* I/O error! */
!             ereport(elevel,
!                     (errcode_for_file_access(),
!                      errmsg("could not read file \"%s\": %m", filename)));
!             err_msg = psprintf("could not read file \"%s\": %s",
!                                filename, strerror(save_errno));
!             rawline[0] = '\0';
!         }
          if (strlen(rawline) == MAX_LINE - 1)
+         {
              /* Line too long! */
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("authentication file line too long"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_number, filename)));
+             err_msg = "authentication file line too long";
+         }

          /* Strip trailing linebreak from rawline */
          lineptr = rawline + strlen(rawline) - 1;
*************** tokenize_file(const char *filename, FILE
*** 425,442 ****

          /* Parse fields */
          lineptr = rawline;
!         while (*lineptr)
          {
              List       *current_field;

!             current_field = next_field_expand(filename, &lineptr);
              /* add field to line, unless we are at EOL or comment start */
              if (current_field != NIL)
                  current_line = lappend(current_line, current_field);
          }

          /* Reached EOL; emit line to TokenizedLine list unless it's boring */
!         if (current_line != NIL)
          {
              TokenizedLine *tok_line;

--- 526,544 ----

          /* Parse fields */
          lineptr = rawline;
!         while (*lineptr && err_msg == NULL)
          {
              List       *current_field;

!             current_field = next_field_expand(filename, &lineptr,
!                                               elevel, &err_msg);
              /* add field to line, unless we are at EOL or comment start */
              if (current_field != NIL)
                  current_line = lappend(current_line, current_field);
          }

          /* Reached EOL; emit line to TokenizedLine list unless it's boring */
!         if (current_line != NIL || err_msg != NULL)
          {
              TokenizedLine *tok_line;

*************** tokenize_file(const char *filename, FILE
*** 444,449 ****
--- 546,552 ----
              tok_line->fields = current_line;
              tok_line->line_num = line_number;
              tok_line->raw_line = pstrdup(rawline);
+             tok_line->err_msg = err_msg;
              *tok_lines = lappend(*tok_lines, tok_line);
          }

*************** check_same_host_or_net(SockAddr *raddr,
*** 746,751 ****
--- 849,858 ----

  /*
   * Macros used to check and report on invalid configuration options.
+  * On error: log a message at level elevel, set *err_msg, and exit the function.
+  * These macros are not as general-purpose as they look, because they know
+  * what the calling function's error-exit value is.
+  *
   * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
   *                         not supported.
   * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
*************** check_same_host_or_net(SockAddr *raddr,
*** 754,797 ****
   * MANDATORY_AUTH_ARG  = check if a required option is set for an authentication method,
   *                         reporting error if it's not.
   */
! #define INVALID_AUTH_OPTION(optname, validmethods) do {\
!     ereport(LOG, \
              (errcode(ERRCODE_CONFIG_FILE_ERROR), \
               /* translator: the second %s is a list of auth methods */ \
               errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
                      optname, _(validmethods)), \
               errcontext("line %d of configuration file \"%s\"", \
                      line_num, HbaFileName))); \
      return false; \
! } while (0);

! #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
      if (hbaline->auth_method != methodval) \
          INVALID_AUTH_OPTION(optname, validmethods); \
! } while (0);

! #define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\
!     if (argvar == NULL) {\
!         ereport(LOG, \
                  (errcode(ERRCODE_CONFIG_FILE_ERROR), \
                   errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \
                          authname, argname), \
                   errcontext("line %d of configuration file \"%s\"", \
                          line_num, HbaFileName))); \
          return NULL; \
      } \
! } while (0);

  /*
   * IDENT_FIELD_ABSENT:
!  * Throw an error and exit the function if the given ident field ListCell is
   * not populated.
   *
   * IDENT_MULTI_VALUE:
!  * Throw an error and exit the function if the given ident token List has more
   * than one element.
   */
! #define IDENT_FIELD_ABSENT(field) do {\
      if (!field) { \
          ereport(LOG, \
                  (errcode(ERRCODE_CONFIG_FILE_ERROR), \
--- 861,916 ----
   * MANDATORY_AUTH_ARG  = check if a required option is set for an authentication method,
   *                         reporting error if it's not.
   */
! #define INVALID_AUTH_OPTION(optname, validmethods) \
! do { \
!     ereport(elevel, \
              (errcode(ERRCODE_CONFIG_FILE_ERROR), \
               /* translator: the second %s is a list of auth methods */ \
               errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
                      optname, _(validmethods)), \
               errcontext("line %d of configuration file \"%s\"", \
                      line_num, HbaFileName))); \
+     *err_msg = psprintf("authentication option \"%s\" is only valid for authentication methods %s", \
+                         optname, validmethods); \
      return false; \
! } while (0)

! #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
! do { \
      if (hbaline->auth_method != methodval) \
          INVALID_AUTH_OPTION(optname, validmethods); \
! } while (0)

! #define MANDATORY_AUTH_ARG(argvar, argname, authname) \
! do { \
!     if (argvar == NULL) { \
!         ereport(elevel, \
                  (errcode(ERRCODE_CONFIG_FILE_ERROR), \
                   errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \
                          authname, argname), \
                   errcontext("line %d of configuration file \"%s\"", \
                          line_num, HbaFileName))); \
+         *err_msg = psprintf("authentication method \"%s\" requires argument \"%s\" to be set", \
+                             authname, argname); \
          return NULL; \
      } \
! } while (0)

  /*
+  * Macros for handling pg_ident problems.
+  * Much as above, but currently the message level is hardwired as LOG
+  * and there is no provision for an err_msg string.
+  *
   * IDENT_FIELD_ABSENT:
!  * Log a message and exit the function if the given ident field ListCell is
   * not populated.
   *
   * IDENT_MULTI_VALUE:
!  * Log a message and exit the function if the given ident token List has more
   * than one element.
   */
! #define IDENT_FIELD_ABSENT(field) \
! do { \
      if (!field) { \
          ereport(LOG, \
                  (errcode(ERRCODE_CONFIG_FILE_ERROR), \
*************** check_same_host_or_net(SockAddr *raddr,
*** 799,807 ****
                          IdentFileName, line_num))); \
          return NULL; \
      } \
! } while (0);

! #define IDENT_MULTI_VALUE(tokens) do {\
      if (tokens->length > 1) { \
          ereport(LOG, \
                  (errcode(ERRCODE_CONFIG_FILE_ERROR), \
--- 918,927 ----
                          IdentFileName, line_num))); \
          return NULL; \
      } \
! } while (0)

! #define IDENT_MULTI_VALUE(tokens) \
! do { \
      if (tokens->length > 1) { \
          ereport(LOG, \
                  (errcode(ERRCODE_CONFIG_FILE_ERROR), \
*************** check_same_host_or_net(SockAddr *raddr,
*** 810,832 ****
                              line_num, IdentFileName))); \
          return NULL; \
      } \
! } while (0);


  /*
   * Parse one tokenised line from the hba config file and store the result in a
   * HbaLine structure.
   *
!  * Return NULL if parsing fails.
   *
   * Note: this function leaks memory when an error occurs.  Caller is expected
   * to have set a memory context that will be reset if this function returns
   * NULL.
   */
  static HbaLine *
! parse_hba_line(TokenizedLine *tok_line)
  {
      int            line_num = tok_line->line_num;
      char       *str;
      struct addrinfo *gai_result;
      struct addrinfo hints;
--- 930,955 ----
                              line_num, IdentFileName))); \
          return NULL; \
      } \
! } while (0)


  /*
   * Parse one tokenised line from the hba config file and store the result in a
   * HbaLine structure.
   *
!  * If parsing fails, log a message at ereport level elevel, store an error
!  * string in tok_line->err_msg, and return NULL.  (Some non-error conditions
!  * can also result in such messages.)
   *
   * Note: this function leaks memory when an error occurs.  Caller is expected
   * to have set a memory context that will be reset if this function returns
   * NULL.
   */
  static HbaLine *
! parse_hba_line(TokenizedLine *tok_line, int elevel)
  {
      int            line_num = tok_line->line_num;
+     char      **err_msg = &tok_line->err_msg;
      char       *str;
      struct addrinfo *gai_result;
      struct addrinfo hints;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 849,860 ****
      tokens = lfirst(field);
      if (tokens->length > 1)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("multiple values specified for connection type"),
                   errhint("Specify exactly one connection type per line."),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }
      token = linitial(tokens);
--- 972,984 ----
      tokens = lfirst(field);
      if (tokens->length > 1)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("multiple values specified for connection type"),
                   errhint("Specify exactly one connection type per line."),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "multiple values specified for connection type";
          return NULL;
      }
      token = linitial(tokens);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 863,873 ****
  #ifdef HAVE_UNIX_SOCKETS
          parsedline->conntype = ctLocal;
  #else
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("local connections are not supported by this build"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
  #endif
      }
--- 987,998 ----
  #ifdef HAVE_UNIX_SOCKETS
          parsedline->conntype = ctLocal;
  #else
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("local connections are not supported by this build"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "local connections are not supported by this build";
          return NULL;
  #endif
      }
*************** parse_hba_line(TokenizedLine *tok_line)
*** 882,900 ****
              /* Log a warning if SSL support is not active */
  #ifdef USE_SSL
              if (!EnableSSL)
!                 ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                  errmsg("hostssl record cannot match because SSL is disabled"),
                           errhint("Set ssl = on in postgresql.conf."),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
  #else
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("hostssl record cannot match because SSL is not supported by this build"),
                errhint("Compile with --with-openssl to use SSL connections."),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
  #endif
          }
          else if (token->string[4] == 'n')        /* "hostnossl" */
--- 1007,1029 ----
              /* Log a warning if SSL support is not active */
  #ifdef USE_SSL
              if (!EnableSSL)
!             {
!                 ereport(elevel,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                  errmsg("hostssl record cannot match because SSL is disabled"),
                           errhint("Set ssl = on in postgresql.conf."),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 *err_msg = "hostssl record cannot match because SSL is disabled";
+             }
  #else
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("hostssl record cannot match because SSL is not supported by this build"),
                errhint("Compile with --with-openssl to use SSL connections."),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = "hostssl record cannot match because SSL is not supported by this build";
  #endif
          }
          else if (token->string[4] == 'n')        /* "hostnossl" */
*************** parse_hba_line(TokenizedLine *tok_line)
*** 909,920 ****
      }                            /* record type */
      else
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("invalid connection type \"%s\"",
                          token->string),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }

--- 1038,1050 ----
      }                            /* record type */
      else
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("invalid connection type \"%s\"",
                          token->string),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = psprintf("invalid connection type \"%s\"", token->string);
          return NULL;
      }

*************** parse_hba_line(TokenizedLine *tok_line)
*** 922,932 ****
      field = lnext(field);
      if (!field)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("end-of-line before database specification"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }
      parsedline->databases = NIL;
--- 1052,1063 ----
      field = lnext(field);
      if (!field)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("end-of-line before database specification"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "end-of-line before database specification";
          return NULL;
      }
      parsedline->databases = NIL;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 941,951 ****
      field = lnext(field);
      if (!field)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("end-of-line before role specification"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }
      parsedline->roles = NIL;
--- 1072,1083 ----
      field = lnext(field);
      if (!field)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("end-of-line before role specification"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "end-of-line before role specification";
          return NULL;
      }
      parsedline->roles = NIL;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 962,983 ****
          field = lnext(field);
          if (!field)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("end-of-line before IP address specification"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              return NULL;
          }
          tokens = lfirst(field);
          if (tokens->length > 1)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("multiple values specified for host address"),
                       errhint("Specify one address range per line."),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              return NULL;
          }
          token = linitial(tokens);
--- 1094,1117 ----
          field = lnext(field);
          if (!field)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("end-of-line before IP address specification"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = "end-of-line before IP address specification";
              return NULL;
          }
          tokens = lfirst(field);
          if (tokens->length > 1)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("multiple values specified for host address"),
                       errhint("Specify one address range per line."),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = "multiple values specified for host address";
              return NULL;
          }
          token = linitial(tokens);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1027,1038 ****
                  parsedline->hostname = str;
              else
              {
!                 ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("invalid IP address \"%s\": %s",
                                  str, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return NULL;
--- 1161,1174 ----
                  parsedline->hostname = str;
              else
              {
!                 ereport(elevel,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("invalid IP address \"%s\": %s",
                                  str, gai_strerror(ret)),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 *err_msg = psprintf("invalid IP address \"%s\": %s",
+                                     str, gai_strerror(ret));
                  if (gai_result)
                      pg_freeaddrinfo_all(hints.ai_family, gai_result);
                  return NULL;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1045,1068 ****
              {
                  if (parsedline->hostname)
                  {
!                     ereport(LOG,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
                                      token->string),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
                      return NULL;
                  }

                  if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
                                            parsedline->addr.ss_family) < 0)
                  {
!                     ereport(LOG,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("invalid CIDR mask in address \"%s\"",
                                      token->string),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
                      return NULL;
                  }
                  pfree(str);
--- 1181,1208 ----
              {
                  if (parsedline->hostname)
                  {
!                     ereport(elevel,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
                                      token->string),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
+                     *err_msg = psprintf("specifying both host name and CIDR mask is invalid: \"%s\"",
+                                         token->string);
                      return NULL;
                  }

                  if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
                                            parsedline->addr.ss_family) < 0)
                  {
!                     ereport(elevel,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("invalid CIDR mask in address \"%s\"",
                                      token->string),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
+                     *err_msg = psprintf("invalid CIDR mask in address \"%s\"",
+                                         token->string);
                      return NULL;
                  }
                  pfree(str);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1074,1095 ****
                  field = lnext(field);
                  if (!field)
                  {
!                     ereport(LOG,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                            errmsg("end-of-line before netmask specification"),
                               errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
                      return NULL;
                  }
                  tokens = lfirst(field);
                  if (tokens->length > 1)
                  {
!                     ereport(LOG,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("multiple values specified for netmask"),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
                      return NULL;
                  }
                  token = linitial(tokens);
--- 1214,1237 ----
                  field = lnext(field);
                  if (!field)
                  {
!                     ereport(elevel,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                            errmsg("end-of-line before netmask specification"),
                               errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
+                     *err_msg = "end-of-line before netmask specification";
                      return NULL;
                  }
                  tokens = lfirst(field);
                  if (tokens->length > 1)
                  {
!                     ereport(elevel,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("multiple values specified for netmask"),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
+                     *err_msg = "multiple values specified for netmask";
                      return NULL;
                  }
                  token = linitial(tokens);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1098,1109 ****
                                           &hints, &gai_result);
                  if (ret || !gai_result)
                  {
!                     ereport(LOG,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("invalid IP mask \"%s\": %s",
                                      token->string, gai_strerror(ret)),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
                      if (gai_result)
                          pg_freeaddrinfo_all(hints.ai_family, gai_result);
                      return NULL;
--- 1240,1253 ----
                                           &hints, &gai_result);
                  if (ret || !gai_result)
                  {
!                     ereport(elevel,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("invalid IP mask \"%s\": %s",
                                      token->string, gai_strerror(ret)),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
+                     *err_msg = psprintf("invalid IP mask \"%s\": %s",
+                                         token->string, gai_strerror(ret));
                      if (gai_result)
                          pg_freeaddrinfo_all(hints.ai_family, gai_result);
                      return NULL;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1115,1125 ****

                  if (parsedline->addr.ss_family != parsedline->mask.ss_family)
                  {
!                     ereport(LOG,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("IP address and mask do not match"),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
                      return NULL;
                  }
              }
--- 1259,1270 ----

                  if (parsedline->addr.ss_family != parsedline->mask.ss_family)
                  {
!                     ereport(elevel,
                              (errcode(ERRCODE_CONFIG_FILE_ERROR),
                               errmsg("IP address and mask do not match"),
                             errcontext("line %d of configuration file \"%s\"",
                                        line_num, HbaFileName)));
+                     *err_msg = "IP address and mask do not match";
                      return NULL;
                  }
              }
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1130,1151 ****
      field = lnext(field);
      if (!field)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("end-of-line before authentication method"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }
      tokens = lfirst(field);
      if (tokens->length > 1)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("multiple values specified for authentication type"),
                   errhint("Specify exactly one authentication type per line."),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }
      token = linitial(tokens);
--- 1275,1298 ----
      field = lnext(field);
      if (!field)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("end-of-line before authentication method"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "end-of-line before authentication method";
          return NULL;
      }
      tokens = lfirst(field);
      if (tokens->length > 1)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("multiple values specified for authentication type"),
                   errhint("Specify exactly one authentication type per line."),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "multiple values specified for authentication type";
          return NULL;
      }
      token = linitial(tokens);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1177,1187 ****
      {
          if (Db_user_namespace)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              return NULL;
          }
          parsedline->auth_method = uaMD5;
--- 1324,1335 ----
      {
          if (Db_user_namespace)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = "MD5 authentication is not supported when \"db_user_namespace\" is enabled";
              return NULL;
          }
          parsedline->auth_method = uaMD5;
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1214,1236 ****
          parsedline->auth_method = uaRADIUS;
      else
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("invalid authentication method \"%s\"",
                          token->string),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }

      if (unsupauth)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("invalid authentication method \"%s\": not supported by this build",
                          token->string),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }

--- 1362,1388 ----
          parsedline->auth_method = uaRADIUS;
      else
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("invalid authentication method \"%s\"",
                          token->string),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = psprintf("invalid authentication method \"%s\"",
+                             token->string);
          return NULL;
      }

      if (unsupauth)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("invalid authentication method \"%s\": not supported by this build",
                          token->string),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = psprintf("invalid authentication method \"%s\": not supported by this build",
+                             token->string);
          return NULL;
      }

*************** parse_hba_line(TokenizedLine *tok_line)
*** 1246,1267 ****
      if (parsedline->conntype == ctLocal &&
          parsedline->auth_method == uaGSS)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
             errmsg("gssapi authentication is not supported on local sockets"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }

      if (parsedline->conntype != ctLocal &&
          parsedline->auth_method == uaPeer)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
              errmsg("peer authentication is only supported on local sockets"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }

--- 1398,1421 ----
      if (parsedline->conntype == ctLocal &&
          parsedline->auth_method == uaGSS)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
             errmsg("gssapi authentication is not supported on local sockets"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "gssapi authentication is not supported on local sockets";
          return NULL;
      }

      if (parsedline->conntype != ctLocal &&
          parsedline->auth_method == uaPeer)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
              errmsg("peer authentication is only supported on local sockets"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "peer authentication is only supported on local sockets";
          return NULL;
      }

*************** parse_hba_line(TokenizedLine *tok_line)
*** 1274,1284 ****
      if (parsedline->conntype != ctHostSSL &&
          parsedline->auth_method == uaCert)
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("cert authentication is only supported on hostssl connections"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return NULL;
      }

--- 1428,1439 ----
      if (parsedline->conntype != ctHostSSL &&
          parsedline->auth_method == uaCert)
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("cert authentication is only supported on hostssl connections"),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = "cert authentication is only supported on hostssl connections";
          return NULL;
      }

*************** parse_hba_line(TokenizedLine *tok_line)
*** 1323,1338 ****
                  /*
                   * Got something that's not a name=value pair.
                   */
!                 ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("authentication option not in name=value format: %s", token->string),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  return NULL;
              }

              *val++ = '\0';        /* str now holds "name", val holds "value" */
!             if (!parse_hba_auth_opt(str, val, parsedline, line_num))
                  /* parse_hba_auth_opt already logged the error message */
                  return NULL;
              pfree(str);
--- 1478,1495 ----
                  /*
                   * Got something that's not a name=value pair.
                   */
!                 ereport(elevel,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("authentication option not in name=value format: %s", token->string),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 *err_msg = psprintf("authentication option not in name=value format: %s",
+                                     token->string);
                  return NULL;
              }

              *val++ = '\0';        /* str now holds "name", val holds "value" */
!             if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
                  /* parse_hba_auth_opt already logged the error message */
                  return NULL;
              pfree(str);
*************** parse_hba_line(TokenizedLine *tok_line)
*** 1360,1380 ****
                  parsedline->ldapbindpasswd ||
                  parsedline->ldapsearchattribute)
              {
!                 ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl
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;
          }
      }
--- 1517,1539 ----
                  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
--- 1558,1572 ----
  /*
   * Parse one name-value pair as an authentication option into the given
   * HbaLine.  Return true if we successfully parse the option, false if we
!  * encounter an error.  In the event of an error, also log a message at
!  * ereport level elevel, and store a message string into *err_msg.
   */
  static bool
! parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
!                    int elevel, char **err_msg)
  {
+     int            line_num = hbaline->linenumber;
+
  #ifdef USE_LDAP
      hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
  #endif
*************** parse_hba_auth_opt(char *name, char *val
*** 1422,1432 ****
      {
          if (hbaline->conntype != ctHostSSL)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
              errmsg("clientcert can only be configured for \"hostssl\" rows"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              return false;
          }
          if (strcmp(val, "1") == 0)
--- 1585,1596 ----
      {
          if (hbaline->conntype != ctHostSSL)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
              errmsg("clientcert can only be configured for \"hostssl\" rows"),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = "clientcert can only be configured for \"hostssl\" rows";
              return false;
          }
          if (strcmp(val, "1") == 0)
*************** parse_hba_auth_opt(char *name, char *val
*** 1437,1447 ****
          {
              if (hbaline->auth_method == uaCert)
              {
!                 ereport(LOG,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
                  return false;
              }
              hbaline->clientcert = false;
--- 1601,1612 ----
          {
              if (hbaline->auth_method == uaCert)
              {
!                 ereport(elevel,
                          (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
                           errcontext("line %d of configuration file \"%s\"",
                                      line_num, HbaFileName)));
+                 *err_msg = "clientcert can not be set to 0 when using \"cert\" authentication";
                  return false;
              }
              hbaline->clientcert = false;
*************** parse_hba_auth_opt(char *name, char *val
*** 1473,1489 ****
          rc = ldap_url_parse(val, &urldata);
          if (rc != LDAP_SUCCESS)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
              return false;
          }

          if (strcmp(urldata->lud_scheme, "ldap") != 0)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
              errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
              ldap_free_urldesc(urldata);
              return false;
          }
--- 1638,1658 ----
          rc = ldap_url_parse(val, &urldata);
          if (rc != LDAP_SUCCESS)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
+             *err_msg = psprintf("could not parse LDAP URL \"%s\": %s",
+                                 val, ldap_err2string(rc));
              return false;
          }

          if (strcmp(urldata->lud_scheme, "ldap") != 0)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
              errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
+             *err_msg = psprintf("unsupported LDAP URL scheme: %s",
+                                 urldata->lud_scheme);
              ldap_free_urldesc(urldata);
              return false;
          }
*************** parse_hba_auth_opt(char *name, char *val
*** 1497,1513 ****
          hbaline->ldapscope = urldata->lud_scope;
          if (urldata->lud_filter)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("filters not supported in LDAP URLs")));
              ldap_free_urldesc(urldata);
              return false;
          }
          ldap_free_urldesc(urldata);
  #else                            /* not OpenLDAP */
!         ereport(LOG,
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                   errmsg("LDAP URLs not supported on this platform")));
  #endif   /* not OpenLDAP */
      }
      else if (strcmp(name, "ldaptls") == 0)
--- 1666,1684 ----
          hbaline->ldapscope = urldata->lud_scope;
          if (urldata->lud_filter)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("filters not supported in LDAP URLs")));
+             *err_msg = "filters not supported in LDAP URLs";
              ldap_free_urldesc(urldata);
              return false;
          }
          ldap_free_urldesc(urldata);
  #else                            /* not OpenLDAP */
!         ereport(elevel,
                  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                   errmsg("LDAP URLs not supported on this platform")));
+         *err_msg = "LDAP URLs not supported on this platform";
  #endif   /* not OpenLDAP */
      }
      else if (strcmp(name, "ldaptls") == 0)
*************** parse_hba_auth_opt(char *name, char *val
*** 1529,1539 ****
          hbaline->ldapport = atoi(val);
          if (hbaline->ldapport == 0)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("invalid LDAP port number: \"%s\"", val),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              return false;
          }
      }
--- 1700,1711 ----
          hbaline->ldapport = atoi(val);
          if (hbaline->ldapport == 0)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("invalid LDAP port number: \"%s\"", val),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = psprintf("invalid LDAP port number: \"%s\"", val);
              return false;
          }
      }
*************** parse_hba_auth_opt(char *name, char *val
*** 1617,1628 ****
          ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
          if (ret || !gai_result)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("could not translate RADIUS server name \"%s\" to address: %s",
                              val, gai_strerror(ret)),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              if (gai_result)
                  pg_freeaddrinfo_all(hints.ai_family, gai_result);
              return false;
--- 1789,1802 ----
          ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
          if (ret || !gai_result)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("could not translate RADIUS server name \"%s\" to address: %s",
                              val, gai_strerror(ret)),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = psprintf("could not translate RADIUS server name \"%s\" to address: %s",
+                                 val, gai_strerror(ret));
              if (gai_result)
                  pg_freeaddrinfo_all(hints.ai_family, gai_result);
              return false;
*************** parse_hba_auth_opt(char *name, char *val
*** 1636,1646 ****
          hbaline->radiusport = atoi(val);
          if (hbaline->radiusport == 0)
          {
!             ereport(LOG,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("invalid RADIUS port number: \"%s\"", val),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
              return false;
          }
      }
--- 1810,1821 ----
          hbaline->radiusport = atoi(val);
          if (hbaline->radiusport == 0)
          {
!             ereport(elevel,
                      (errcode(ERRCODE_CONFIG_FILE_ERROR),
                       errmsg("invalid RADIUS port number: \"%s\"", val),
                       errcontext("line %d of configuration file \"%s\"",
                                  line_num, HbaFileName)));
+             *err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
              return false;
          }
      }
*************** parse_hba_auth_opt(char *name, char *val
*** 1656,1667 ****
      }
      else
      {
!         ereport(LOG,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("unrecognized authentication option name: \"%s\"",
                          name),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
          return false;
      }
      return true;
--- 1831,1844 ----
      }
      else
      {
!         ereport(elevel,
                  (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("unrecognized authentication option name: \"%s\"",
                          name),
                   errcontext("line %d of configuration file \"%s\"",
                              line_num, HbaFileName)));
+         *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+                             name);
          return false;
      }
      return true;
*************** load_hba(void)
*** 1794,1800 ****
          return false;
      }

!     linecxt = tokenize_file(HbaFileName, file, &hba_lines);
      FreeFile(file);

      /* Now parse all the lines */
--- 1971,1977 ----
          return false;
      }

!     linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
      FreeFile(file);

      /* Now parse all the lines */
*************** load_hba(void)
*** 1808,1828 ****
          TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
          HbaLine    *newline;

!         if ((newline = parse_hba_line(tok_line)) == NULL)
          {
!             /*
!              * Parse error in the file, so indicate there's a problem.  NB: a
!              * problem in a line will free the memory for all previous lines
!              * as well!
!              */
!             MemoryContextReset(hbacxt);
!             new_parsed_lines = NIL;
              ok = false;

              /*
               * Keep parsing the rest of the file so we can report errors on
!              * more than the first row. Error has already been reported in the
!              * parsing function, so no need to log it here.
               */
              continue;
          }
--- 1985,2006 ----
          TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
          HbaLine    *newline;

!         /* don't parse lines that already have errors */
!         if (tok_line->err_msg != NULL)
          {
!             ok = false;
!             continue;
!         }
!
!         if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
!         {
!             /* Parse error; remember there's trouble */
              ok = false;

              /*
               * Keep parsing the rest of the file so we can report errors on
!              * more than the first line.  Error has already been logged, no
!              * need for more chatter here.
               */
              continue;
          }
*************** load_hba(void)
*** 1865,1874 ****
  }

  /*
   * Parse one tokenised line from the ident config file and store the result in
   * an IdentLine structure.
   *
!  * Return NULL if parsing fails.
   *
   * If ident_user is a regular expression (ie. begins with a slash), it is
   * compiled and stored in IdentLine structure.
--- 2043,2454 ----
  }

  /*
+  * This macro specifies the maximum number of authentication options
+  * that are possible with any given authentication method that is supported.
+  * Currently LDAP supports 10, so the macro value is well above the most any
+  * method needs.
+  */
+ #define MAX_HBA_OPTIONS 12
+
+ /*
+  * Create a text array listing the options specified in the HBA line.
+  * Return NULL if no options are specified.
+  */
+ static ArrayType *
+ gethba_options(HbaLine *hba)
+ {
+     int            noptions;
+     Datum        options[MAX_HBA_OPTIONS];
+
+     noptions = 0;
+
+     if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+     {
+         if (hba->include_realm)
+             options[noptions++] =
+                 CStringGetTextDatum("include_realm=true");
+
+         if (hba->krb_realm)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm));
+     }
+
+     if (hba->usermap)
+         options[noptions++] =
+             CStringGetTextDatum(psprintf("map=%s", hba->usermap));
+
+     if (hba->clientcert)
+         options[noptions++] =
+             CStringGetTextDatum("clientcert=true");
+
+     if (hba->pamservice)
+         options[noptions++] =
+             CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice));
+
+     if (hba->auth_method == uaLDAP)
+     {
+         if (hba->ldapserver)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver));
+
+         if (hba->ldapport)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport));
+
+         if (hba->ldaptls)
+             options[noptions++] =
+                 CStringGetTextDatum("ldaptls=true");
+
+         if (hba->ldapprefix)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix));
+
+         if (hba->ldapsuffix)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix));
+
+         if (hba->ldapbasedn)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn));
+
+         if (hba->ldapbinddn)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn));
+
+         if (hba->ldapbindpasswd)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapbindpasswd=%s",
+                                              hba->ldapbindpasswd));
+
+         if (hba->ldapsearchattribute)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
+                                              hba->ldapsearchattribute));
+
+         if (hba->ldapscope)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
+     }
+
+     if (hba->auth_method == uaRADIUS)
+     {
+         if (hba->radiusserver)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("radiusserver=%s", hba->radiusserver));
+
+         if (hba->radiussecret)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("radiussecret=%s", hba->radiussecret));
+
+         if (hba->radiusidentifier)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("radiusidentifier=%s", hba->radiusidentifier));
+
+         if (hba->radiusport)
+             options[noptions++] =
+                 CStringGetTextDatum(psprintf("radiusport=%d", hba->radiusport));
+     }
+
+     Assert(noptions <= MAX_HBA_OPTIONS);
+
+     if (noptions > 0)
+         return construct_array(options, noptions, TEXTOID, -1, false, 'i');
+     else
+         return NULL;
+ }
+
+ /* Number of columns in pg_hba_file_rules view */
+ #define NUM_PG_HBA_FILE_RULES_ATTS     9
+
+ /*
+  * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore
+  *
+  * tuple_store: where to store data
+  * tupdesc: tuple descriptor for the view
+  * lineno: pg_hba.conf line number (must always be valid)
+  * hba: parsed line data (can be NULL, in which case err_msg should be set)
+  * err_msg: error message (NULL if none)
+  *
+  * Note: leaks memory, but we don't care since this is run in a short-lived
+  * memory context.
+  */
+ static void
+ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+               int lineno, HbaLine *hba, const char *err_msg)
+ {
+     Datum        values[NUM_PG_HBA_FILE_RULES_ATTS];
+     bool        nulls[NUM_PG_HBA_FILE_RULES_ATTS];
+     char        buffer[NI_MAXHOST];
+     HeapTuple    tuple;
+     int            index;
+     ListCell   *lc;
+     const char *typestr;
+     const char *addrstr;
+     const char *maskstr;
+     ArrayType  *options;
+
+     Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
+
+     memset(values, 0, sizeof(values));
+     memset(nulls, 0, sizeof(nulls));
+     index = 0;
+
+     /* line_number */
+     values[index++] = Int32GetDatum(lineno);
+
+     if (hba != NULL)
+     {
+         /* type */
+         /* Avoid a default: case so compiler will warn about missing cases */
+         typestr = NULL;
+         switch (hba->conntype)
+         {
+             case ctLocal:
+                 typestr = "local";
+                 break;
+             case ctHost:
+                 typestr = "host";
+                 break;
+             case ctHostSSL:
+                 typestr = "hostssl";
+                 break;
+             case ctHostNoSSL:
+                 typestr = "hostnossl";
+                 break;
+         }
+         if (typestr)
+             values[index++] = CStringGetTextDatum(typestr);
+         else
+             nulls[index++] = true;
+
+         /* database */
+         if (hba->databases)
+         {
+             /*
+              * Flatten HbaToken list to string list.  It might seem that we
+              * should re-quote any quoted tokens, but that has been rejected
+              * on the grounds that it makes it harder to compare the array
+              * elements to other system catalogs.  That makes entries like
+              * "all" or "samerole" formally ambiguous ... but users who name
+              * databases/roles that way are inflicting their own pain.
+              */
+             List       *names = NIL;
+
+             foreach(lc, hba->databases)
+             {
+                 HbaToken   *tok = lfirst(lc);
+
+                 names = lappend(names, tok->string);
+             }
+             values[index++] = PointerGetDatum(strlist_to_textarray(names));
+         }
+         else
+             nulls[index++] = true;
+
+         /* user */
+         if (hba->roles)
+         {
+             /* Flatten HbaToken list to string list; see comment above */
+             List       *roles = NIL;
+
+             foreach(lc, hba->roles)
+             {
+                 HbaToken   *tok = lfirst(lc);
+
+                 roles = lappend(roles, tok->string);
+             }
+             values[index++] = PointerGetDatum(strlist_to_textarray(roles));
+         }
+         else
+             nulls[index++] = true;
+
+         /* address and netmask */
+         /* Avoid a default: case so compiler will warn about missing cases */
+         addrstr = maskstr = NULL;
+         switch (hba->ip_cmp_method)
+         {
+             case ipCmpMask:
+                 if (hba->hostname)
+                 {
+                     addrstr = hba->hostname;
+                 }
+                 else
+                 {
+                     if (pg_getnameinfo_all(&hba->addr, sizeof(hba->addr),
+                                            buffer, sizeof(buffer),
+                                            NULL, 0,
+                                            NI_NUMERICHOST) == 0)
+                     {
+                         clean_ipv6_addr(hba->addr.ss_family, buffer);
+                         addrstr = pstrdup(buffer);
+                     }
+                     if (pg_getnameinfo_all(&hba->mask, sizeof(hba->mask),
+                                            buffer, sizeof(buffer),
+                                            NULL, 0,
+                                            NI_NUMERICHOST) == 0)
+                     {
+                         clean_ipv6_addr(hba->mask.ss_family, buffer);
+                         maskstr = pstrdup(buffer);
+                     }
+                 }
+                 break;
+             case ipCmpAll:
+                 addrstr = "all";
+                 break;
+             case ipCmpSameHost:
+                 addrstr = "samehost";
+                 break;
+             case ipCmpSameNet:
+                 addrstr = "samenet";
+                 break;
+         }
+         if (addrstr)
+             values[index++] = CStringGetTextDatum(addrstr);
+         else
+             nulls[index++] = true;
+         if (maskstr)
+             values[index++] = CStringGetTextDatum(maskstr);
+         else
+             nulls[index++] = true;
+
+         /* auth_method */
+         values[index++] = CStringGetTextDatum(UserAuthName[hba->auth_method]);
+
+         /* options */
+         options = gethba_options(hba);
+         if (options)
+             values[index++] = PointerGetDatum(options);
+         else
+             nulls[index++] = true;
+     }
+     else
+     {
+         /* no parsing result, so set relevant fields to nulls */
+         memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool));
+     }
+
+     /* error */
+     if (err_msg)
+         values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
+     else
+         nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
+
+     tuple = heap_form_tuple(tupdesc, values, nulls);
+     tuplestore_puttuple(tuple_store, tuple);
+ }
+
+ /*
+  * Read the pg_hba.conf file and fill the tuplestore with view records.
+  */
+ static void
+ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
+ {
+     FILE       *file;
+     List       *hba_lines = NIL;
+     ListCell   *line;
+     MemoryContext linecxt;
+     MemoryContext hbacxt;
+     MemoryContext oldcxt;
+
+     /*
+      * In the unlikely event that we can't open pg_hba.conf, we throw an
+      * error, rather than trying to report it via some sort of view entry.
+      * (Most other error conditions should result in a message in a view
+      * entry.)
+      */
+     file = AllocateFile(HbaFileName, "r");
+     if (file == NULL)
+         ereport(ERROR,
+                 (errcode_for_file_access(),
+                  errmsg("could not open configuration file \"%s\": %m",
+                         HbaFileName)));
+
+     linecxt = tokenize_file(HbaFileName, file, &hba_lines, DEBUG3);
+     FreeFile(file);
+
+     /* Now parse all the lines */
+     hbacxt = AllocSetContextCreate(CurrentMemoryContext,
+                                    "hba parser context",
+                                    ALLOCSET_SMALL_SIZES);
+     oldcxt = MemoryContextSwitchTo(hbacxt);
+     foreach(line, hba_lines)
+     {
+         TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
+         HbaLine    *hbaline = NULL;
+
+         /* don't parse lines that already have errors */
+         if (tok_line->err_msg == NULL)
+             hbaline = parse_hba_line(tok_line, DEBUG3);
+
+         fill_hba_line(tuple_store, tupdesc, tok_line->line_num,
+                       hbaline, tok_line->err_msg);
+     }
+
+     /* Free tokenizer memory */
+     MemoryContextDelete(linecxt);
+     /* Free parse_hba_line memory */
+     MemoryContextSwitchTo(oldcxt);
+     MemoryContextDelete(hbacxt);
+ }
+
+ /*
+  * SQL-accessible SRF to return all the entries in the pg_hba.conf file.
+  */
+ Datum
+ pg_hba_file_rules(PG_FUNCTION_ARGS)
+ {
+     Tuplestorestate *tuple_store;
+     TupleDesc    tupdesc;
+     MemoryContext old_cxt;
+     ReturnSetInfo *rsi;
+
+     /*
+      * We must use the Materialize mode to be safe against HBA file changes
+      * while the cursor is open. It's also more efficient than having to look
+      * up our current position in the parsed list every time.
+      */
+     rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+     /* Check to see if caller supports us returning a tuplestore */
+     if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                  errmsg("set-valued function called in context that cannot accept a set")));
+     if (!(rsi->allowedModes & SFRM_Materialize))
+         ereport(ERROR,
+                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                  errmsg("materialize mode required, but it is not " \
+                         "allowed in this context")));
+
+     rsi->returnMode = SFRM_Materialize;
+
+     /* Build a tuple descriptor for our result type */
+     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+         elog(ERROR, "return type must be a row type");
+
+     /* Build tuplestore to hold the result rows */
+     old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+     tuple_store =
+         tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+                               false, work_mem);
+     rsi->setDesc = tupdesc;
+     rsi->setResult = tuple_store;
+
+     MemoryContextSwitchTo(old_cxt);
+
+     /* Fill the tuplestore */
+     fill_hba_view(tuple_store, tupdesc);
+
+     PG_RETURN_NULL();
+ }
+
+
+ /*
   * Parse one tokenised line from the ident config file and store the result in
   * an IdentLine structure.
   *
!  * If parsing fails, log a message and return NULL.
   *
   * If ident_user is a regular expression (ie. begins with a slash), it is
   * compiled and stored in IdentLine structure.
*************** load_ident(void)
*** 2170,2176 ****
          return false;
      }

!     linecxt = tokenize_file(IdentFileName, file, &ident_lines);
      FreeFile(file);

      /* Now parse all the lines */
--- 2750,2756 ----
          return false;
      }

!     linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
      FreeFile(file);

      /* Now parse all the lines */
*************** load_ident(void)
*** 2183,2208 ****
      {
          TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);

          if ((newline = parse_ident_line(tok_line)) == NULL)
          {
!             /*
!              * Parse error in the file, so indicate there's a problem.  Free
!              * all the memory and regular expressions of lines parsed so far.
!              */
!             foreach(parsed_line_cell, new_parsed_lines)
!             {
!                 newline = (IdentLine *) lfirst(parsed_line_cell);
!                 if (newline->ident_user[0] == '/')
!                     pg_regfree(&newline->re);
!             }
!             MemoryContextReset(ident_context);
!             new_parsed_lines = NIL;
              ok = false;

              /*
               * Keep parsing the rest of the file so we can report errors on
!              * more than the first row. Error has already been reported in the
!              * parsing function, so no need to log it here.
               */
              continue;
          }
--- 2763,2784 ----
      {
          TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);

+         /* don't parse lines that already have errors */
+         if (tok_line->err_msg != NULL)
+         {
+             ok = false;
+             continue;
+         }
+
          if ((newline = parse_ident_line(tok_line)) == NULL)
          {
!             /* Parse error; remember there's trouble */
              ok = false;

              /*
               * Keep parsing the rest of the file so we can report errors on
!              * more than the first line.  Error has already been logged, no
!              * need for more chatter here.
               */
              continue;
          }
*************** load_ident(void)
*** 2216,2222 ****

      if (!ok)
      {
!         /* File contained one or more errors, so bail out */
          foreach(parsed_line_cell, new_parsed_lines)
          {
              newline = (IdentLine *) lfirst(parsed_line_cell);
--- 2792,2802 ----

      if (!ok)
      {
!         /*
!          * File contained one or more errors, so bail out, first being careful
!          * to clean up whatever we allocated.  Most stuff will go away via
!          * MemoryContextDelete, but we have to clean up regexes explicitly.
!          */
          foreach(parsed_line_cell, new_parsed_lines)
          {
              newline = (IdentLine *) lfirst(parsed_line_cell);
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 31c828a..05652e8 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2084 (  pg_show_all_se
*** 3076,3081 ****
--- 3076,3083 ----
  DESCR("SHOW ALL as a function");
  DATA(insert OID = 3329 (  pg_show_all_file_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 ""
"{25,23,23,25,25,16,25}""{o,o,o,o,o,o,o}" "{sourcefile,sourceline,seqno,name,setting,applied,error}" _null_ _null_
show_all_file_settings_null_ _null_ _null_ )); 
  DESCR("show config file settings");
+ DATA(insert OID = 3401 (  pg_hba_file_rules PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 ""
"{23,25,1009,1009,25,25,25,1009,25}""{o,o,o,o,o,o,o,o,o}"
"{line_number,type,database,user_name,address,netmask,auth_method,options,error}"_null_ _null_ pg_hba_file_rules _null_
_null__null_ )); 
+ DESCR("show pg_hba.conf rules");
  DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 ""
"{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}""{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}"
"{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}"
_null__null_ pg_lock_status _null_ _null_ _null_ )); 
  DESCR("view system lock information");
  DATA(insert OID = 2561 (  pg_blocking_pids PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 1007 "23" _null_ _null_ _null_
_null__null_ pg_blocking_pids _null_ _null_ _null_ )); 
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index dc7d257..893767f 100644
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 16,25 ****
  #include "regex/regex.h"


  typedef enum UserAuth
  {
      uaReject,
!     uaImplicitReject,
      uaTrust,
      uaIdent,
      uaPassword,
--- 16,31 ----
  #include "regex/regex.h"


+ /*
+  * The following enum represents the authentication methods that
+  * are supported by PostgreSQL.
+  *
+  * Note: keep this in sync with the UserAuthName array in hba.c.
+  */
  typedef enum UserAuth
  {
      uaReject,
!     uaImplicitReject,            /* Not a user-visible option */
      uaTrust,
      uaIdent,
      uaPassword,
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 60abcad..de5ae00 100644
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** pg_group| SELECT pg_authid.rolname AS gr
*** 1338,1343 ****
--- 1338,1353 ----
            WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
     FROM pg_authid
    WHERE (NOT pg_authid.rolcanlogin);
+ pg_hba_file_rules| SELECT a.line_number,
+     a.type,
+     a.database,
+     a.user_name,
+     a.address,
+     a.netmask,
+     a.auth_method,
+     a.options,
+     a.error
+    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
  pg_indexes| SELECT n.nspname AS schemaname,
      c.relname AS tablename,
      i.relname AS indexname,

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Tom Lane
Дата:
Сообщение: Re: [HACKERS] proposal: EXPLAIN ANALYZE formatting
Следующее
От: Cat
Дата:
Сообщение: Re: [HACKERS] One-shot expanded output in psql using \G