]> granicus.if.org Git - postgresql/commitdiff
Invent pg_hba_file_rules view to show the content of pg_hba.conf.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 30 Jan 2017 23:00:26 +0000 (18:00 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 30 Jan 2017 23:00:26 +0000 (18:00 -0500)
This view is designed along the same lines as pg_file_settings, to wit
it shows what is currently in the file, not what the postmaster has
loaded as the active settings.  That allows it to be used to pre-vet
edits before issuing SIGHUP.  As with the earlier view, go out of our
way to allow errors in the file to be reflected in the view, to assist
that use-case.

(We might at some point invent a view to show the current active settings,
but this is not that patch; and it's not trivial to do.)

Haribabu Kommi, reviewed by Ashutosh Bapat, Michael Paquier, Simon Riggs,
and myself

Discussion: https://postgr.es/m/CAJrrPGerH4jiwpcXT1-46QXUDmNp2QDrG9+-Tek_xC8APHShYw@mail.gmail.com

doc/src/sgml/catalogs.sgml
doc/src/sgml/client-auth.sgml
src/backend/catalog/system_views.sql
src/backend/libpq/hba.c
src/include/catalog/catversion.h
src/include/catalog/pg_proc.h
src/include/libpq/hba.h
src/test/regress/expected/rules.out
src/test/regress/expected/sysviews.out
src/test/regress/sql/sysviews.sql

index 086fafc694475a62c0dcb40ae3486ee25a02f879..204b8cfd55ec523ea3332cfe48f7ee50aa68f1a4 100644 (file)
       <entry>groups of database users</entry>
      </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>
 
  </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>
 
index dda5891900406c884dd9fa2650e658a47bd3437e..231fc40fc3067dd133789d2102aef4f78ebadd1d 100644 (file)
@@ -597,6 +597,24 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
    re-read the file.
   </para>
 
+  <note>
+   <para>
+    The preceding statement is not true on Microsoft Windows: there, any
+    changes in the <filename>pg_hba.conf</filename> file are immediately
+    applied by subsequent new connections.
+   </para>
+  </note>
+
+  <para>
+   The system view
+   <link linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link>
+   can be helpful for pre-testing changes to the <filename>pg_hba.conf</>
+   file, or for diagnosing problems if loading of the file did not have the
+   desired effects.  Rows in the view with
+   non-null <structfield>error</structfield> fields indicate problems in the
+   corresponding lines of the file.
+  </para>
   <tip>
    <para>
     To connect to a particular database, a user must not only pass the
index 4dfedf89b611dff4a6a58600100fbdf38f48174e..28be27a07ecb6a8b63348416a782c122955a1e97 100644 (file)
@@ -459,6 +459,12 @@ CREATE VIEW pg_file_settings AS
 REVOKE ALL on pg_file_settings FROM PUBLIC;
 REVOKE EXECUTE ON FUNCTION pg_show_all_file_settings() FROM PUBLIC;
 
+CREATE VIEW pg_hba_file_rules AS
+   SELECT * FROM pg_hba_file_rules() AS A;
+
+REVOKE ALL on pg_hba_file_rules FROM PUBLIC;
+REVOKE EXECUTE ON FUNCTION pg_hba_file_rules() FROM PUBLIC;
+
 CREATE VIEW pg_timezone_abbrevs AS
     SELECT * FROM pg_timezone_abbrevs();
 
index bbe0a888ffc0a6e37836bb16bf61830da77cc826..5bb8eb93b10e422d84d63d0e5acb17e60a2d74d7 100644 (file)
 #include <arpa/inet.h>
 #include <unistd.h>
 
+#include "access/htup_details.h"
 #include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
 #include "common/ip.h"
+#include "funcapi.h"
 #include "libpq/ifaddr.h"
 #include "libpq/libpq.h"
+#include "miscadmin.h"
 #include "postmaster/postmaster.h"
 #include "regex/regex.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
 #include "utils/acl.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -80,12 +85,15 @@ typedef struct HbaToken
  * Each item in the "fields" list is a sub-list of HbaTokens.
  * We don't emit a TokenizedLine for empty or all-comment lines,
  * so "fields" is never NIL (nor are any of its sub-lists).
+ * Exception: if an error occurs during tokenization, we might
+ * have fields == NIL, in which case err_msg != NULL.
  */
 typedef struct TokenizedLine
 {
        List       *fields;                     /* List of lists of HbaTokens */
        int                     line_num;               /* Line number */
        char       *raw_line;           /* Raw line text */
+       char       *err_msg;            /* Error message if any */
 } TokenizedLine;
 
 /*
@@ -106,13 +114,42 @@ static MemoryContext parsed_hba_context = NULL;
 static List *parsed_ident_lines = NIL;
 static MemoryContext parsed_ident_context = NULL;
 
+/*
+ * The following character array represents the names of the authentication
+ * methods that are supported by PostgreSQL.
+ *
+ * Note: keep this in sync with the UserAuth enum in hba.h.
+ */
+static const char *const UserAuthName[] =
+{
+       "reject",
+       "implicit reject",                      /* Not a user-visible option */
+       "trust",
+       "ident",
+       "password",
+       "md5",
+       "gss",
+       "sspi",
+       "pam",
+       "bsd",
+       "ldap",
+       "cert",
+       "radius",
+       "peer"
+};
+
 
 static MemoryContext tokenize_file(const char *filename, FILE *file,
-                         List **tok_lines);
+                         List **tok_lines, int elevel);
 static List *tokenize_inc_file(List *tokens, const char *outer_filename,
-                                 const char *inc_filename);
+                                 const char *inc_filename, int elevel, char **err_msg);
 static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
-                                  int line_num);
+                                  int elevel, char **err_msg);
+static ArrayType *gethba_options(HbaLine *hba);
+static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+                         int lineno, HbaLine *hba, const char *err_msg);
+static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
+
 
 /*
  * isblank() exists in the ISO C99 spec, but it's not very portable yet,
@@ -126,32 +163,37 @@ pg_isblank(const char c)
 
 
 /*
- * Grab one token out of the string pointed to by lineptr.
+ * Grab one token out of the string pointed to by *lineptr.
+ *
  * Tokens are strings of non-blank
  * characters bounded by blank characters, commas, beginning of line, and
  * end of line. Blank means space or tab. Tokens can be delimited by
  * double quotes (this allows the inclusion of blanks, but not newlines).
+ * Comments (started by an unquoted '#') are skipped.
+ *
+ * The token, if any, is returned at *buf (a buffer of size bufsz), and
+ * *lineptr is advanced past the token.
  *
- * The token, if any, is returned at *buf (a buffer of size bufsz).
  * Also, we set *initial_quote to indicate whether there was quoting before
  * the first character.  (We use that to prevent "@x" from being treated
  * as a file inclusion request.  Note that @"x" should be so treated;
  * we want to allow that to support embedded spaces in file paths.)
+ *
  * We set *terminating_comma to indicate whether the token is terminated by a
- * comma (which is not returned.)
+ * comma (which is not returned).
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error.  Currently the only
+ * possible error is token too long for buf.
  *
  * If successful: store null-terminated token at *buf and return TRUE.
  * If no more tokens on line: set *buf = '\0' and return FALSE.
- *
- * Leave file positioned at the character immediately after the token or EOF,
- * whichever comes first. If no more tokens on line, position the file to the
- * beginning of the next line or EOF, whichever comes first.
- *
- * Handle comments.
+ * If error: fill buf with truncated or misformatted token and return FALSE.
  */
 static bool
