Hi,

The cfbot says that the patch doesn't apply anymore, so here's a v3 with the
changes mentioned below.

On Tue, Mar 01, 2022 at 05:19:50PM +0800, Julien Rouhaud wrote:
>
> If you prefer to interleave static and non static function I can change it.

Change the split to not reorder functions.

> > +#include "utils/guc.h"
> > +//#include "utils/tuplestore.h"
>
> Yes I noticed this one this morning.  I didn't want to send a new patch 
> version
> just for that, but I already fixed it locally.

Included.

> Yes I'm aware of that thread.  I will be happy to change the patch to use
> MakeFuncResultTuplestore() as soon as it lands.  Thanks for the notice though.

Done, with the new SetSingleFuncCall().

> > It could be possible to do installcheck on an instance that has user
> > mappings meaning that this had better be ">= 0", no?
>
> I thought about it, and supposed it would bring a bit more value with the test
> like that.  I can change it if you prefer.

Change this way.
>From 865b181ea989bb5c52fad2c3c9e20a38aa173cf3 Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouh...@free.fr>
Date: Tue, 1 Mar 2022 21:45:42 +0800
Subject: [PATCH v3 1/4] Extract view processing code from hba.c

This file is already quite big and a following commit will add yet an
additional view, so let's move all the view related code in hba.c into a new
adt/hbafuncs.c.

Author: Julien Rouhaud
Reviewed-by: FIXME
Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud
---
 src/backend/libpq/hba.c          | 462 ++-----------------------------
 src/backend/utils/adt/Makefile   |   1 +
 src/backend/utils/adt/hbafuncs.c | 423 ++++++++++++++++++++++++++++
 src/include/libpq/hba.h          |  29 ++
 src/tools/pgindent/typedefs.list |   2 +-
 5 files changed, 475 insertions(+), 442 deletions(-)
 create mode 100644 src/backend/utils/adt/hbafuncs.c

diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 90953c38f3..f9843a0b30 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -68,32 +68,6 @@ typedef struct check_network_data
 #define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)
 #define token_matches(t, k)  (strcmp(t->string, k) == 0)
 
-/*
- * A single string token lexed from a config file, together with whether
- * the token had been quoted.
- */
-typedef struct HbaToken
-{
-       char       *string;
-       bool            quoted;
-} HbaToken;
-
-/*
- * TokenizedLine represents one line lexed from a config file.
- * 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;
-
 /*
  * pre-parsed content of HBA config file: list of HbaLine structs.
  * parsed_hba_context is the memory context where it lives.
@@ -138,16 +112,10 @@ static const char *const UserAuthName[] =
 };
 
 
-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);
 
 
 /*
@@ -419,7 +387,7 @@ tokenize_inc_file(List *tokens,
        }
 
        /* There is possible recursion here if the file contains @ */
-       linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, elevel);
+       linecxt = tokenize_auth_file(inc_fullname, inc_file, &inc_lines, 
elevel);
 
        FreeFile(inc_file);
        pfree(inc_fullname);