-next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
-                  bool *terminating_comma)
+next_token(char **lineptr, char *buf, int bufsz,
+                  bool *initial_quote, bool *terminating_comma,
+                  int elevel, char **err_msg)
 {
        int                     c;
        char       *start_buf = buf;
@@ -197,14 +239,15 @@ next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
                if (buf >= end_buf)
                {
                        *buf = '\0';
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                           errmsg("authentication file token too long, skipping: \"%s\"",
                                          start_buf)));
+                       *err_msg = "authentication file token too long";
                        /* Discard remainder of line */
                        while ((c = (*(*lineptr)++)) != '\0' && c != '\n')
                                ;
-                       break;
+                       return false;
                }
 
                /* we do not pass back the comma in the token */
@@ -245,13 +288,17 @@ next_token(char **lineptr, char *buf, int bufsz, bool *initial_quote,
        return (saw_quote || buf > start_buf);
 }
 
+/*
+ * Construct a palloc'd HbaToken struct, copying the given string.
+ */
 static HbaToken *
-make_hba_token(char *token, bool quoted)
+make_hba_token(const char *token, bool quoted)
 {
        HbaToken   *hbatoken;
        int                     toklen;
 
        toklen = strlen(token);
+       /* we copy string into same palloc block as the struct */
        hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1);
        hbatoken->string = (char *) hbatoken + sizeof(HbaToken);
        hbatoken->quoted = quoted;
@@ -275,11 +322,20 @@ copy_hba_token(HbaToken *in)
 /*
  * Tokenize one HBA field from a line, handling file inclusion and comma lists.
  *
- * The result is a List of HbaToken structs for each individual token,
+ * filename: current file's pathname (needed to resolve relative pathnames)
+ * *lineptr: current line pointer, which will be advanced past field
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error.  Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
+ *
+ * The result is a List of HbaToken structs, one for each token in the field,
  * or NIL if we reached EOL.
  */
 static List *
-next_field_expand(const char *filename, char **lineptr)
+next_field_expand(const char *filename, char **lineptr,
+                                 int elevel, char **err_msg)
 {
        char            buf[MAX_TOKEN];
        bool            trailing_comma;
@@ -288,15 +344,18 @@ next_field_expand(const char *filename, char **lineptr)
 
        do
        {
-               if (!next_token(lineptr, buf, sizeof(buf), &initial_quote, &trailing_comma))
+               if (!next_token(lineptr, buf, sizeof(buf),
+                                               &initial_quote, &trailing_comma,
+                                               elevel, err_msg))
                        break;
 
                /* Is this referencing a file? */
                if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
-                       tokens = tokenize_inc_file(tokens, filename, buf + 1);
+                       tokens = tokenize_inc_file(tokens, filename, buf + 1,
+                                                                          elevel, err_msg);
                else
                        tokens = lappend(tokens, make_hba_token(buf, initial_quote));
-       } while (trailing_comma);
+       } while (trailing_comma && (*err_msg == NULL));
 
        return tokens;
 }
@@ -307,13 +366,21 @@ next_field_expand(const char *filename, char **lineptr)
  *
  * Opens and tokenises a file included from another HBA config file with @,
  * and returns all values found therein as a flat list of HbaTokens.  If a
- * @-token is found, recursively expand it.  The given token list is used as
- * initial contents of list (so foo,bar,@baz does what you expect).
+ * @-token is found, recursively expand it.  The newly read tokens are
+ * appended to "tokens" (so that foo,bar,@baz does what you expect).
+ * All new tokens are allocated in caller's memory context.
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error.  Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
  */
 static List *
 tokenize_inc_file(List *tokens,
                                  const char *outer_filename,
-                                 const char *inc_filename)
+                                 const char *inc_filename,
+                                 int elevel,
+                                 char **err_msg)
 {
        char       *inc_fullname;
        FILE       *inc_file;
@@ -340,16 +407,20 @@ tokenize_inc_file(List *tokens,
        inc_file = AllocateFile(inc_fullname, "r");
        if (inc_file == NULL)
        {
-               ereport(LOG,
+               int                     save_errno = errno;
+
+               ereport(elevel,
                                (errcode_for_file_access(),
                                 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
                                                inc_filename, inc_fullname)));
+               *err_msg = psprintf("could not open secondary authentication file \"@%s\" as \"%s\": %s",
+                                                       inc_filename, inc_fullname, strerror(save_errno));
                pfree(inc_fullname);
                return tokens;
        }
 
        /* There is possible recursion here if the file contains @ */
-       linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines);
+       linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, elevel);
 
        FreeFile(inc_file);
        pfree(inc_fullname);
@@ -360,6 +431,13 @@ tokenize_inc_file(List *tokens,
                TokenizedLine *tok_line = (TokenizedLine *) lfirst(inc_line);
                ListCell   *inc_field;
 
+               /* If any line has an error, propagate that up to caller */
+               if (tok_line->err_msg)
+               {
+                       *err_msg = pstrdup(tok_line->err_msg);
+                       break;
+               }
+
                foreach(inc_field, tok_line->fields)
                {
                        List       *inc_tokens = lfirst(inc_field);
@@ -383,13 +461,20 @@ tokenize_inc_file(List *tokens,
  *
  * The output is a list of TokenizedLine structs; see struct definition above.
  *
- * filename must be the absolute path to the target file.
+ * filename: the absolute path to the target file
+ * file: the already-opened target file
+ * tok_lines: receives output list
+ * elevel: message logging level
+ *
+ * Errors are reported by logging messages at ereport level elevel and by
+ * adding TokenizedLine structs containing non-null err_msg fields to the
+ * output list.
  *
  * Return value is a memory context which contains all memory allocated by
  * this function (it's a child of caller's context).
  */
 static MemoryContext
-tokenize_file(const char *filename, FILE *file, List **tok_lines)
+tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
 {
        int                     line_number = 1;
        MemoryContext linecxt;
@@ -407,16 +492,32 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines)
                char            rawline[MAX_LINE];
                char       *lineptr;
                List       *current_line = NIL;
+               char       *err_msg = NULL;
 
                if (!fgets(rawline, sizeof(rawline), file))
-                       break;
+               {
+                       int                     save_errno = errno;
+
+                       if (!ferror(file))
+                               break;                  /* normal EOF */
+                       /* I/O error! */
+                       ereport(elevel,
+                                       (errcode_for_file_access(),
+                                        errmsg("could not read file \"%s\": %m", filename)));
+                       err_msg = psprintf("could not read file \"%s\": %s",
+                                                          filename, strerror(save_errno));
+                       rawline[0] = '\0';
+               }
                if (strlen(rawline) == MAX_LINE - 1)
+               {
                        /* Line too long! */
-                       ereport(ERROR,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("authentication file line too long"),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_number, filename)));
+                       err_msg = "authentication file line too long";
+               }
 
                /* Strip trailing linebreak from rawline */
                lineptr = rawline + strlen(rawline) - 1;
@@ -425,18 +526,19 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines)
 
                /* Parse fields */
                lineptr = rawline;
-               while (*lineptr)
+               while (*lineptr && err_msg == NULL)
                {
                        List       *current_field;
 
-                       current_field = next_field_expand(filename, &lineptr);
+                       current_field = next_field_expand(filename, &lineptr,
+                                                                                         elevel, &err_msg);
                        /* add field to line, unless we are at EOL or comment start */
                        if (current_field != NIL)
                                current_line = lappend(current_line, current_field);
                }
 
                /* Reached EOL; emit line to TokenizedLine list unless it's boring */
-               if (current_line != NIL)
+               if (current_line != NIL || err_msg != NULL)
                {
                        TokenizedLine *tok_line;
 
@@ -444,6 +546,7 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines)
                        tok_line->fields = current_line;
                        tok_line->line_num = line_number;
                        tok_line->raw_line = pstrdup(rawline);
+                       tok_line->err_msg = err_msg;
                        *tok_lines = lappend(*tok_lines, tok_line);
                }
 
@@ -746,6 +849,10 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
 
 /*
  * Macros used to check and report on invalid configuration options.
+ * On error: log a message at level elevel, set *err_msg, and exit the function.
+ * These macros are not as general-purpose as they look, because they know
+ * what the calling function's error-exit value is.
+ *
  * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
  *                                              not supported.
  * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
@@ -754,44 +861,56 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
  * MANDATORY_AUTH_ARG  = check if a required option is set for an authentication method,
  *                                              reporting error if it's not.
  */
-#define INVALID_AUTH_OPTION(optname, validmethods) do {\
-       ereport(LOG, \
+#define INVALID_AUTH_OPTION(optname, validmethods) \
+do { \
+       ereport(elevel, \
                        (errcode(ERRCODE_CONFIG_FILE_ERROR), \
                         /* translator: the second %s is a list of auth methods */ \
                         errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
                                        optname, _(validmethods)), \
                         errcontext("line %d of configuration file \"%s\"", \
                                        line_num, HbaFileName))); \
+       *err_msg = psprintf("authentication option \"%s\" is only valid for authentication methods %s", \
+                                               optname, validmethods); \
        return false; \
-} while (0);
+} while (0)
 
-#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
+#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
+do { \
        if (hbaline->auth_method != methodval) \
                INVALID_AUTH_OPTION(optname, validmethods); \
-} while (0);
+} while (0)
 
-#define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\
-       if (argvar == NULL) {\
-               ereport(LOG, \
+#define MANDATORY_AUTH_ARG(argvar, argname, authname) \
+do { \
+       if (argvar == NULL) { \
+               ereport(elevel, \
                                (errcode(ERRCODE_CONFIG_FILE_ERROR), \
                                 errmsg("authentication method \"%s\" requires argument \"%s\" to be set", \
                                                authname, argname), \
                                 errcontext("line %d of configuration file \"%s\"", \
                                                line_num, HbaFileName))); \
+               *err_msg = psprintf("authentication method \"%s\" requires argument \"%s\" to be set", \
+                                                       authname, argname); \
                return NULL; \
        } \
-} while (0);
+} while (0)
 
 /*
+ * Macros for handling pg_ident problems.
+ * Much as above, but currently the message level is hardwired as LOG
+ * and there is no provision for an err_msg string.
+ *
  * IDENT_FIELD_ABSENT:
- * Throw an error and exit the function if the given ident field ListCell is
+ * Log a message and exit the function if the given ident field ListCell is
  * not populated.
  *
  * IDENT_MULTI_VALUE:
- * Throw an error and exit the function if the given ident token List has more
+ * Log a message and exit the function if the given ident token List has more
  * than one element.
  */
-#define IDENT_FIELD_ABSENT(field) do {\
+#define IDENT_FIELD_ABSENT(field) \
+do { \
        if (!field) { \
                ereport(LOG, \
                                (errcode(ERRCODE_CONFIG_FILE_ERROR), \
@@ -799,9 +918,10 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
                                                IdentFileName, line_num))); \
                return NULL; \
        } \
-} while (0);
+} while (0)
 
-#define IDENT_MULTI_VALUE(tokens) do {\
+#define IDENT_MULTI_VALUE(tokens) \
+do { \
        if (tokens->length > 1) { \
                ereport(LOG, \
                                (errcode(ERRCODE_CONFIG_FILE_ERROR), \
@@ -810,23 +930,26 @@ check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
                                                        line_num, IdentFileName))); \
                return NULL; \
        } \
-} while (0);
+} while (0)
 
 
 /*
  * Parse one tokenised line from the hba config file and store the result in a
  * HbaLine structure.
  *
- * Return NULL if parsing fails.
+ * If parsing fails, log a message at ereport level elevel, store an error
+ * string in tok_line->err_msg, and return NULL.  (Some non-error conditions
+ * can also result in such messages.)
  *
  * Note: this function leaks memory when an error occurs.  Caller is expected
  * to have set a memory context that will be reset if this function returns
  * NULL.
  */
 static HbaLine *
-parse_hba_line(TokenizedLine *tok_line)
+parse_hba_line(TokenizedLine *tok_line, int elevel)
 {
        int                     line_num = tok_line->line_num;
+       char      **err_msg = &tok_line->err_msg;
        char       *str;
        struct addrinfo *gai_result;
        struct addrinfo hints;
@@ -849,12 +972,13 @@ parse_hba_line(TokenizedLine *tok_line)
        tokens = lfirst(field);
        if (tokens->length > 1)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("multiple values specified for connection type"),
                                 errhint("Specify exactly one connection type per line."),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "multiple values specified for connection type";
                return NULL;
        }
        token = linitial(tokens);
@@ -863,11 +987,12 @@ parse_hba_line(TokenizedLine *tok_line)
 #ifdef HAVE_UNIX_SOCKETS
                parsedline->conntype = ctLocal;
 #else
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("local connections are not supported by this build"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "local connections are not supported by this build";
                return NULL;
 #endif
        }