@@ -427,7 +395,7 @@ tokenize_inc_file(List *tokens,
        /* Copy all tokens found in the file and append to the tokens list */
        foreach(inc_line, inc_lines)
        {
-               TokenizedLine *tok_line = (TokenizedLine *) lfirst(inc_line);
+               TokenizedAuthLine *tok_line = (TokenizedAuthLine *) 
lfirst(inc_line);
                ListCell   *inc_field;
 
                /* If any line has an error, propagate that up to caller */
@@ -458,7 +426,8 @@ tokenize_inc_file(List *tokens,
 /*
  * Tokenize the given file.
  *
- * The output is a list of TokenizedLine structs; see struct definition above.
+ * The output is a list of TokenizedAuthLine structs; see struct definition
+ * above.
  *
  * filename: the absolute path to the target file
  * file: the already-opened target file
@@ -466,14 +435,15 @@ tokenize_inc_file(List *tokens,
  * 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
+ * adding TokenizedAuthLine 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)
+MemoryContext
+tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
+                                  int elevel)
 {
        int                     line_number = 1;
        StringInfoData buf;
@@ -481,7 +451,7 @@ tokenize_file(const char *filename, FILE *file, List 
**tok_lines, int elevel)
        MemoryContext oldcxt;
 
        linecxt = AllocSetContextCreate(CurrentMemoryContext,
-                                                                       
"tokenize_file",
+                                                                       
"tokenize_auth_file",
                                                                        
ALLOCSET_SMALL_SIZES);
        oldcxt = MemoryContextSwitchTo(linecxt);
 
@@ -550,12 +520,14 @@ tokenize_file(const char *filename, FILE *file, List 
**tok_lines, int elevel)
                                current_line = lappend(current_line, 
current_field);
                }
 
-               /* Reached EOL; emit line to TokenizedLine list unless it's 
boring */
+               /*
+                * Reached EOL; emit line to TokenizedAuthLine list unless it's 
boring
+                */
                if (current_line != NIL || err_msg != NULL)
                {
-                       TokenizedLine *tok_line;
+                       TokenizedAuthLine *tok_line;
 
-                       tok_line = (TokenizedLine *) 
palloc(sizeof(TokenizedLine));
+                       tok_line = (TokenizedAuthLine *) 
palloc(sizeof(TokenizedAuthLine));
                        tok_line->fields = current_line;
                        tok_line->line_num = line_number;
                        tok_line->raw_line = pstrdup(buf.data);
@@ -962,8 +934,8 @@ do { \
  * 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)
+HbaLine *
+parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
 {
        int                     line_num = tok_line->line_num;
        char      **err_msg = &tok_line->err_msg;
@@ -2257,7 +2229,7 @@ load_hba(void)
                return false;
        }
 
-       linecxt = tokenize_file(HbaFileName, file, &hba_lines, LOG);
+       linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, LOG);
        FreeFile(file);
 
        /* Now parse all the lines */
@@ -2268,7 +2240,7 @@ load_hba(void)
        oldcxt = MemoryContextSwitchTo(hbacxt);
        foreach(line, hba_lines)
        {
-               TokenizedLine *tok_line = (TokenizedLine *) lfirst(line);
+               TokenizedAuthLine *tok_line = (TokenizedAuthLine *) 
lfirst(line);
                HbaLine    *newline;
 
                /* don't parse lines that already have errors */
@@ -2328,398 +2300,6 @@ 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 11, and there are 3 that are not dependent on
- * the auth method here.  It may not actually be possible to set all of them
- * at the same time, but we'll set the macro value high enough to be
- * conservative and avoid warnings from static analysis tools.
- */
-#define MAX_HBA_OPTIONS 14
-
-/*
- * 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 != clientCertOff)
-               options[noptions++] =
-                       CStringGetTextDatum(psprintf("clientcert=%s", 
(hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
-
-       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->ldapsearchfilter)
-                       options[noptions++] =
-                               
CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
-                                                                               
         hba->ldapsearchfilter));
-
-               if (hba->ldapscope)
-                       options[noptions++] =
-                               CStringGetTextDatum(psprintf("ldapscope=%d", 
hba->ldapscope));
-       }
-
-       if (hba->auth_method == uaRADIUS)
-       {
-               if (hba->radiusservers_s)
-                       options[noptions++] =
-                               
CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
-
-               if (hba->radiussecrets_s)
-                       options[noptions++] =
-                               
CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
-
-               if (hba->radiusidentifiers_s)
-                       options[noptions++] =
-                               
CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
-
-               if (hba->radiusports_s)
-                       options[noptions++] =
-                               CStringGetTextDatum(psprintf("radiusports=%s", 
hba->radiusports_s));
-       }
-
-       /* If you add more options, consider increasing MAX_HBA_OPTIONS. */
-       Assert(noptions <= MAX_HBA_OPTIONS);
-
-       if (noptions > 0)
-               return construct_array(options, noptions, TEXTOID, -1, false, 
TYPALIGN_INT);
-       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;
-                       case ctHostGSS:
-                               typestr = "hostgssenc";
-                               break;
-                       case ctHostNoGSS:
-                               typestr = "hostnogssenc";
-                               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
-                               {
-                                       /*
-                                        * Note: if pg_getnameinfo_all fails, 
it'll set buffer to
-                                        * "???", which we want to return.
-                                        */
-                                       if (hba->addrlen > 0)
-                                       {
-                                               if 
(pg_getnameinfo_all(&hba->addr, hba->addrlen,
-                                                                               
           buffer, sizeof(buffer),
-                                                                               
           NULL, 0,
-                                                                               
           NI_NUMERICHOST) == 0)
-                                                       
clean_ipv6_addr(hba->addr.ss_family, buffer);
-                                               addrstr = pstrdup(buffer);
-                                       }
-                                       if (hba->masklen > 0)
-                                       {
-                                               if 
(pg_getnameinfo_all(&hba->mask, hba->masklen,
-                                                                               
           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(hba_authname(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)
-{
-       ReturnSetInfo *rsi;
-
-       /*
-        * Build tuplestore to hold the result rows.  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.
-        */
-       SetSingleFuncCall(fcinfo, 0);
-
-       /* Fill the tuplestore */
-       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
-       fill_hba_view(rsi->setResult, rsi->setDesc);
-
-       PG_RETURN_NULL();
-}
-
 
 /*
  * Parse one tokenised line from the ident config file and store the result in
@@ -2735,7 +2315,7 @@ pg_hba_file_rules(PG_FUNCTION_ARGS)
  * NULL.
  */
 static IdentLine *
-parse_ident_line(TokenizedLine *tok_line)
+parse_ident_line(TokenizedAuthLine *tok_line)
 {
        int                     line_num = tok_line->line_num;
        ListCell   *field;
@@ -3026,7 +2606,7 @@ load_ident(void)
                return false;
        }
 
-       linecxt = tokenize_file(IdentFileName, file, &ident_lines, LOG);
+       linecxt = tokenize_auth_file(IdentFileName, file, &ident_lines, LOG);
        FreeFile(file);
 
        /* Now parse all the lines */
@@ -3037,7 +2617,7 @@ load_ident(void)
        oldcxt = MemoryContextSwitchTo(ident_context);
        foreach(line_cell, ident_lines)
        {
-               TokenizedLine *tok_line = (TokenizedLine *) lfirst(line_cell);
+               TokenizedAuthLine *tok_line = (TokenizedAuthLine *) 
lfirst(line_cell);
 
                /* don't parse lines that already have errors */
                if (tok_line->err_msg != NULL)
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 41b486bcef..7c722ea2ce 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -42,6 +42,7 @@ OBJS = \
        geo_ops.o \
        geo_selfuncs.o \
        geo_spgist.o \
+       hbafuncs.o \
        inet_cidr_ntop.o \
        inet_net_pton.o \
        int.o \
diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c
new file mode 100644
index 0000000000..f230bad8b6
--- /dev/null
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -0,0 +1,423 @@
+/*-------------------------------------------------------------------------
+ *
+ * hbafuncs.c
+ *       Support functions for authentication files SQL views.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       src/backend/utils/adt/hbafuncs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "catalog/objectaddress.h"
+#include "common/ip.h"
+#include "funcapi.h"
+#include "libpq/hba.h"
+#include "miscadmin.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+
+
+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);
+
+
+/*
+ * This macro specifies the maximum number of authentication options
+ * that are possible with any given authentication method that is supported.
+ * Currently LDAP supports 11, and there are 3 that are not dependent on
+ * the auth method here.  It may not actually be possible to set all of them
+ * at the same time, but we'll set the macro value high enough to be
+ * conservative and avoid warnings from static analysis tools.
+ */
+#define MAX_HBA_OPTIONS 14
+
+/*
+ * 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 != clientCertOff)
+               options[noptions++] =
+                       CStringGetTextDatum(psprintf("clientcert=%s", 
(hba->clientcert == clientCertCA) ? "verify-ca" : "verify-full"));
+
+       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->ldapsearchfilter)
+                       options[noptions++] =
+                               
CStringGetTextDatum(psprintf("ldapsearchfilter=%s",
+                                                                               
         hba->ldapsearchfilter));
+
+               if (hba->ldapscope)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("ldapscope=%d", 
hba->ldapscope));
+       }
+
+       if (hba->auth_method == uaRADIUS)
+       {
+               if (hba->radiusservers_s)
+                       options[noptions++] =
+                               
CStringGetTextDatum(psprintf("radiusservers=%s", hba->radiusservers_s));
+
+               if (hba->radiussecrets_s)
+                       options[noptions++] =
+                               
CStringGetTextDatum(psprintf("radiussecrets=%s", hba->radiussecrets_s));
+
+               if (hba->radiusidentifiers_s)
+                       options[noptions++] =
+                               
CStringGetTextDatum(psprintf("radiusidentifiers=%s", hba->radiusidentifiers_s));
+
+               if (hba->radiusports_s)
+                       options[noptions++] =
+                               CStringGetTextDatum(psprintf("radiusports=%s", 
hba->radiusports_s));
+       }
+
+       /* If you add more options, consider increasing MAX_HBA_OPTIONS. */
+       Assert(noptions <= MAX_HBA_OPTIONS);
+
+       if (noptions > 0)
+               return construct_array(options, noptions, TEXTOID, -1, false, 
TYPALIGN_INT);
+       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;
+                       case ctHostGSS:
+                               typestr = "hostgssenc";
+                               break;
+                       case ctHostNoGSS:
+                               typestr = "hostnogssenc";
+                               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
+                               {
+                                       /*
+                                        * Note: if pg_getnameinfo_all fails, 
it'll set buffer to
+                                        * "???", which we want to return.
+                                        */
+                                       if (hba->addrlen > 0)
+                                       {
+                                               if 
(pg_getnameinfo_all(&hba->addr, hba->addrlen,
+                                                                               
           buffer, sizeof(buffer),
+                                                                               
           NULL, 0,
+                                                                               
           NI_NUMERICHOST) == 0)
+                                                       
clean_ipv6_addr(hba->addr.ss_family, buffer);
+                                               addrstr = pstrdup(buffer);
+                                       }
+                                       if (hba->masklen > 0)
+                                       {
+                                               if 
(pg_getnameinfo_all(&hba->mask, hba->masklen,
+                                                                               
           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(hba_authname(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_auth_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)
+       {
+               TokenizedAuthLine *tok_line = (TokenizedAuthLine *) 
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)
+{
+       ReturnSetInfo *rsi;
+
+       /*
+        * Build tuplestore to hold the result rows.  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.
+        */
+       SetSingleFuncCall(fcinfo, 0);
+
+       /* Fill the tuplestore */
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+       fill_hba_view(rsi->setResult, rsi->setDesc);
+
+       PG_RETURN_NULL();
+}
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 8d9f3821b1..19924dca67 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -122,6 +122,16 @@ typedef struct HbaLine
        char       *radiusports_s;
 } HbaLine;
 
+/*
+ * A single string token lexed from a config file, together with whether
+ * the token had been quoted.
+ */
+typedef struct HbaToken
+{
+       char       *string;
+       bool            quoted;
+} HbaToken;
+
 typedef struct IdentLine
 {
        int                     linenumber;
@@ -132,6 +142,22 @@ typedef struct IdentLine
        regex_t         re;
 } IdentLine;
 
+/*
+ * TokenizedAuthLine represents one line lexed from a config file.
+ * Each item in the "fields" list is a sub-list of HbaTokens.
+ * We don't emit a TokenizedAuthLine 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 TokenizedAuthLine
+{
+       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 */
+} TokenizedAuthLine;
+
 /* kluge to avoid including libpq/libpq-be.h here */
 typedef struct Port hbaPort;
 
@@ -142,6 +168,9 @@ extern void hba_getauthmethod(hbaPort *port);
 extern int     check_usermap(const char *usermap_name,
                                                  const char *pg_role, const 
char *auth_user,
                                                  bool case_sensitive);
+extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel);
 extern bool pg_isblank(const char c);
+extern MemoryContext tokenize_auth_file(const char *filename, FILE *file,
+                                                                               
List **tok_lines, int elevel);
 
 #endif                                                 /* HBA_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index eaf3e7a8d4..109df9dc90 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2694,7 +2694,7 @@ ToastTupleContext
 ToastedAttribute
 TocEntry
 TokenAuxData
-TokenizedLine
+TokenizedAuthLine
 TrackItem
 TransInvalidationInfo
 TransState
-- 
2.33.1

>From 050caf9657ceca7251d98476f325fc2d67f6787b Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouh...@free.fr>
Date: Mon, 21 Feb 2022 17:38:34 +0800
Subject: [PATCH v3 2/4] Add a pg_ident_file_mappings view.

This view is similar to pg_hba_file_rules view, and can be also helpful to help
diagnosing configuration problems.

A following commit will add the possibility to include files in pg_hba and
pg_ident configuration files, which will then make this view even more useful.

Author: Julien Rouhaud
Reviewed-by: FIXME
Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud
---
 doc/src/sgml/catalogs.sgml             | 108 ++++++++++++++++++++
 doc/src/sgml/client-auth.sgml          |  10 ++
 doc/src/sgml/func.sgml                 |   5 +-
 src/backend/catalog/system_views.sql   |   6 ++
 src/backend/libpq/hba.c                |  31 +++---
 src/backend/utils/adt/hbafuncs.c       | 136 +++++++++++++++++++++++++
 src/include/catalog/pg_proc.dat        |   7 ++
 src/include/libpq/hba.h                |   1 +
 src/test/regress/expected/rules.out    |   6 ++
 src/test/regress/expected/sysviews.out |   6 ++
 src/test/regress/sql/sysviews.sql      |   2 +
 11 files changed, 302 insertions(+), 16 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 7777d60514..e87f0543d6 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -9540,6 +9540,11 @@ SCRAM-SHA-256$<replaceable>&lt;iteration 
count&gt;</replaceable>:<replaceable>&l
       <entry>summary of client authentication configuration file 
contents</entry>
      </row>
 
+     <row>
+      <entry><link 
linkend="view-pg-hba-file-rules"><structname>pg_ident_file_mappings</structname></link></entry>
+      <entry>summary of client user name mapping configuration file 
contents</entry>
+     </row>
+
      <row>
       <entry><link 
linkend="view-pg-indexes"><structname>pg_indexes</structname></link></entry>
       <entry>indexes</entry>
@@ -10533,6 +10538,109 @@ SCRAM-SHA-256$<replaceable>&lt;iteration 
count&gt;</replaceable>:<replaceable>&l
   </para>
  </sect1>
 
+ <sect1 id="view-pg-ident-file-mappings">
+  <title><structname>pg_ident_file_mappings</structname></title>
+
+  <indexterm zone="view-pg-ident-file-mappings">
+   <primary>pg_ident_file_mappings</primary>
+  </indexterm>
+
+  <para>
+   The view <structname>pg_ident_file_mappings</structname> provides a summary
+   of the contents of the client user name mapping configuration file,
+   <link 
linkend="auth-username-maps"><filename>pg_ident.conf</filename></link>.
+   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</emphasis>
+   contents of the file, not on what was last loaded by the server.
+  </para>
+
+  <para>
+   By default, the <structname>pg_ident_file_mappings</structname> view can be
+   read only by superusers.
+  </para>
+
+  <table>
+   <title><structname>pg_ident_file_mappings</structname> Columns</title> 
<tgroup
+   cols="1">
+    <thead>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       Column Type
+      </para>
+      <para>
+       Description
+      </para></entry>
+     </row>
+    </thead>
+
+    <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>line_number</structfield> <type>int4</type>
+      </para>
+      <para>
+       Line number of this rule in <filename>pg_ident.conf</filename>
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>map_name</structfield> <type>text</type>
+      </para>
+      <para>
+       Name of the map
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>sys_name</structfield> <type>text</type>
+      </para>
+      <para>
+       Detected user name of the client
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>pg_username</structfield> <type>text</type>
+      </para>
+      <para>
+       Requested PostgreSQL user name
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>error</structfield> <type>text</type>
+      </para>
+      <para>
+       If not null, an error message indicating why this line could not be
+       processed
+      </para></entry>
+     </row>
+    </tbody>
+   </tgroup>
+  </table>
+
+  <para>
+   Usually, a row reflecting an incorrect entry will have values for only
+   the <structfield>line_number</structfield> and 
<structfield>error</structfield> 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 02f0489112..142b0affcb 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -896,6 +896,16 @@ mymap   /^(.*)@otherdomain\.com$   guest
    -HUP</literal>) to make it re-read the file.
   </para>
 
+  <para>
+   The system view
+   <link 
linkend="view-pg-ident-file-mappings"><structname>pg_ident_file_mappings</structname></link>
+   can be helpful for pre-testing changes to the
+   <filename>pg_ident.conf</filename> 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>
+
   <para>
    A <filename>pg_ident.conf</filename> file that could be used in
    conjunction with the <filename>pg_hba.conf</filename> file in <xref
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 8a802fb225..b32cc61886 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -25475,8 +25475,9 @@ SELECT collation for ('foo' COLLATE "de_DE");
         sending a <systemitem>SIGHUP</systemitem> signal to the postmaster
         process, which in turn sends <systemitem>SIGHUP</systemitem> to each
         of its children.) You can use the
-        <link 
linkend="view-pg-file-settings"><structname>pg_file_settings</structname></link>
 and
-        <link 
linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link>
 views
+        <link 
linkend="view-pg-file-settings"><structname>pg_file_settings</structname></link>,
+        <link 
linkend="view-pg-hba-file-rules"><structname>pg_hba_file_rules</structname></link>
 and
+        <link 
linkend="view-pg-hba-file-rules"><structname>pg_ident_file_mappings</structname></link>
 views
         to check the configuration files for possible errors, before reloading.
        </para></entry>
       </row>
diff --git a/src/backend/catalog/system_views.sql 
b/src/backend/catalog/system_views.sql
index bb1ac30cd1..90011f2d68 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -607,6 +607,12 @@ CREATE VIEW pg_hba_file_rules AS
 REVOKE ALL ON pg_hba_file_rules FROM PUBLIC;
 REVOKE EXECUTE ON FUNCTION pg_hba_file_rules() FROM PUBLIC;
 
+CREATE VIEW pg_ident_file_mappings AS
+   SELECT * FROM pg_ident_file_mappings() AS A;
+
+REVOKE ALL ON pg_ident_file_mappings FROM PUBLIC;
+REVOKE EXECUTE ON FUNCTION pg_ident_file_mappings() 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 f9843a0b30..68136f6244 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -886,25 +886,22 @@ do { \
 } 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.
+ * Macros for handling pg_ident problems, similar as above.
  *
  * IDENT_FIELD_ABSENT:
- * Log a message and exit the function if the given ident field ListCell is
- * not populated.
+ * Reports when 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.
+ * Reports when the given ident token List has more than one element.
  */
 #define IDENT_FIELD_ABSENT(field) \
 do { \
        if (!field) { \
-               ereport(LOG, \
+               ereport(elevel, \
                                (errcode(ERRCODE_CONFIG_FILE_ERROR), \
                                 errmsg("missing entry in file \"%s\" at end of 
line %d", \
                                                IdentFileName, line_num))); \
+               *err_msg = psprintf("missing entry at end of line"); \
                return NULL; \
        } \
 } while (0)
@@ -912,11 +909,12 @@ do { \
 #define IDENT_MULTI_VALUE(tokens) \
 do { \
        if (tokens->length > 1) { \
-               ereport(LOG, \
+               ereport(elevel, \
                                (errcode(ERRCODE_CONFIG_FILE_ERROR), \
                                 errmsg("multiple values in ident field"), \
                                 errcontext("line %d of configuration file 
\"%s\"", \
                                                        line_num, 
IdentFileName))); \
+               *err_msg = psprintf("multiple values in ident field"); \
                return NULL; \
        } \
 } while (0)
@@ -2305,7 +2303,8 @@ load_hba(void)
  * 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 parsing fails, log a message at ereport level elevel, store an error
+ * string in tok_line->err_msg and return NULL.
  *
  * If ident_user is a regular expression (ie. begins with a slash), it is
  * compiled and stored in IdentLine structure.
@@ -2314,10 +2313,11 @@ load_hba(void)
  * to have set a memory context that will be reset if this function returns
  * NULL.
  */
-static IdentLine *
-parse_ident_line(TokenizedAuthLine *tok_line)
+IdentLine *
+parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
 {
        int                     line_num = tok_line->line_num;
+       char      **err_msg = &tok_line->err_msg;
        ListCell   *field;
        List       *tokens;
        HbaToken   *token;
@@ -2371,11 +2371,14 @@ parse_ident_line(TokenizedAuthLine *tok_line)
                        char            errstr[100];
 
                        pg_regerror(r, &parsedline->re, errstr, sizeof(errstr));
-                       ereport(LOG,
+                       ereport(elevel,
                                        
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
                                         errmsg("invalid regular expression 
\"%s\": %s",
                                                        parsedline->ident_user 
+ 1, errstr)));
 
+                       *err_msg = psprintf("invalid regular expression \"%s\": 
%s",
+                                                          
parsedline->ident_user + 1, errstr);
+
                        pfree(wstr);
                        return NULL;
                }
@@ -2626,7 +2629,7 @@ load_ident(void)
                        continue;
                }
 
-               if ((newline = parse_ident_line(tok_line)) == NULL)
+               if ((newline = parse_ident_line(tok_line, LOG)) == NULL)
                {
                        /* Parse error; remember there's trouble */
                        ok = false;
diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c
index f230bad8b6..75e69383c2 100644
--- a/src/backend/utils/adt/hbafuncs.c
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -28,6 +28,9 @@ 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);
+static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+                                                       int lineno, IdentLine 
*ident, const char *err_msg);
+static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
 
 
 /*
@@ -421,3 +424,136 @@ pg_hba_file_rules(PG_FUNCTION_ARGS)
 
        PG_RETURN_NULL();
 }
+
+/* Number of columns in pg_hba_file_mappings view */
+#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS         5
+
+/*
+ * fill_ident_line: build one row of pg_ident_file_mappings 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)
+ * ident: 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_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+                               int lineno, IdentLine *ident, const char 
*err_msg)
+{
+       Datum           values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
+       bool            nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
+       HeapTuple       tuple;
+       int                     index;
+
+       Assert(tupdesc->natts == NUM_PG_IDENT_FILE_MAPPINGS_ATTS);
+
+       memset(values, 0, sizeof(values));
+       memset(nulls, 0, sizeof(nulls));
+       index = 0;
+
+       /* line_number */
+       values[index++] = Int32GetDatum(lineno);
+
+       if (ident != NULL)
+       {
+               values[index++] = CStringGetTextDatum(ident->usermap);
+               values[index++] = CStringGetTextDatum(ident->ident_user);
+               values[index++] = CStringGetTextDatum(ident->pg_role);
+       }
+       else
+       {
+               /* no parsing result, so set relevant fields to nulls */
+               memset(&nulls[1], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 2) * 
sizeof(bool));
+       }
+
+       /* error */
+       if (err_msg)
+               values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = 
CStringGetTextDatum(err_msg);
+       else
+               nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 1] = true;
+
+       tuple = heap_form_tuple(tupdesc, values, nulls);
+       tuplestore_puttuple(tuple_store, tuple);
+}
+
+/*
+ * Read the pg_ident.conf file and fill the tuplestore with view records.
+ */
+static void
+fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
+{
+       FILE       *file;
+       List       *ident_lines = NIL;
+       ListCell   *line;
+       MemoryContext linecxt;
+       MemoryContext identcxt;
+       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(IdentFileName, "r");
+       if (file == NULL)
+               ereport(ERROR,
+                               (errcode_for_file_access(),
+                                errmsg("could not open usermap file \"%s\": 
%m",
+                                               IdentFileName)));
+
+       linecxt = tokenize_auth_file(HbaFileName, file, &ident_lines, DEBUG3);
+       FreeFile(file);
+
+       /* Now parse all the lines */
+       identcxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                  "ident 
parser context",
+                                                                  
ALLOCSET_SMALL_SIZES);
+       oldcxt = MemoryContextSwitchTo(identcxt);
+       foreach(line, ident_lines)
+       {
+               TokenizedAuthLine *tok_line = (TokenizedAuthLine *) 
lfirst(line);
+               IdentLine   *identline = NULL;
+
+               /* don't parse lines that already have errors */
+               if (tok_line->err_msg == NULL)
+                       identline = parse_ident_line(tok_line, DEBUG3);
+
+               fill_ident_line(tuple_store, tupdesc, tok_line->line_num, 
identline,
+                                         tok_line->err_msg);
+       }
+
+       /* Free tokenizer memory */
+       MemoryContextDelete(linecxt);
+       /* Free parse_hba_line memory */
+       MemoryContextSwitchTo(oldcxt);
+       MemoryContextDelete(identcxt);
+}
+
+/*
+ * SQL-accessible SRF to return all the entries in the pg_ident.conf file.
+ */
+Datum
+pg_ident_file_mappings(PG_FUNCTION_ARGS)
+{
+       ReturnSetInfo *rsi;
+
+       /*
+        * Build tuplestore to hold the result rows.  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.
+        */
+       SetSingleFuncCall(fcinfo, 0);
+
+       /* Fill the tuplestore */
+       rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+       fill_ident_view(rsi->setResult, rsi->setDesc);
+
+       PG_RETURN_NULL();
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index d8e8715ed1..6ccbc9af4c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6115,6 +6115,13 @@
   proargmodes => '{o,o,o,o,o,o,o,o,o}',
   proargnames => 
'{line_number,type,database,user_name,address,netmask,auth_method,options,error}',
   prosrc => 'pg_hba_file_rules' },
+{ oid => '9556', descr => 'show pg_ident.conf mappings',
+  proname => 'pg_ident_file_mappings', prorows => '1000', proretset => 't',
+  provolatile => 'v', prorettype => 'record', proargtypes => '',
+  proallargtypes => '{int4,text,text,text,text}',
+  proargmodes => '{o,o,o,o,o}',
+  proargnames => '{line_number,map_name,sys_name,pg_usernamee,error}',
+  prosrc => 'pg_ident_file_mappings' },
 { oid => '1371', descr => 'view system lock information',
   proname => 'pg_lock_status', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index 19924dca67..fce7db248b 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -169,6 +169,7 @@ extern int  check_usermap(const char *usermap_name,
                                                  const char *pg_role, const 
char *auth_user,
                                                  bool case_sensitive);
 extern HbaLine *parse_hba_line(TokenizedAuthLine *tok_line, int elevel);
+extern IdentLine *parse_ident_line(TokenizedAuthLine *tok_line, int elevel);
 extern bool pg_isblank(const char c);
 extern MemoryContext tokenize_auth_file(const char *filename, FILE *file,
                                                                                
List **tok_lines, int elevel);
diff --git a/src/test/regress/expected/rules.out 
b/src/test/regress/expected/rules.out
index ac468568a1..76a209b717 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1347,6 +1347,12 @@ pg_hba_file_rules| SELECT a.line_number,
     a.options,
     a.error
    FROM pg_hba_file_rules() a(line_number, type, database, user_name, address, 
netmask, auth_method, options, error);
+pg_ident_file_mappings| SELECT a.line_number,
+    a.map_name,
+    a.sys_name,
+    a.pg_usernamee,
+    a.error
+   FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, 
pg_usernamee, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     i.relname AS indexname,
diff --git a/src/test/regress/expected/sysviews.out 
b/src/test/regress/expected/sysviews.out
index 442eeb1e3f..31ba549883 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -55,6 +55,12 @@ select count(*) > 0 as ok from pg_hba_file_rules;
  t
 (1 row)
 
+select count(*) >= 0 as ok from pg_ident_file_mappings;
+ ok 
+----
+ t
+(1 row)
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;
  ok 
diff --git a/src/test/regress/sql/sysviews.sql 
b/src/test/regress/sql/sysviews.sql
index 4980f07be2..1148014e47 100644
--- a/src/test/regress/sql/sysviews.sql
+++ b/src/test/regress/sql/sysviews.sql
@@ -28,6 +28,8 @@ 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;
 
+select count(*) >= 0 as ok from pg_ident_file_mappings;
+
 -- There will surely be at least one active lock
 select count(*) > 0 as ok from pg_locks;
 
-- 
2.33.1

>From d0fdc3c1443802087fd58259729ee83128751056 Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouh...@free.fr>
Date: Mon, 21 Feb 2022 15:45:26 +0800
Subject: [PATCH v3 3/4] Allow file inclusion in pg_hba and pg_ident files.

Author: Julien Rouhaud
Reviewed-by: FIXME
Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud
---
 doc/src/sgml/catalogs.sgml             |  48 +++++-
 doc/src/sgml/client-auth.sgml          |  34 +++-
 src/backend/libpq/hba.c                | 218 +++++++++++++++++++------
 src/backend/libpq/pg_hba.conf.sample   |   8 +-
 src/backend/libpq/pg_ident.conf.sample |   8 +-
 src/backend/utils/adt/hbafuncs.c       |  51 ++++--
 src/include/catalog/pg_proc.dat        |  12 +-
 src/include/libpq/hba.h                |   2 +
 src/test/regress/expected/rules.out    |  12 +-
 9 files changed, 314 insertions(+), 79 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index e87f0543d6..c6c5e7d08e 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -10440,12 +10440,31 @@ SCRAM-SHA-256$<replaceable>&lt;iteration 
count&gt;</replaceable>:<replaceable>&l
     </thead>
 
     <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>rule_number</structfield> <type>int4</type>
+      </para>
+      <para>
+       Rule number, in priority order, of this rule if the rule is valid,
+       otherwise null
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>file_name</structfield> <type>text</type>
+      </para>
+      <para>
+       File name of this rule
+      </para></entry>
+     </row>
+
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>line_number</structfield> <type>int4</type>
       </para>
       <para>
-       Line number of this rule in <filename>pg_hba.conf</filename>
+       Line number of this rule in the given file_name
       </para></entry>
      </row>
 
@@ -10581,6 +10600,33 @@ SCRAM-SHA-256$<replaceable>&lt;iteration 
count&gt;</replaceable>:<replaceable>&l
     </thead>
 
     <tbody>
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>mapping_number</structfield> <type>int4</type>
+      </para>
+      <para>
+       Rule number, in priority order, of this mapping if the mapping is valid,
+       otherwise null
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>file_name</structfield> <type>text</type>
+      </para>
+      <para>
+       File name of this mapping
+      </para></entry>
+     </row>
+
+     <row>
+      <entry role="catalog_table_entry"><para role="column_definition">
+       <structfield>line_number</structfield> <type>int4</type>
+      </para>
+      <para>
+       Line number of this mapping in the given file_name
+      </para></entry>
+     </row>
      <row>
       <entry role="catalog_table_entry"><para role="column_definition">
        <structfield>line_number</structfield> <type>int4</type>
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 142b0affcb..e1d0e103b3 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -89,8 +89,17 @@
   </para>
 
   <para>
-   Each record specifies a connection type, a client IP address range
-   (if relevant for the connection type), a database name, a user name,
+   Each record can either be an inclusion directive or an authentication rule.
+   Inclusion records specifies files that can be included, which contains
+   additional records.  The records will be inserted in lieu of the inclusion
+   records.  Those records only contains two fields: the
+   <literal>include</literal> directive and the file to be included.  The file
+   can be a relative of absolute path, and can be double quoted if needed.
+  </para>
+
+  <para>
+   Each authentication record specifies a connection type, a client IP address
+   range (if relevant for the connection type), a database name, a user name,
    and the authentication method to be used for connections matching
    these parameters. The first record with a matching connection type,
    client address, requested database, and user name is used to perform
@@ -103,6 +112,7 @@
   <para>
    A record can have several formats:
 <synopsis>
+include       <replaceable>file</replaceable>
 local         <replaceable>database</replaceable>  
<replaceable>user</replaceable>  <replaceable>auth-method</replaceable> 
<optional><replaceable>auth-options</replaceable></optional>
 host          <replaceable>database</replaceable>  
<replaceable>user</replaceable>  <replaceable>address</replaceable>     
<replaceable>auth-method</replaceable>  
<optional><replaceable>auth-options</replaceable></optional>
 hostssl       <replaceable>database</replaceable>  
<replaceable>user</replaceable>  <replaceable>address</replaceable>     
<replaceable>auth-method</replaceable>  
<optional><replaceable>auth-options</replaceable></optional>
@@ -118,6 +128,15 @@ hostnogssenc  <replaceable>database</replaceable>  
<replaceable>user</replaceabl
    The meaning of the fields is as follows:
 
    <variablelist>
+    <varlistentry>
+     <term><literal>include</literal></term>
+     <listitem>
+      <para>
+       This line will be replaced with the content of the given file.
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry>
      <term><literal>local</literal></term>
      <listitem>
@@ -835,8 +854,9 @@ local   db1,db2,@demodbs  all                               
    md5
    cluster's data directory.  (It is possible to place the map file
    elsewhere, however; see the <xref linkend="guc-ident-file"/>
    configuration parameter.)
-   The ident map file contains lines of the general form:
+   The ident map file contains lines of two general form:
 <synopsis>
+<replaceable>include</replaceable> <replaceable>file</replaceable>
 <replaceable>map-name</replaceable> <replaceable>system-username</replaceable> 
<replaceable>database-username</replaceable>
 </synopsis>
    Comments, whitespace and line continuations are handled in the same way as 
in
@@ -847,6 +867,14 @@ local   db1,db2,@demodbs  all                              
     md5
    database user name. The same <replaceable>map-name</replaceable> can be
    used repeatedly to specify multiple user-mappings within a single map.
   </para>
+  <para>
+   The lines can record can either be an inclusion directive or an 
authentication rule.
+   Inclusion records specifies files that can be included, which contains
+   additional records.  The records will be inserted in lieu of the inclusion
+   records.  Those records only contains two fields: the
+   <literal>include</literal> directive and the file to be included.  The file
+   can be a relative of absolute path, and can be double quoted if needed.
+  </para>
   <para>
    There is no restriction regarding how many database users a given
    operating system user can correspond to, nor vice versa.  Thus, entries
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 68136f6244..1bfcaef025 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -68,6 +68,12 @@ typedef struct check_network_data
 #define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)
 #define token_matches(t, k)  (strcmp(t->string, k) == 0)
 
+typedef enum HbaIncludeKind
+{
+       SecondaryAuthFile,
+       IncludedAuthFile
+} HbaIncludeKind;
+
 /*
  * pre-parsed content of HBA config file: list of HbaLine structs.
  * parsed_hba_context is the memory context where it lives.
@@ -112,10 +118,16 @@ static const char *const UserAuthName[] =
 };
 
 
+static void tokenize_file_with_context(MemoryContext linecxt,
+                                                                          
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 FILE *open_inc_file(HbaIncludeKind kind, const char *inc_filename,
+                                                  const char *outer_filename, 
int elevel,
+                                                  char **err_msg, char 
**inc_fullname);
 
 
 /*
@@ -355,36 +367,11 @@ tokenize_inc_file(List *tokens,
        ListCell   *inc_line;
        MemoryContext linecxt;
 
-       if (is_absolute_path(inc_filename))
-       {
-               /* absolute path is taken as-is */
-               inc_fullname = pstrdup(inc_filename);
-       }
-       else
-       {
-               /* relative path is relative to dir of calling file */
-               inc_fullname = (char *) palloc(strlen(outer_filename) + 1 +
-                                                                          
strlen(inc_filename) + 1);
-               strcpy(inc_fullname, outer_filename);
-               get_parent_directory(inc_fullname);
-               join_path_components(inc_fullname, inc_fullname, inc_filename);
-               canonicalize_path(inc_fullname);
-       }
+       inc_file = open_inc_file(SecondaryAuthFile, inc_filename, 
outer_filename,
+                                                        elevel, err_msg, 
&inc_fullname);
 
-       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_auth_file(inc_fullname, inc_file, &inc_lines, 
elevel);
@@ -423,12 +410,38 @@ tokenize_inc_file(List *tokens,
        return tokens;
 }
 
+/*
+ * Tokenize the given file.
+ *
+ * Wrapper around tokenize_file_with_context, creating a decicated memory
+ * context.
+ *
+ * Return value is this memory context which contains all memory allocated by
+ * this function (it's a child of caller's context).
+ */
+MemoryContext
+tokenize_auth_file(const char *filename, FILE *file, List **tok_lines, int 
elevel)
+{
+       MemoryContext linecxt;
+       linecxt = AllocSetContextCreate(CurrentMemoryContext,
+                                                                       
"tokenize_auth_file",
+                                                                       
ALLOCSET_SMALL_SIZES);
+
+       *tok_lines = NIL;
+
+       tokenize_file_with_context(linecxt, filename, file, tok_lines, elevel);
+
+       return linecxt;
+}
+
 /*
  * Tokenize the given file.
  *
  * The output is a list of TokenizedAuthLine structs; see struct definition
  * above.
  *
+ * linecxt: memory context which must contain all memory allocated by the
+ * function
  * filename: the absolute path to the target file
  * file: the already-opened target file
  * tok_lines: receives output list
@@ -437,30 +450,22 @@ tokenize_inc_file(List *tokens,
  * Errors are reported by logging messages at ereport level elevel and by
  * adding TokenizedAuthLine 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).
  */
-MemoryContext
-tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
-                                  int elevel)
+static void
+tokenize_file_with_context(MemoryContext linecxt, const char *filename,
+                                                  FILE *file, List 
**tok_lines, int elevel)
 {
-       int                     line_number = 1;
        StringInfoData buf;
-       MemoryContext linecxt;
+       int                     line_number = 1;
        MemoryContext oldcxt;
 
-       linecxt = AllocSetContextCreate(CurrentMemoryContext,
-                                                                       
"tokenize_auth_file",
-                                                                       
ALLOCSET_SMALL_SIZES);
        oldcxt = MemoryContextSwitchTo(linecxt);
 
        initStringInfo(&buf);
 
-       *tok_lines = NIL;
-
        while (!feof(file) && !ferror(file))
        {
+               TokenizedAuthLine *tok_line;
                char       *lineptr;
                List       *current_line = NIL;
                char       *err_msg = NULL;
@@ -521,29 +526,76 @@ tokenize_auth_file(const char *filename, FILE *file, List 
**tok_lines,
                }
 
                /*
-                * Reached EOL; emit line to TokenizedAuthLine list unless it's 
boring
+                * Reached EOL; no need to emit line to TokenizedAuthLine list 
if it's
+                * boring.
                 */
-               if (current_line != NIL || err_msg != NULL)
+               if (current_line == NIL && err_msg == NULL)
+                       goto next_line;
+
+               /* If the line is valid, check if that's an include directive */
+               if (err_msg == NULL && list_length(current_line) == 2)
                {
-                       TokenizedAuthLine *tok_line;
+                       HbaToken *first, *second;
+
+                       first = linitial(linitial_node(List, current_line));
+                       second = linitial(lsecond_node(List, current_line));
 
-                       tok_line = (TokenizedAuthLine *) 
palloc(sizeof(TokenizedAuthLine));
-                       tok_line->fields = current_line;
-                       tok_line->line_num = line_number;
-                       tok_line->raw_line = pstrdup(buf.data);
-                       tok_line->err_msg = err_msg;
-                       *tok_lines = lappend(*tok_lines, tok_line);
+                       if (strcmp(first->string, "include") == 0)
+                       {
+                               char       *inc_filename;
+                               char       *inc_fullname;
+                               FILE       *inc_file;
+
+                               inc_filename = second->string;
+
+                               inc_file = open_inc_file(IncludedAuthFile, 
inc_filename,
+                                                                               
 filename, elevel, &err_msg,
+                                                                               
 &inc_fullname);
+
+                               /*
+                                * The included file could be open, now 
recursively process it.
+                                * Errors will be reported in the general 
TokenizedAuthLine
+                                * processing.
+                                */
+                               if (inc_file != NULL)
+                               {
+                                       tokenize_file_with_context(linecxt, 
inc_fullname, inc_file,
+                                                                               
           tok_lines, elevel);
+
+                                       FreeFile(inc_file);
+                                       pfree(inc_fullname);
+
+                                       /*
+                                        * The line is fully processed, bypass 
the general
+                                        * TokenizedAuthLine processing.
+                                        */
+                                       goto next_line;
+                               }
+                               else
+                               {
+                                       /* We should got an error */
+                                       Assert(err_msg != NULL);
+                               }
+                       }
                }
 
+               /* General processing: emit line to the TokenizedAuthLine */
+               tok_line = (TokenizedAuthLine *) 
palloc(sizeof(TokenizedAuthLine));
+               tok_line->fields = current_line;
+               tok_line->file_name = pstrdup(filename);
+               tok_line->line_num = line_number;
+               tok_line->raw_line = pstrdup(buf.data);
+               tok_line->err_msg = err_msg;
+               *tok_lines = lappend(*tok_lines, tok_line);
+
+next_line:
                line_number += continuations + 1;
+
        }
 
        MemoryContextSwitchTo(oldcxt);
-
-       return linecxt;
 }
 
-
 /*
  * Does user belong to role?
  *
@@ -950,6 +1002,7 @@ parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
        HbaLine    *parsedline;
 
        parsedline = palloc0(sizeof(HbaLine));
+       parsedline->sourcefile = pstrdup(tok_line->file_name);
        parsedline->linenumber = line_num;
        parsedline->rawline = pstrdup(tok_line->raw_line);
 
@@ -2298,6 +2351,67 @@ load_hba(void)
        return true;
 }
 
+/*
+ * Open the  given file for inclusion in an authentication file, whether
+ * secondary or included.
+ */
+static FILE *
+open_inc_file(HbaIncludeKind kind, const char *inc_filename,
+                         const char *outer_filename, int elevel, char 
**err_msg,
+                         char **inc_fullname)
+{
+       FILE       *inc_file;
+
+       if (is_absolute_path(inc_filename))
+       {
+               /* absolute path is taken as-is */
+               *inc_fullname = pstrdup(inc_filename);
+       }
+       else
+       {
+               /* relative path is relative to dir of calling file */
+               *inc_fullname = (char *) palloc(strlen(outer_filename) + 1 +
+                                                                          
strlen(inc_filename) + 1);
+               strcpy(*inc_fullname, outer_filename);
+               get_parent_directory(*inc_fullname);
+               join_path_components(*inc_fullname, *inc_fullname, 
inc_filename);
+               canonicalize_path(*inc_fullname);
+       }
+
+       inc_file = AllocateFile(*inc_fullname, "r");
+       if (inc_file == NULL)
+       {
+               int                     save_errno = errno;
+               const char *msglog;
+               const char *msgview;
+
+               switch (kind)
+               {
+                       case SecondaryAuthFile:
+                               msglog = "could not open secondary 
authentication file \"@%s\" as \"%s\": %m";
+                               msgview = "could not open secondary 
authentication file \"@%s\" as \"%s\": %s";
+                               break;
+                       case IncludedAuthFile:
+                               msglog = "could not open included 
authentication file \"%s\" as \"%s\": %m";
+                               msgview = "could not open included 
authentication file \"%s\" as \"%s\": %s";
+                               break;
+                       default:
+                               elog(ERROR, "unknown HbaIncludeKind: %d", kind);
+                               break;
+               }
+
+               ereport(elevel,
+                               (errcode_for_file_access(),
+                                errmsg(msglog, inc_filename, *inc_fullname)));
+               *err_msg = psprintf(msgview, inc_filename, *inc_fullname,
+                                                       strerror(save_errno));
+               pfree(*inc_fullname);
+               *inc_fullname = NULL;
+               return NULL;
+       }
+
+       return inc_file;
+}
 
 /*
  * Parse one tokenised line from the ident config file and store the result in
diff --git a/src/backend/libpq/pg_hba.conf.sample 
b/src/backend/libpq/pg_hba.conf.sample
index 5f3f63eb0c..0b6589a7b9 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -9,6 +9,7 @@
 # are authenticated, which PostgreSQL user names they can use, which
 # databases they can access.  Records take one of these forms:
 #
+# include       FILE
 # local         DATABASE  USER  METHOD  [OPTIONS]
 # host          DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
 # hostssl       DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
@@ -18,7 +19,12 @@
 #
 # (The uppercase items must be replaced by actual values.)
 #
-# The first field is the connection type:
+# If the first field is "include", it's not a mapping record but a directive to
+# include records from another file, specified in the field.  FILE is the file
+# to include.  It can be specified with a relative or absolute path, and can be
+# double quoted if it contains spaces.
+#
+# Otherwise the first field is the connection type:
 # - "local" is a Unix-domain socket
 # - "host" is a TCP/IP socket (encrypted or not)
 # - "hostssl" is a TCP/IP socket that is SSL-encrypted
diff --git a/src/backend/libpq/pg_ident.conf.sample 
b/src/backend/libpq/pg_ident.conf.sample
index a5870e6448..138359cf03 100644
--- a/src/backend/libpq/pg_ident.conf.sample
+++ b/src/backend/libpq/pg_ident.conf.sample
@@ -7,12 +7,18 @@
 #
 # This file controls PostgreSQL user name mapping.  It maps external
 # user names to their corresponding PostgreSQL user names.  Records
-# are of the form:
+# are one of these forms:
 #
+# include  FILE
 # MAPNAME  SYSTEM-USERNAME  PG-USERNAME
 #
 # (The uppercase quantities must be replaced by actual values.)
 #
+# If the first field is "include", it's not an authentication record but a
+# directive to include records from another file, specified in the field.  FILE
+# is the file to include.  It can be specified with a relative or absolute
+# path, and can be double quoted if it contains spaces.
+#
 # MAPNAME is the (otherwise freely chosen) map name that was used in
 # pg_hba.conf.  SYSTEM-USERNAME is the detected user name of the
 # client.  PG-USERNAME is the requested PostgreSQL user name.  The
diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c
index 75e69383c2..ee12dc1893 100644
--- a/src/backend/utils/adt/hbafuncs.c
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -26,9 +26,11 @@
 
 static ArrayType *gethba_options(HbaLine *hba);
 static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
-                                                 int lineno, HbaLine *hba, 
const char *err_msg);
+                                                 int rule_number, const char 
*filename, int lineno,
+                                                 HbaLine *hba, const char 
*err_msg);
 static void fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
 static void fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
+                                                       int mapping_number, 
const char *filename,
                                                        int lineno, IdentLine 
*ident, const char *err_msg);
 static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
 
@@ -157,7 +159,7 @@ gethba_options(HbaLine *hba)
 }
 
 /* Number of columns in pg_hba_file_rules view */
-#define NUM_PG_HBA_FILE_RULES_ATTS      9
+#define NUM_PG_HBA_FILE_RULES_ATTS      11
 
 /*
  * fill_hba_line: build one row of pg_hba_file_rules view, add it to tuplestore
@@ -173,7 +175,8 @@ gethba_options(HbaLine *hba)
  */
 static void
 fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
-                         int lineno, HbaLine *hba, const char *err_msg)
+                         int rule_number, const char *filename, int lineno, 
HbaLine *hba,
+                         const char *err_msg)
 {
        Datum           values[NUM_PG_HBA_FILE_RULES_ATTS];
        bool            nulls[NUM_PG_HBA_FILE_RULES_ATTS];
@@ -192,6 +195,13 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc 
tupdesc,
        memset(nulls, 0, sizeof(nulls));
        index = 0;
 
+       /* rule_number */
+       if (err_msg)
+               nulls[index++] = true;
+       else
+               values[index++] = Int32GetDatum(rule_number);
+       /* file_name */
+       values[index++] = CStringGetTextDatum(filename);
        /* line_number */
        values[index++] = Int32GetDatum(lineno);
 
@@ -335,7 +345,7 @@ fill_hba_line(Tuplestorestate *tuple_store, TupleDesc 
tupdesc,
        else
        {
                /* no parsing result, so set relevant fields to nulls */
-               memset(&nulls[1], true, (NUM_PG_HBA_FILE_RULES_ATTS - 2) * 
sizeof(bool));
+               memset(&nulls[3], true, (NUM_PG_HBA_FILE_RULES_ATTS - 4) * 
sizeof(bool));
        }
 
        /* error */
@@ -357,6 +367,7 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc 
tupdesc)
        FILE       *file;
        List       *hba_lines = NIL;
        ListCell   *line;
+       int                     rule_number = 0;
        MemoryContext linecxt;
        MemoryContext hbacxt;
        MemoryContext oldcxt;
@@ -391,8 +402,12 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc 
tupdesc)
                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);
+               /* No error, set a rule number */
+               if (tok_line->err_msg == NULL)
+                       rule_number++;
+
+               fill_hba_line(tuple_store, tupdesc, rule_number, 
tok_line->file_name,
+                                         tok_line->line_num, hbaline, 
tok_line->err_msg);
        }
 
        /* Free tokenizer memory */
@@ -426,7 +441,7 @@ pg_hba_file_rules(PG_FUNCTION_ARGS)
 }
 
 /* Number of columns in pg_hba_file_mappings view */
-#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS         5
+#define NUM_PG_IDENT_FILE_MAPPINGS_ATTS         7
 
 /*
  * fill_ident_line: build one row of pg_ident_file_mappings view, add it to
@@ -443,7 +458,8 @@ pg_hba_file_rules(PG_FUNCTION_ARGS)
  */
 static void
 fill_ident_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
-                               int lineno, IdentLine *ident, const char 
*err_msg)
+                               int mapping_number, const char *filename, int 
lineno,
+                               IdentLine *ident, const char *err_msg)
 {
        Datum           values[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
        bool            nulls[NUM_PG_IDENT_FILE_MAPPINGS_ATTS];
@@ -456,6 +472,13 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc 
tupdesc,
        memset(nulls, 0, sizeof(nulls));
        index = 0;
 
+       /* mapping_number */
+       if (err_msg)
+               nulls[index++] = true;
+       else
+               values[index++] = Int32GetDatum(mapping_number);
+       /* file_name */
+       values[index++] = CStringGetTextDatum(filename);
        /* line_number */
        values[index++] = Int32GetDatum(lineno);
 
@@ -468,7 +491,7 @@ fill_ident_line(Tuplestorestate *tuple_store, TupleDesc 
tupdesc,
        else
        {
                /* no parsing result, so set relevant fields to nulls */
-               memset(&nulls[1], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 2) * 
sizeof(bool));
+               memset(&nulls[3], true, (NUM_PG_IDENT_FILE_MAPPINGS_ATTS - 4) * 
sizeof(bool));
        }
 
        /* error */
@@ -490,6 +513,7 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc 
tupdesc)
        FILE       *file;
        List       *ident_lines = NIL;
        ListCell   *line;
+       int                     mapping_number = 0;
        MemoryContext linecxt;
        MemoryContext identcxt;
        MemoryContext oldcxt;
@@ -524,8 +548,13 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc 
tupdesc)
                if (tok_line->err_msg == NULL)
                        identline = parse_ident_line(tok_line, DEBUG3);
 
-               fill_ident_line(tuple_store, tupdesc, tok_line->line_num, 
identline,
-                                         tok_line->err_msg);
+               /* No error, set a rule number */
+               if (tok_line->err_msg == NULL)
+                       mapping_number++;
+
+               fill_ident_line(tuple_store, tupdesc, mapping_number,
+                                               tok_line->file_name, 
tok_line->line_num, identline,
+                                               tok_line->err_msg);
        }
 
        /* Free tokenizer memory */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 6ccbc9af4c..0e8a589302 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6111,16 +6111,16 @@
 { oid => '3401', descr => 'show pg_hba.conf rules',
   proname => 'pg_hba_file_rules', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{int4,text,_text,_text,text,text,text,_text,text}',
-  proargmodes => '{o,o,o,o,o,o,o,o,o}',
-  proargnames => 
'{line_number,type,database,user_name,address,netmask,auth_method,options,error}',
+  proallargtypes => 
'{int4,text,int4,text,_text,_text,text,text,text,_text,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => 
'{rule_number,file_name,line_number,type,database,user_name,address,netmask,auth_method,options,error}',
   prosrc => 'pg_hba_file_rules' },
 { oid => '9556', descr => 'show pg_ident.conf mappings',
   proname => 'pg_ident_file_mappings', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-  proallargtypes => '{int4,text,text,text,text}',
-  proargmodes => '{o,o,o,o,o}',
-  proargnames => '{line_number,map_name,sys_name,pg_usernamee,error}',
+  proallargtypes => '{int4,text,int4,text,text,text,text}',
+  proargmodes => '{o,o,o,o,o,o,o}',
+  proargnames => 
'{mapping_number,file_name,line_number,map_name,sys_name,pg_usernamee,error}',
   prosrc => 'pg_ident_file_mappings' },
 { oid => '1371', descr => 'view system lock information',
   proname => 'pg_lock_status', prorows => '1000', proretset => 't',
diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index fce7db248b..551e961585 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -79,6 +79,7 @@ typedef enum ClientCertName
 
 typedef struct HbaLine
 {
+       char       *sourcefile;
        int                     linenumber;
        char       *rawline;
        ConnType        conntype;
@@ -153,6 +154,7 @@ typedef struct IdentLine
 typedef struct TokenizedAuthLine
 {
        List       *fields;                     /* List of lists of HbaTokens */
+       char       *file_name;          /* File name */
        int                     line_num;               /* Line number */
        char       *raw_line;           /* Raw line text */
        char       *err_msg;            /* Error message if any */
diff --git a/src/test/regress/expected/rules.out 
b/src/test/regress/expected/rules.out
index 76a209b717..5b7a4c01ee 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1337,7 +1337,9 @@ 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,
+pg_hba_file_rules| SELECT a.rule_number,
+    a.file_name,
+    a.line_number,
     a.type,
     a.database,
     a.user_name,
@@ -1346,13 +1348,15 @@ pg_hba_file_rules| SELECT a.line_number,
     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_ident_file_mappings| SELECT a.line_number,
+   FROM pg_hba_file_rules() a(rule_number, file_name, line_number, type, 
database, user_name, address, netmask, auth_method, options, error);
+pg_ident_file_mappings| SELECT a.mapping_number,
+    a.file_name,
+    a.line_number,
     a.map_name,
     a.sys_name,
     a.pg_usernamee,
     a.error
-   FROM pg_ident_file_mappings() a(line_number, map_name, sys_name, 
pg_usernamee, error);
+   FROM pg_ident_file_mappings() a(mapping_number, file_name, line_number, 
map_name, sys_name, pg_usernamee, error);
 pg_indexes| SELECT n.nspname AS schemaname,
     c.relname AS tablename,
     i.relname AS indexname,
-- 
2.33.1

>From 711359a1a80a7bfcf350b9ffa0a2f869f15a7e81 Mon Sep 17 00:00:00 2001
From: Julien Rouhaud <julien.rouh...@free.fr>
Date: Tue, 22 Feb 2022 21:34:54 +0800
Subject: [PATCH v3 4/4] POC: Add a pg_hba_matches() function.

Author: Julien Rouhaud
Reviewed-by: FIXME
Discussion: https://postgr.es/m/20220223045959.35ipdsvbxcstrhya%40jrouhaud
---
 src/backend/catalog/system_functions.sql |   9 ++
 src/backend/libpq/hba.c                  | 138 +++++++++++++++++++++++
 src/include/catalog/pg_proc.dat          |   7 ++
 3 files changed, 154 insertions(+)

diff --git a/src/backend/catalog/system_functions.sql 
b/src/backend/catalog/system_functions.sql
index 81bac6f581..049cdabc81 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -594,6 +594,15 @@ LANGUAGE internal
 STRICT IMMUTABLE PARALLEL SAFE
 AS 'unicode_is_normalized';
 
+CREATE OR REPLACE FUNCTION
+  pg_hba_matches(
+    IN address inet, IN role text, IN ssl bool DEFAULT false,
+    OUT file_name text, OUT line_num int4, OUT raw_line text)
+RETURNS RECORD
+LANGUAGE INTERNAL
+VOLATILE
+AS 'pg_hba_matches';
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 1bfcaef025..8afbb16b76 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -26,6 +26,7 @@
 #include <unistd.h>
 
 #include "access/htup_details.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/ip.h"
@@ -41,6 +42,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
+#include "utils/inet.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/varlena.h"
@@ -2833,3 +2835,139 @@ hba_authname(UserAuth auth_method)
 
        return UserAuthName[auth_method];
 }
+
+#define PG_HBA_MATCHES_ATTS    3
+
+/*
+ * SQL-accessible SRF to return the entries that match the given connection
+ * info, if any.
+ */
+Datum pg_hba_matches(PG_FUNCTION_ARGS)
+{
+       MemoryContext ctxt;
+       inet       *address = NULL;
+       bool            ssl_in_use = false;
+       hbaPort    *port = palloc0(sizeof(hbaPort));
+       TupleDesc       tupdesc;
+       Datum           values[PG_HBA_MATCHES_ATTS];
+       bool            isnull[PG_HBA_MATCHES_ATTS];
+
+       if (!is_member_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                                errmsg("only superuser or a member of the 
pg_read_server_files role may call this function")));
+
+       if (PG_ARGISNULL(0))
+               port->raddr.addr.ss_family = AF_UNIX;
+       else
+       {
+               int                     bits;
+               char       *ptr;
+               char            
tmp[sizeof("xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:255.255.255.255/128")];
+
+               address = PG_GETARG_INET_PP(0);
+
+               bits = ip_maxbits(address) - ip_bits(address);
+               if (bits != 0)
+               {
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("Invalid address")));
+               }
+
+               /* force display of max bits, regardless of masklen... */
+               if (pg_inet_net_ntop(ip_family(address), ip_addr(address),
+                                                        ip_maxbits(address), 
tmp, sizeof(tmp)) == NULL)
+                       ereport(ERROR,
+                                       
(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+                                        errmsg("could not format inet value: 
%m")));
+
+               /* Suppress /n if present (shouldn't happen now) */
+               if ((ptr = strchr(tmp, '/')) != NULL)
+                       *ptr = '\0';
+
+               switch (ip_family(address))
+               {
+                       case PGSQL_AF_INET:
+                       {
+                               struct sockaddr_in *dst;
+
+                               dst = (struct sockaddr_in *) &port->raddr.addr;
+                               dst->sin_family = AF_INET;
+
+                               /* ip_addr(address) always contains network 
representation */
+                               memcpy(&dst->sin_addr, &ip_addr(address), 
sizeof(dst->sin_addr));
+
+                               break;
+                       }
+                       /* See pg_inet_net_ntop() for details about those 
constants */
+                       case PGSQL_AF_INET6:
+#if defined(AF_INET6) && AF_INET6 != PGSQL_AF_INET6
+                       case AF_INET6:
+#endif
+                       {
+                               struct sockaddr_in6 *dst;
+
+                               dst = (struct sockaddr_in6 *) &port->raddr.addr;
+                               dst->sin6_family = AF_INET6;
+
+                               /* ip_addr(address) always contains network 
representation */
+                               memcpy(&dst->sin6_addr, &ip_addr(address), 
sizeof(dst->sin6_addr));
+
+                               break;
+                       }
+                       default:
+                               elog(ERROR, "unexpected ip_family: %d", 
ip_family(address));
+                               break;
+               }
+       }
+
+       if (PG_ARGISNULL(1))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("parameter role is mandatory")));
+       port->user_name = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+       if (!PG_ARGISNULL(2))
+               ssl_in_use = PG_GETARG_BOOL(2);
+
+       port->ssl_in_use = ssl_in_use;
+
+       tupdesc = CreateTemplateTupleDesc(PG_HBA_MATCHES_ATTS);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 1, "file_name",
+                                          TEXTOID, -1, 0);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 2, "line_num",
+                                          INT4OID, -1, 0);
+       TupleDescInitEntry(tupdesc, (AttrNumber) 3, "raw_line",
+                                          TEXTOID, -1, 0);
+
+       BlessTupleDesc(tupdesc);
+
+       memset(isnull, 0, sizeof(isnull));
+
+       /* FIXME rework API to not rely on PostmasterContext */
+       ctxt = AllocSetContextCreate(CurrentMemoryContext, "load_hba",
+                                                                
ALLOCSET_DEFAULT_SIZES);
+       PostmasterContext = AllocSetContextCreate(ctxt,
+                                                                               
          "Postmaster",
+                                                                               
          ALLOCSET_DEFAULT_SIZES);
+       parsed_hba_context = NULL;
+       if (!load_hba())
+               ereport(ERROR,
+                               (errcode(ERRCODE_CONFIG_FILE_ERROR),
+                                errmsg("Invalidation auth configuration 
file")));
+
+       check_hba(port);
+
+       if (port->hba->auth_method == uaImplicitReject)
+               PG_RETURN_NULL();
+
+       values[0] = CStringGetTextDatum(port->hba->sourcefile);
+       values[1] = Int32GetDatum(port->hba->linenumber);
+       values[2] = CStringGetTextDatum(port->hba->rawline);
+
+       MemoryContextDelete(PostmasterContext);
+       PostmasterContext = NULL;
+
+       return HeapTupleGetDatum(heap_form_tuple(tupdesc, values, isnull));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 0e8a589302..ee77bd1136 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6122,6 +6122,13 @@
   proargmodes => '{o,o,o,o,o,o,o}',
   proargnames => 
'{mapping_number,file_name,line_number,map_name,sys_name,pg_usernamee,error}',
   prosrc => 'pg_ident_file_mappings' },
+{ oid => '9557', descr => 'show wether the given connection would match an hba 
line',
+  proname => 'pg_hba_matches', provolatile => 'v', prorettype => 'record',
+  proargtypes => 'inet text bool', proisstrict => 'f',
+  proallargtypes => '{inet,text,bool,text,int4,text}',
+  proargmodes => '{i,i,i,o,o,o}',
+  proargnames => '{address,role,ssl,file_name,line_num,raw_line}',
+  prosrc => 'pg_hba_matches' },
 { oid => '1371', descr => 'view system lock information',
   proname => 'pg_lock_status', prorows => '1000', proretset => 't',
   provolatile => 'v', prorettype => 'record', proargtypes => '',
-- 
2.33.1

Reply via email to