@@ -882,19 +1007,23 @@ parse_hba_line(TokenizedLine *tok_line)
                        /* Log a warning if SSL support is not active */
 #ifdef USE_SSL
                        if (!EnableSSL)
-                               ereport(LOG,
+                       {
+                               ereport(elevel,
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                errmsg("hostssl record cannot match because SSL is disabled"),
                                                 errhint("Set ssl = on in postgresql.conf."),
                                                 errcontext("line %d of configuration file \"%s\"",
                                                                        line_num, HbaFileName)));
+                               *err_msg = "hostssl record cannot match because SSL is disabled";
+                       }
 #else
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("hostssl record cannot match because SSL is not supported by this build"),
                          errhint("Compile with --with-openssl to use SSL connections."),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = "hostssl record cannot match because SSL is not supported by this build";
 #endif
                }
                else if (token->string[4] == 'n')               /* "hostnossl" */
@@ -909,12 +1038,13 @@ parse_hba_line(TokenizedLine *tok_line)
        }                                                       /* record type */
        else
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("invalid connection type \"%s\"",
                                                token->string),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = psprintf("invalid connection type \"%s\"", token->string);
                return NULL;
        }
 
@@ -922,11 +1052,12 @@ parse_hba_line(TokenizedLine *tok_line)
        field = lnext(field);
        if (!field)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("end-of-line before database specification"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "end-of-line before database specification";
                return NULL;
        }
        parsedline->databases = NIL;
@@ -941,11 +1072,12 @@ parse_hba_line(TokenizedLine *tok_line)
        field = lnext(field);
        if (!field)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("end-of-line before role specification"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "end-of-line before role specification";
                return NULL;
        }
        parsedline->roles = NIL;
@@ -962,22 +1094,24 @@ parse_hba_line(TokenizedLine *tok_line)
                field = lnext(field);
                if (!field)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("end-of-line before IP address specification"),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = "end-of-line before IP address specification";
                        return NULL;
                }
                tokens = lfirst(field);
                if (tokens->length > 1)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("multiple values specified for host address"),
                                         errhint("Specify one address range per line."),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = "multiple values specified for host address";
                        return NULL;
                }
                token = linitial(tokens);
@@ -1027,12 +1161,14 @@ parse_hba_line(TokenizedLine *tok_line)
                                parsedline->hostname = str;
                        else
                        {
-                               ereport(LOG,
+                               ereport(elevel,
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                 errmsg("invalid IP address \"%s\": %s",
                                                                str, gai_strerror(ret)),
                                                 errcontext("line %d of configuration file \"%s\"",
                                                                        line_num, HbaFileName)));
+                               *err_msg = psprintf("invalid IP address \"%s\": %s",
+                                                                       str, gai_strerror(ret));
                                if (gai_result)
                                        pg_freeaddrinfo_all(hints.ai_family, gai_result);
                                return NULL;
@@ -1045,24 +1181,28 @@ parse_hba_line(TokenizedLine *tok_line)
                        {
                                if (parsedline->hostname)
                                {
-                                       ereport(LOG,
+                                       ereport(elevel,
                                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                         errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
                                                                        token->string),
                                                   errcontext("line %d of configuration file \"%s\"",
                                                                          line_num, HbaFileName)));
+                                       *err_msg = psprintf("specifying both host name and CIDR mask is invalid: \"%s\"",
+                                                                               token->string);
                                        return NULL;
                                }
 
                                if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
                                                                                  parsedline->addr.ss_family) < 0)
                                {
-                                       ereport(LOG,
+                                       ereport(elevel,
                                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                         errmsg("invalid CIDR mask in address \"%s\"",
                                                                        token->string),
                                                   errcontext("line %d of configuration file \"%s\"",
                                                                          line_num, HbaFileName)));
+                                       *err_msg = psprintf("invalid CIDR mask in address \"%s\"",
+                                                                               token->string);
                                        return NULL;
                                }
                                pfree(str);
@@ -1074,22 +1214,24 @@ parse_hba_line(TokenizedLine *tok_line)
                                field = lnext(field);
                                if (!field)
                                {
-                                       ereport(LOG,
+                                       ereport(elevel,
                                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                  errmsg("end-of-line before netmask specification"),
                                                         errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
                                                   errcontext("line %d of configuration file \"%s\"",
                                                                          line_num, HbaFileName)));
+                                       *err_msg = "end-of-line before netmask specification";
                                        return NULL;
                                }
                                tokens = lfirst(field);
                                if (tokens->length > 1)
                                {
-                                       ereport(LOG,
+                                       ereport(elevel,
                                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                         errmsg("multiple values specified for netmask"),
                                                   errcontext("line %d of configuration file \"%s\"",
                                                                          line_num, HbaFileName)));
+                                       *err_msg = "multiple values specified for netmask";
                                        return NULL;
                                }
                                token = linitial(tokens);
@@ -1098,12 +1240,14 @@ parse_hba_line(TokenizedLine *tok_line)
                                                                                 &hints, &gai_result);
                                if (ret || !gai_result)
                                {
-                                       ereport(LOG,
+                                       ereport(elevel,
                                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                         errmsg("invalid IP mask \"%s\": %s",
                                                                        token->string, gai_strerror(ret)),
                                                   errcontext("line %d of configuration file \"%s\"",
                                                                          line_num, HbaFileName)));
+                                       *err_msg = psprintf("invalid IP mask \"%s\": %s",
+                                                                               token->string, gai_strerror(ret));
                                        if (gai_result)
                                                pg_freeaddrinfo_all(hints.ai_family, gai_result);
                                        return NULL;
@@ -1115,11 +1259,12 @@ parse_hba_line(TokenizedLine *tok_line)
 
                                if (parsedline->addr.ss_family != parsedline->mask.ss_family)
                                {
-                                       ereport(LOG,
+                                       ereport(elevel,
                                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                         errmsg("IP address and mask do not match"),
                                                   errcontext("line %d of configuration file \"%s\"",
                                                                          line_num, HbaFileName)));
+                                       *err_msg = "IP address and mask do not match";
                                        return NULL;
                                }
                        }
@@ -1130,22 +1275,24 @@ parse_hba_line(TokenizedLine *tok_line)
        field = lnext(field);
        if (!field)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("end-of-line before authentication method"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "end-of-line before authentication method";
                return NULL;
        }
        tokens = lfirst(field);
        if (tokens->length > 1)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("multiple values specified for authentication type"),
                                 errhint("Specify exactly one authentication type per line."),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "multiple values specified for authentication type";
                return NULL;
        }
        token = linitial(tokens);
@@ -1177,11 +1324,12 @@ parse_hba_line(TokenizedLine *tok_line)
        {
                if (Db_user_namespace)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = "MD5 authentication is not supported when \"db_user_namespace\" is enabled";
                        return NULL;
                }
                parsedline->auth_method = uaMD5;
@@ -1214,23 +1362,27 @@ parse_hba_line(TokenizedLine *tok_line)
                parsedline->auth_method = uaRADIUS;
        else
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("invalid authentication method \"%s\"",
                                                token->string),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = psprintf("invalid authentication method \"%s\"",
+                                                       token->string);
                return NULL;
        }
 
        if (unsupauth)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("invalid authentication method \"%s\": not supported by this build",
                                                token->string),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = psprintf("invalid authentication method \"%s\": not supported by this build",
+                                                       token->string);
                return NULL;
        }
 
@@ -1246,22 +1398,24 @@ parse_hba_line(TokenizedLine *tok_line)
        if (parsedline->conntype == ctLocal &&
                parsedline->auth_method == uaGSS)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                   errmsg("gssapi authentication is not supported on local sockets"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "gssapi authentication is not supported on local sockets";
                return NULL;
        }
 
        if (parsedline->conntype != ctLocal &&
                parsedline->auth_method == uaPeer)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                        errmsg("peer authentication is only supported on local sockets"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "peer authentication is only supported on local sockets";
                return NULL;
        }
 
@@ -1274,11 +1428,12 @@ parse_hba_line(TokenizedLine *tok_line)
        if (parsedline->conntype != ctHostSSL &&
                parsedline->auth_method == uaCert)
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("cert authentication is only supported on hostssl connections"),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = "cert authentication is only supported on hostssl connections";
                return NULL;
        }
 
@@ -1323,16 +1478,18 @@ parse_hba_line(TokenizedLine *tok_line)
                                /*
                                 * Got something that's not a name=value pair.
                                 */
-                               ereport(LOG,
+                               ereport(elevel,
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                 errmsg("authentication option not in name=value format: %s", token->string),
                                                 errcontext("line %d of configuration file \"%s\"",
                                                                        line_num, HbaFileName)));
+                               *err_msg = psprintf("authentication option not in name=value format: %s",
+                                                                       token->string);
                                return NULL;
                        }
 
                        *val++ = '\0';          /* str now holds "name", val holds "value" */
-                       if (!parse_hba_auth_opt(str, val, parsedline, line_num))
+                       if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
                                /* parse_hba_auth_opt already logged the error message */
                                return NULL;
                        pfree(str);
@@ -1360,21 +1517,23 @@ parse_hba_line(TokenizedLine *tok_line)
                                parsedline->ldapbindpasswd ||
                                parsedline->ldapsearchattribute)
                        {
-                               ereport(LOG,
+                               ereport(elevel,
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix"),
                                                 errcontext("line %d of configuration file \"%s\"",
                                                                        line_num, HbaFileName)));
+                               *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, or ldapurl together with ldapprefix";
                                return NULL;
                        }
                }
                else if (!parsedline->ldapbasedn)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
                        return NULL;
                }
        }
@@ -1399,11 +1558,15 @@ parse_hba_line(TokenizedLine *tok_line)
 /*
  * Parse one name-value pair as an authentication option into the given
  * HbaLine.  Return true if we successfully parse the option, false if we
- * encounter an error.
+ * encounter an error.  In the event of an error, also log a message at
+ * ereport level elevel, and store a message string into *err_msg.
  */
 static bool
-parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
+parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
+                                  int elevel, char **err_msg)
 {
+       int                     line_num = hbaline->linenumber;
+
 #ifdef USE_LDAP
        hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
 #endif
@@ -1422,11 +1585,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
        {
                if (hbaline->conntype != ctHostSSL)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                        errmsg("clientcert can only be configured for \"hostssl\" rows"),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = "clientcert can only be configured for \"hostssl\" rows";
                        return false;
                }
                if (strcmp(val, "1") == 0)
@@ -1437,11 +1601,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
                {
                        if (hbaline->auth_method == uaCert)
                        {
-                               ereport(LOG,
+                               ereport(elevel,
                                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                                 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
                                                 errcontext("line %d of configuration file \"%s\"",
                                                                        line_num, HbaFileName)));
+                               *err_msg = "clientcert can not be set to 0 when using \"cert\" authentication";
                                return false;
                        }
                        hbaline->clientcert = false;
@@ -1473,17 +1638,21 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
                rc = ldap_url_parse(val, &urldata);
                if (rc != LDAP_SUCCESS)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
+                       *err_msg = psprintf("could not parse LDAP URL \"%s\": %s",
+                                                               val, ldap_err2string(rc));
                        return false;
                }
 
                if (strcmp(urldata->lud_scheme, "ldap") != 0)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                        errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
+                       *err_msg = psprintf("unsupported LDAP URL scheme: %s",
+                                                               urldata->lud_scheme);
                        ldap_free_urldesc(urldata);
                        return false;
                }
@@ -1497,17 +1666,19 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
                hbaline->ldapscope = urldata->lud_scope;
                if (urldata->lud_filter)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("filters not supported in LDAP URLs")));
+                       *err_msg = "filters not supported in LDAP URLs";
                        ldap_free_urldesc(urldata);
                        return false;
                }
                ldap_free_urldesc(urldata);
 #else                                                  /* not OpenLDAP */
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 errmsg("LDAP URLs not supported on this platform")));
+               *err_msg = "LDAP URLs not supported on this platform";
 #endif   /* not OpenLDAP */
        }
        else if (strcmp(name, "ldaptls") == 0)
@@ -1529,11 +1700,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
                hbaline->ldapport = atoi(val);
                if (hbaline->ldapport == 0)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("invalid LDAP port number: \"%s\"", val),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = psprintf("invalid LDAP port number: \"%s\"", val);
                        return false;
                }
        }
@@ -1617,12 +1789,14 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
                ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
                if (ret || !gai_result)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("could not translate RADIUS server name \"%s\" to address: %s",
                                                        val, gai_strerror(ret)),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = psprintf("could not translate RADIUS server name \"%s\" to address: %s",
+                                                               val, gai_strerror(ret));
                        if (gai_result)
                                pg_freeaddrinfo_all(hints.ai_family, gai_result);
                        return false;
@@ -1636,11 +1810,12 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
                hbaline->radiusport = atoi(val);
                if (hbaline->radiusport == 0)
                {
-                       ereport(LOG,
+                       ereport(elevel,
                                        (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                         errmsg("invalid RADIUS port number: \"%s\"", val),
                                         errcontext("line %d of configuration file \"%s\"",
                                                                line_num, HbaFileName)));
+                       *err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
                        return false;
                }
        }
@@ -1656,12 +1831,14 @@ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
        }
        else
        {
-               ereport(LOG,
+               ereport(elevel,
                                (errcode(ERRCODE_CONFIG_FILE_ERROR),
                                 errmsg("unrecognized authentication option name: \"%s\"",
                                                name),
                                 errcontext("line %d of configuration file \"%s\"",
                                                        line_num, HbaFileName)));
+               *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+                                                       name);
                return false;
        }
        return true;
@@ -1794,7 +1971,7 @@ load_hba(void)
                return false;
        }
 
-       linecxt = tokenize_file(HbaFileName, file, &hba_lines);
+       linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
        FreeFile(file);
 
        /* Now parse all the lines */
@@ -1808,21 +1985,22 @@ load_hba(void)
                TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
                HbaLine    *newline;
 
-               if ((newline = parse_hba_line(tok_line)) == NULL)
+               /* don't parse lines that already have errors */
+               if (tok_line->err_msg != NULL)
                {
-                       /*
-                        * Parse error in the file, so indicate there's a problem.  NB: a
-                        * problem in a line will free the memory for all previous lines
-                        * as well!
-                        */
-                       MemoryContextReset(hbacxt);
-                       new_parsed_lines = NIL;
+                       ok = false;
+                       continue;
+               }
+
+               if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
+               {
+                       /* Parse error; remember there's trouble */
                        ok = false;
 
                        /*
                         * Keep parsing the rest of the file so we can report errors on
-                        * more than the first row. Error has already been reported in the
-                        * parsing function, so no need to log it here.
+                        * more than the first line.  Error has already been logged, no
+                        * need for more chatter here.
                         */
                        continue;
                }
@@ -1864,11 +2042,419 @@ load_hba(void)
        return true;
 }
 
+/*
+ * This macro specifies the maximum number of authentication options
+ * that are possible with any given authentication method that is supported.
+ * Currently LDAP supports 10, so the macro value is well above the most any
+ * method needs.
+ */
+#define MAX_HBA_OPTIONS 12
+
+/*
+ * Create a text array listing the options specified in the HBA line.
+ * Return NULL if no options are specified.
+ */
+static ArrayType *
+gethba_options(HbaLine *hba)
+{
+       int                     noptions;
+       Datum           options[MAX_HBA_OPTIONS];
+
+       noptions = 0;
+
+       if (hba->auth_method == uaGSS || hba->auth_method == uaSSPI)
+       {
+               if (hba->include_realm)
+                       options[noptions++] =
+                               CStringGetTextDatum("include_realm=true");
+
+               if (hba->krb_realm)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("krb_realm=%s", hba->krb_realm));
+       }
+
+       if (hba->usermap)
+               options[noptions++] =
+                       CStringGetTextDatum(psprintf("map=%s", hba->usermap));
+
+       if (hba->clientcert)
+               options[noptions++] =
+                       CStringGetTextDatum("clientcert=true");
+
+       if (hba->pamservice)
+               options[noptions++] =
+                       CStringGetTextDatum(psprintf("pamservice=%s", hba->pamservice));
+
+       if (hba->auth_method == uaLDAP)
+       {
+               if (hba->ldapserver)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapserver=%s", hba->ldapserver));
+
+               if (hba->ldapport)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapport=%d", hba->ldapport));
+
+               if (hba->ldaptls)
+                       options[noptions++] =
+                               CStringGetTextDatum("ldaptls=true");
+
+               if (hba->ldapprefix)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapprefix=%s", hba->ldapprefix));
+
+               if (hba->ldapsuffix)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapsuffix=%s", hba->ldapsuffix));
+
+               if (hba->ldapbasedn)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapbasedn=%s", hba->ldapbasedn));
+
+               if (hba->ldapbinddn)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapbinddn=%s", hba->ldapbinddn));
+
+               if (hba->ldapbindpasswd)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapbindpasswd=%s",
+                                                                                        hba->ldapbindpasswd));
+
+               if (hba->ldapsearchattribute)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapsearchattribute=%s",
+                                                                                        hba->ldapsearchattribute));
+
+               if (hba->ldapscope)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapscope=%d", hba->ldapscope));
+       }
+
+       if (hba->auth_method == uaRADIUS)
+       {
+               if (hba->radiusserver)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("radiusserver=%s", hba->radiusserver));
+
+               if (hba->radiussecret)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("radiussecret=%s", hba->radiussecret));
+
+               if (hba->radiusidentifier)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("radiusidentifier=%s", hba->radiusidentifier));
+
+               if (hba->radiusport)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("radiusport=%d", hba->radiusport));
+       }
+
+       Assert(noptions <= MAX_HBA_OPTIONS);
+
+       if (noptions > 0)
+               return construct_array(options, noptions, TEXTOID, -1, false, 'i');
+       else
+               return NULL;
+}
+
+/* Number of columns in pg_hba_file_rules view */
+#define NUM_PG_HBA_FILE_RULES_ATTS      9
+
+/*
+ * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore
+ *
+ * tuple_store: where to store data
+ * tupdesc: tuple descriptor for the view
+ * lineno: pg_hba.conf line number (must always be valid)
+ * hba: parsed line data (can be NULL, in which case err_msg should be set)
+ * err_msg: error message (NULL if none)
+ *
+ * Note: leaks memory, but we don't care since this is run in a short-lived
+ * memory context.
+ */
+static void
+fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+                         int lineno, HbaLine *hba, const char *err_msg)
+{
+       Datum           values[NUM_PG_HBA_FILE_RULES_ATTS];
+       bool            nulls[NUM_PG_HBA_FILE_RULES_ATTS];
+       char            buffer[NI_MAXHOST];
+       HeapTuple       tuple;
+       int                     index;
+       ListCell   *lc;
+       const char *typestr;
+       const char *addrstr;
+       const char *maskstr;
+       ArrayType  *options;
+
+       Assert(tupdesc->natts == NUM_PG_HBA_FILE_RULES_ATTS);
+
+       memset(values, 0, sizeof(values));
+       memset(nulls, 0, sizeof(nulls));
+       index = 0;
+
+       /* line_number */
+       values[index++] = Int32GetDatum(lineno);
+
+       if (hba != NULL)
+       {
+               /* type */
+               /* Avoid a default: case so compiler will warn about missing cases */
+               typestr = NULL;
+               switch (hba->conntype)
+               {
+                       case ctLocal:
+                               typestr = "local";
+                               break;
+                       case ctHost:
+                               typestr = "host";
+                               break;
+                       case ctHostSSL:
+                               typestr = "hostssl";
+                               break;
+                       case ctHostNoSSL:
+                               typestr = "hostnossl";
+                               break;
+               }
+               if (typestr)
+                       values[index++] = CStringGetTextDatum(typestr);
+               else
+                       nulls[index++] = true;
+
+               /* database */
+               if (hba->databases)
+               {
+                       /*
+                        * Flatten HbaToken list to string list.  It might seem that we
+                        * should re-quote any quoted tokens, but that has been rejected
+                        * on the grounds that it makes it harder to compare the array
+                        * elements to other system catalogs.  That makes entries like
+                        * "all" or "samerole" formally ambiguous ... but users who name
+                        * databases/roles that way are inflicting their own pain.
+                        */
+                       List       *names = NIL;
+
+                       foreach(lc, hba->databases)
+                       {
+                               HbaToken   *tok = lfirst(lc);
+
+                               names = lappend(names, tok->string);
+                       }
+                       values[index++] = PointerGetDatum(strlist_to_textarray(names));
+               }
+               else
+                       nulls[index++] = true;
+
+               /* user */
+               if (hba->roles)
+               {
+                       /* Flatten HbaToken list to string list; see comment above */
+                       List       *roles = NIL;
+
+                       foreach(lc, hba->roles)
+                       {
+                               HbaToken   *tok = lfirst(lc);
+
+                               roles = lappend(roles, tok->string);
+                       }
+                       values[index++] = PointerGetDatum(strlist_to_textarray(roles));
+               }
+               else
+                       nulls[index++] = true;
+
+               /* address and netmask */
+               /* Avoid a default: case so compiler will warn about missing cases */
+               addrstr = maskstr = NULL;
+               switch (hba->ip_cmp_method)
+               {
+                       case ipCmpMask:
+                               if (hba->hostname)
+                               {
+                                       addrstr = hba->hostname;
+                               }
+                               else
+                               {
+                                       if (pg_getnameinfo_all(&hba->addr, sizeof(hba->addr),
+                                                                                  buffer, sizeof(buffer),
+                                                                                  NULL, 0,
+                                                                                  NI_NUMERICHOST) == 0)
+                                       {
+                                               clean_ipv6_addr(hba->addr.ss_family, buffer);
+                                               addrstr = pstrdup(buffer);
+                                       }
+                                       if (pg_getnameinfo_all(&hba->mask, sizeof(hba->mask),
+                                                                                  buffer, sizeof(buffer),
+                                                                                  NULL, 0,
+                                                                                  NI_NUMERICHOST) == 0)
+                                       {
+                                               clean_ipv6_addr(hba->mask.ss_family, buffer);
+                                               maskstr = pstrdup(buffer);
+                                       }
+                               }
+                               break;
+                       case ipCmpAll:
+                               addrstr = "all";
+                               break;
+                       case ipCmpSameHost:
+                               addrstr = "samehost";
+                               break;
+                       case ipCmpSameNet:
+                               addrstr = "samenet";
+                               break;
+               }
+               if (addrstr)
+                       values[index++] = CStringGetTextDatum(addrstr);
+               else
+                       nulls[index++] = true;
+               if (maskstr)
+                       values[index++] = CStringGetTextDatum(maskstr);
+               else
+                       nulls[index++] = true;
+
+               /*
+                * Make sure UserAuthName[] tracks additions to the UserAuth enum
+                */
+               StaticAssertStmt(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
+                                                "UserAuthName[] must match the UserAuth enum");
+
+               /* auth_method */
+               values[index++] = CStringGetTextDatum(UserAuthName[hba->auth_method]);
+
+               /* options */
+               options = gethba_options(hba);
+               if (options)
+                       values[index++] = PointerGetDatum(options);
+               else
+                       nulls[index++] = true;
+       }
+       else
+       {
+               /* no parsing result, so set relevant fields to nulls */
+               memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * sizeof(bool));
+       }
+
+       /* error */
+       if (err_msg)
+               values[NUM_PG_HBA_FILE_RULES_ATTS - 1] = CStringGetTextDatum(err_msg);
+       else
+               nulls[NUM_PG_HBA_FILE_RULES_ATTS - 1] = true;
+
+       tuple = heap_form_tuple(tupdesc, values, nulls);
+       tuplestore_puttuple(tuple_store, tuple);
+}
+
+/*
+ * Read the pg_hba.conf file and fill the tuplestore with view records.
+ */
+static void
+fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
+{
+       FILE       *file;
+       List       *hba_lines = NIL;
+       ListCell   *line;
+       MemoryContext linecxt;
+       MemoryContext hbacxt;
+       MemoryContext oldcxt;
+
+       /*
+        * In the unlikely event that we can't open pg_hba.conf, we throw an
+        * error, rather than trying to report it via some sort of view entry.
+        * (Most other error conditions should result in a message in a view
+        * entry.)
+        */
+       file = AllocateFile(HbaFileName, "r");
+       if (file == NULL)
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not open configuration file \"%s\": %m",
+                                               HbaFileName)));
+
+       linecxt = tokenize_file(HbaFileName, file, &hba_lines, DEBUG3);
+       FreeFile(file);
+
+       /* Now parse all the lines */
+       hbacxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                  "hba parser context",
+                                                                  ALLOCSET_SMALL_SIZES);
+       oldcxt = MemoryContextSwitchTo(hbacxt);
+       foreach(line, hba_lines)
+       {
+               TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
+               HbaLine    *hbaline = NULL;
+
+               /* don't parse lines that already have errors */
+               if (tok_line->err_msg == NULL)
+                       hbaline = parse_hba_line(tok_line, DEBUG3);
+
+               fill_hba_line(tuple_store, tupdesc, tok_line->line_num,
+                                         hbaline, tok_line->err_msg);
+       }
+
+       /* Free tokenizer memory */
+       MemoryContextDelete(linecxt);
+       /* Free parse_hba_line memory */
+       MemoryContextSwitchTo(oldcxt);
+       MemoryContextDelete(hbacxt);
+}
+
+/*
+ * SQL-accessible SRF to return all the entries in the pg_hba.conf file.
+ */
+Datum
+pg_hba_file_rules(PG_FUNCTION_ARGS)
+{
+       Tuplestorestate *tuple_store;
+       TupleDesc       tupdesc;
+       MemoryContext old_cxt;
+       ReturnSetInfo *rsi;
+
+       /*
+        * We must use the Materialize mode to be safe against HBA file changes
+        * while the cursor is open. It's also more efficient than having to look
+        * up our current position in the parsed list every time.
+        */
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+       /* Check to see if caller supports us returning a tuplestore */
+       if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context that cannot accept a set")));
+       if (!(rsi->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("materialize mode required, but it is not " \
+                                               "allowed in this context")));
+
+       rsi->returnMode = SFRM_Materialize;
+
+       /* Build a tuple descriptor for our result type */
+       if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+               elog(ERROR, "return type must be a row type");
+
+       /* Build tuplestore to hold the result rows */
+       old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+       tuple_store =
+               tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+                                                         false, work_mem);
+       rsi->setDesc = tupdesc;
+       rsi->setResult = tuple_store;
+
+       MemoryContextSwitchTo(old_cxt);
+
+       /* Fill the tuplestore */
+       fill_hba_view(tuple_store, tupdesc);
+
+       PG_RETURN_NULL();
+}
+
+
 /*
  * Parse one tokenised line from the ident config file and store the result in
  * an IdentLine structure.
  *
- * Return NULL if parsing fails.
+ * If parsing fails, log a message and return NULL.
  *
  * If ident_user is a regular expression (ie. begins with a slash), it is
  * compiled and stored in IdentLine structure.
@@ -2170,7 +2756,7 @@ load_ident(void)
                return false;
        }
 
-       linecxt = tokenize_file(IdentFileName, file, &ident_lines);
+       linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
        FreeFile(file);
 
        /* Now parse all the lines */
@@ -2183,26 +2769,22 @@ load_ident(void)
        {
                TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);
 
+               /* don't parse lines that already have errors */
+               if (tok_line->err_msg != NULL)
+               {
+                       ok = false;
+                       continue;
+               }
+
                if ((newline = parse_ident_line(tok_line)) == NULL)
                {
-                       /*
-                        * Parse error in the file, so indicate there's a problem.  Free
-                        * all the memory and regular expressions of lines parsed so far.
-                        */
-                       foreach(parsed_line_cell, new_parsed_lines)
-                       {
-                               newline = (IdentLine *) lfirst(parsed_line_cell);
-                               if (newline->ident_user[0] == '/')
-                                       pg_regfree(&newline->re);
-                       }
-                       MemoryContextReset(ident_context);
-                       new_parsed_lines = NIL;
+                       /* Parse error; remember there's trouble */
                        ok = false;
 
                        /*
                         * Keep parsing the rest of the file so we can report errors on
-                        * more than the first row. Error has already been reported in the
-                        * parsing function, so no need to log it here.
+                        * more than the first line.  Error has already been logged, no
+                        * need for more chatter here.
                         */
                        continue;
                }
@@ -2216,7 +2798,11 @@ load_ident(void)
 
        if (!ok)
        {
-               /* File contained one or more errors, so bail out */
+               /*
+                * File contained one or more errors, so bail out, first being careful
+                * to clean up whatever we allocated.  Most stuff will go away via
+                * MemoryContextDelete, but we have to clean up regexes explicitly.
+                */
                foreach(parsed_line_cell, new_parsed_lines)
                {
                        newline = (IdentLine *) lfirst(parsed_line_cell);
index 417cfc36ec62a31936de3590b35d057ffbe6a8e5..82511c2db806af2fae4bb9e98aaa5bf65135d2fc 100644 (file)
@@ -53,6 +53,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     201701251
+#define CATALOG_VERSION_NO     201701301
 
 #endif
index 31c828a3f27d624fc36768c21706fcfd0f5f7e9f..05652e86c2dd22a6f6d856732c593b3d62f4c335 100644 (file)
@@ -3076,6 +3076,8 @@ DATA(insert OID = 2084 (  pg_show_all_settings    PGNSP PGUID 12 1 1000 0 0 f f f f
 DESCR("SHOW ALL as a function");
 DATA(insert OID = 3329 (  pg_show_all_file_settings PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,23,23,25,25,16,25}" "{o,o,o,o,o,o,o}" "{sourcefile,sourceline,seqno,name,setting,applied,error}" _null_ _null_ show_all_file_settings _null_ _null_ _null_ ));
 DESCR("show config file settings");
+DATA(insert OID = 3401 (  pg_hba_file_rules PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{23,25,1009,1009,25,25,25,1009,25}" "{o,o,o,o,o,o,o,o,o}" "{line_number,type,database,user_name,address,netmask,auth_method,options,error}" _null_ _null_ pg_hba_file_rules _null_ _null_ _null_ ));
+DESCR("show pg_hba.conf rules");
 DATA(insert OID = 1371 (  pg_lock_status   PGNSP PGUID 12 1 1000 0 0 f f f f t t v s 0 0 2249 "" "{25,26,26,23,21,25,28,26,26,21,25,23,25,16,16}" "{o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}" "{locktype,database,relation,page,tuple,virtualxid,transactionid,classid,objid,objsubid,virtualtransaction,pid,mode,granted,fastpath}" _null_ _null_ pg_lock_status _null_ _null_ _null_ ));
 DESCR("view system lock information");
 DATA(insert OID = 2561 (  pg_blocking_pids PGNSP PGUID 12 1 0 0 0 f f f f t f v s 1 0 1007 "23" _null_ _null_ _null_ _null_ _null_ pg_blocking_pids _null_ _null_ _null_ ));
index dc7d2572eaa29d2997060c2ec6aab9d42a3a9ee1..748a07285412e4ef4d48bc05b2509d555b1dd00a 100644 (file)
 #include "regex/regex.h"
 
 
+/*
+ * The following enum represents the authentication methods that
+ * are supported by PostgreSQL.
+ *
+ * Note: keep this in sync with the UserAuthName array in hba.c.
+ */
 typedef enum UserAuth
 {
        uaReject,
-       uaImplicitReject,
+       uaImplicitReject,                       /* Not a user-visible option */
        uaTrust,
        uaIdent,
        uaPassword,
@@ -32,6 +38,7 @@ typedef enum UserAuth
        uaCert,
        uaRADIUS,
        uaPeer
+#define USER_AUTH_LAST uaPeer  /* Must be last value of this enum */
 } UserAuth;
 
 typedef enum IPCompareMethod
index 60abcad1017aea67b9b950657546305d958b8e57..de5ae00970e998f3c61283d4e67920a70308e599 100644 (file)
@@ -1338,6 +1338,16 @@ pg_group| SELECT pg_authid.rolname AS groname,
           WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
    FROM pg_authid
   WHERE (NOT pg_authid.rolcanlogin);
+pg_hba_file_rules| SELECT a.line_number,
+    a.type,
+    a.database,
+    a.user_name,
+    a.address,
+    a.netmask,
+    a.auth_method,
+    a.options,
+    a.error
+   FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, netmask, auth_method, options, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     i.relname AS indexname,
index 852a7c347e1d3769a3e189629539463fd398b7fd..d48abd7e09281f80d72bbbb29c2fb154d2bfbea7 100644 (file)
@@ -39,6 +39,13 @@ select count(*) >= 0 as ok from pg_file_settings;
  t
 (1 row)
 
+-- There will surely be at least one rule
+select count(*) > 0 as ok from pg_hba_file_rules;
+ ok 
+----
+ t
+(1 row)
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;
  ok 
index 0941b6beac632775f45bf4c987c252c4dc6d696f..28e412b73530b83ebb594e5d551274ee9512e1ef 100644 (file)
@@ -20,6 +20,9 @@ select count(*) = 0 as ok from pg_cursors;
 
 select count(*) >= 0 as ok from pg_file_settings;
 
+-- There will surely be at least one rule
+select count(*) > 0 as ok from pg_hba_file_rules;
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;