From a3f02d723274e643741a12c106e9440e7229401d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Tue, 22 Nov 2022 17:18:05 +0900
Subject: [PATCH v20 2/2] My own changes

---
 src/include/libpq/hba.h                |   6 +-
 src/backend/libpq/hba.c                | 272 ++++++++++++-------------
 src/backend/libpq/pg_hba.conf.sample   |  52 +++--
 src/backend/libpq/pg_ident.conf.sample |  31 ++-
 src/backend/utils/adt/hbafuncs.c       |  12 +-
 src/test/authentication/meson.build    |   1 +
 doc/src/sgml/client-auth.sgml          | 109 +++++-----
 doc/src/sgml/system-views.sgml         |   5 +-
 8 files changed, 257 insertions(+), 231 deletions(-)

diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h
index a84a5f0961..b1f2d8410d 100644
--- a/src/include/libpq/hba.h
+++ b/src/include/libpq/hba.h
@@ -179,7 +179,9 @@ extern IdentLine *parse_ident_line(TokenizedAuthLine *tok_line, int elevel);
 extern bool pg_isblank(const char c);
 extern FILE *open_auth_file(const char *filename, int elevel, int depth,
 							char **err_msg);
-extern MemoryContext tokenize_auth_file(const char *filename, FILE *file,
-										List **tok_lines, int elevel, int depth);
+extern void tokenize_auth_file(const char *filename, FILE *file,
+							   List **tok_lines, int elevel, int depth);
+extern void tokenize_init_context(void);
+extern void tokenize_reset_context(void);
 
 #endif							/* HBA_H */
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index d5d5c111bc..4382e5a7d1 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -76,11 +76,12 @@ typedef struct
 #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;
+/*
+ * Memory context holding the list of TokenizedAuthLines when parsing
+ * HBA or ident config files.  This is created at the top point loading
+ * HBA or ident files.
+ */
+static MemoryContext tokenize_context = NULL;
 
 /*
  * pre-parsed content of HBA config file: list of HbaLine structs.
@@ -127,10 +128,6 @@ static const char *const UserAuthName[] =
 };
 
 
-static void tokenize_file_with_context(MemoryContext linecxt,
-									   const char *filename, FILE *file,
-									   List **tok_lines, int depth,
-									   int elevel);
 static List *tokenize_inc_file(List *tokens, const char *outer_filename,
 							   const char *inc_filename, int elevel,
 							   int depth, char **err_msg);
@@ -141,10 +138,6 @@ static int	regcomp_auth_token(AuthToken *token, char *filename, int line_num,
 static int	regexec_auth_token(const char *match, AuthToken *token,
 							   size_t nmatch, regmatch_t pmatch[]);
 static void tokenize_error_callback(void *arg);
-static char *process_included_authfile(const char *inc_filename, bool strict,
-									   const char *outer_filename, int depth,
-									   int elevel, MemoryContext linecxt,
-									   List **tok_lines);
 
 
 /*
@@ -485,9 +478,8 @@ tokenize_inc_file(List *tokens,
 {
 	char	   *inc_fullname;
 	FILE	   *inc_file;
-	List	   *inc_lines;
+	List	   *inc_lines = NIL;
 	ListCell   *inc_line;
-	MemoryContext linecxt;
 
 	inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
 	inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
@@ -500,13 +492,16 @@ tokenize_inc_file(List *tokens,
 	}
 
 	/* There is possible recursion here if the file contains @ */
-	linecxt = tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel,
-								 depth);
+	tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel,
+					   depth);
 
 	FreeFile(inc_file);
 	pfree(inc_fullname);
 
-	/* Copy all tokens found in the file and append to the tokens list */
+	/*
+	 * Move all the tokens found in the file to the tokens list.  These
+	 * are already saved in tokenize_context.
+	 */
 	foreach(inc_line, inc_lines)
 	{
 		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(inc_line);
@@ -528,12 +523,11 @@ tokenize_inc_file(List *tokens,
 			{
 				AuthToken  *token = lfirst(inc_token);
 
-				tokens = lappend(tokens, copy_auth_token(token));
+				tokens = lappend(tokens, token);
 			}
 		}
 	}
 
-	MemoryContextDelete(linecxt);
 	return tokens;
 }
 
@@ -584,6 +578,9 @@ open_auth_file(const char *filename, int elevel, int depth,
 		if (err_msg)
 			*err_msg = psprintf("could not open file \"%s\": %s",
 								filename, strerror(save_errno));
+
+		/* the caller may care about some specific errno */
+		errno = save_errno;
 		return NULL;
 	}
 
@@ -602,40 +599,32 @@ tokenize_error_callback(void *arg)
 			   callback_arg->linenum, callback_arg->filename);
 }
 
-/*
- * tokenize_auth_file
- *
- * Wrapper around tokenize_file_with_context, creating a dedicated 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 depth, int elevel)
+void
+tokenize_init_context(void)
 {
-	MemoryContext linecxt;
-	linecxt = AllocSetContextCreate(CurrentMemoryContext,
-									"tokenize_auth_file",
-									ALLOCSET_SMALL_SIZES);
+	/*
+	 * A context may be present, but assume that it has been eliminated
+	 * already.
+	 * */
+	tokenize_context = AllocSetContextCreate(CurrentMemoryContext,
+											 "tokenize_context",
+											 ALLOCSET_START_SMALL_SIZES);
+}
 
-	*tok_lines = NIL;
-
-	tokenize_file_with_context(linecxt, filename, file, tok_lines, depth,
-							   elevel);
-
-	return linecxt;
+void
+tokenize_reset_context(void)
+{
+	MemoryContextDelete(tokenize_context);
+	tokenize_context = NULL;
 }
 
 /*
- * Tokenize the given file.
+ * tokenize_auth_file
+ *		Tokenize the given file.
  *
  * The output is a list of TokenizedAuthLine structs; see the struct definition
  * in libpq/hba.h.
  *
- * 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
@@ -646,9 +635,9 @@ tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
  * adding TokenizedAuthLine structs containing non-null err_msg fields to the
  * output list.
  */
-static void
-tokenize_file_with_context(MemoryContext linecxt, const char *filename,
-						   FILE *file, List **tok_lines, int elevel, int depth)
+void
+tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
+				   int elevel, int depth)
 {
 	StringInfoData buf;
 	int			line_number = 1;
@@ -656,6 +645,8 @@ tokenize_file_with_context(MemoryContext linecxt, const char *filename,
 	ErrorContextCallback tokenerrcontext;
 	tokenize_error_callback_arg callback_arg;
 
+	Assert(tokenize_context);
+
 	callback_arg.filename = filename;
 	callback_arg.linenum = line_number;
 
@@ -664,8 +655,6 @@ tokenize_file_with_context(MemoryContext linecxt, const char *filename,
 	tokenerrcontext.previous = error_context_stack;
 	error_context_stack = &tokenerrcontext;
 
-	oldcxt = MemoryContextSwitchTo(linecxt);
-
 	initStringInfo(&buf);
 
 	while (!feof(file) && !ferror(file))
@@ -747,22 +736,31 @@ tokenize_file_with_context(MemoryContext linecxt, const char *filename,
 
 			if (strcmp(first->string, "include") == 0)
 			{
-				char	   *inc_filename;
+				char	   *inc_fullname;
+				FILE	   *inc_file;
 
-				inc_filename = second->string;
+				inc_fullname = AbsoluteConfigLocation(second->string, filename);
+				inc_file = open_auth_file(inc_fullname, elevel, depth + 1,
+										  &err_msg);
 
-				err_msg = process_included_authfile(inc_filename, true,
-										  filename, depth + 1, elevel, linecxt,
-										  tok_lines);
-
-				if (!err_msg)
+				if (!inc_file)
 				{
-					/*
-					 * The line is fully processed, bypass the general
-					 * TokenizedAuthLine processing.
-					 */
-					goto next_line;
+					/* error in err_msg, so create an entry */
+					pfree(inc_fullname);
+					Assert(err_msg);
+					goto process_line;
 				}
+
+				tokenize_auth_file(inc_fullname, inc_file, tok_lines, elevel,
+								   depth + 1);
+				FreeFile(inc_file);
+				pfree(inc_fullname);
+
+				/*
+				 * tokenize_auth_file() has taken care of creating the
+				 * TokenizedAuthLines, so move on.
+				 */
+				goto next_line;
 			}
 			else if (strcmp(first->string, "include_dir") == 0)
 			{
@@ -776,23 +774,36 @@ tokenize_file_with_context(MemoryContext linecxt, const char *filename,
 
 				if (!filenames)
 				{
-					/* We have the error in err_msg, simply process it */
+					/* the error is in err_msg, so create an entry */
 					goto process_line;
 				}
 
 				initStringInfo(&err_buf);
 				for (int i = 0; i < num_filenames; i++)
 				{
-					/*
-					 * err_msg is used here as a temp buffer, it will be
-					 * overwritten at the end of the loop with the
-					 * cumulated errors, if any.
-					 */
-					err_msg = process_included_authfile(filenames[i], true,
-												filename, depth + 1, elevel,
-												linecxt, tok_lines);
+					char	   *inc_fullname;
+					FILE	   *inc_file;
 
-					/* Cumulate errors if any. */
+					inc_fullname = AbsoluteConfigLocation(filenames[i], filename);
+					inc_file = open_auth_file(inc_fullname, elevel, depth + 1,
+											  &err_msg);
+
+					if (!inc_file)
+					{
+						/*
+						 * One of the files has failed, so report it
+						 * and ignore the rest.
+						 */
+						goto process_line;
+					}
+
+					tokenize_auth_file(inc_fullname, inc_file, tok_lines, elevel,
+									   depth + 1);
+
+					FreeFile(inc_file);
+					pfree(inc_fullname);
+
+					/* cumulate errors if any */
 					if (err_msg)
 					{
 						if (err_buf.len > 0)
@@ -810,48 +821,70 @@ tokenize_file_with_context(MemoryContext linecxt, const char *filename,
 
 				/* Otherwise, process the cumulated errors, if any. */
 				err_msg = err_buf.data;
+				goto process_line;
 			}
 			else if (strcmp(first->string, "include_if_exists") == 0)
 			{
-				char	   *inc_filename;
+				char	   *inc_fullname;
+				FILE	   *inc_file;
 
-				inc_filename = second->string;
+				inc_fullname = AbsoluteConfigLocation(second->string, filename);
+				inc_file = open_auth_file(inc_fullname, elevel, depth + 1,
+										  &err_msg);
 
-				err_msg = process_included_authfile(inc_filename, false,
-										  filename, depth + 1, elevel, linecxt,
-										  tok_lines);
-
-				if (!err_msg)
+				if (!inc_file)
 				{
-					/*
-					 * The line is fully processed, bypass the general
-					 * TokenizedAuthLine processing.
-					 */
-					goto next_line;
+					if (errno == ENOENT)
+					{
+						/* no file, so move to next line */
+
+						/* XXX: this should stick to elevel for some cases? */
+						ereport(LOG,
+								(errmsg("skipping missing authentication file \"%s\"",
+										inc_fullname)));
+						pfree(inc_fullname);
+						goto next_line;
+					}
+
+					pfree(inc_fullname);
+					Assert(err_msg);
+					goto process_line;
 				}
+
+				tokenize_auth_file(inc_fullname, inc_file, tok_lines, elevel,
+								   depth + 1);
+				FreeFile(inc_file);
+				pfree(inc_fullname);
+
+				/*
+				 * tokenize_auth_file() has taken care of creating the
+				 * TokenizedAuthLines.
+				 */
+				goto next_line;
 			}
 		}
 
 process_line:
 		/*
 		 * General processing: report the error if any and emit line to the
-		 * TokenizedAuthLine
-		*/
-		tok_line = (TokenizedAuthLine *) palloc(sizeof(TokenizedAuthLine));
+		 * TokenizedAuthLine.  This is saved in the memory context dedicated
+		 * to this list.
+		 */
+		oldcxt = MemoryContextSwitchTo(tokenize_context);
+		tok_line = (TokenizedAuthLine *) palloc0(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_line->err_msg = err_msg ? pstrdup(err_msg) : NULL;
 		*tok_lines = lappend(*tok_lines, tok_line);
+		MemoryContextSwitchTo(oldcxt);
 
 next_line:
 		line_number += continuations + 1;
 		callback_arg.linenum = line_number;
 	}
 
-	MemoryContextSwitchTo(oldcxt);
-
 	error_context_stack = tokenerrcontext.previous;
 }
 
@@ -2539,7 +2572,6 @@ load_hba(void)
 	ListCell   *line;
 	List	   *new_parsed_lines = NIL;
 	bool		ok = true;
-	MemoryContext linecxt;
 	MemoryContext oldcxt;
 	MemoryContext hbacxt;
 
@@ -2550,7 +2582,8 @@ load_hba(void)
 		return false;
 	}
 
-	linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, LOG, 0);
+	tokenize_init_context();
+	tokenize_auth_file(HbaFileName, file, &hba_lines, LOG, 0);
 	FreeFile(file);
 
 	/* Now parse all the lines */
@@ -2602,7 +2635,7 @@ load_hba(void)
 	}
 
 	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
+	tokenize_reset_context();
 	MemoryContextSwitchTo(oldcxt);
 
 	if (!ok)
@@ -2641,53 +2674,6 @@ load_hba(void)
 }
 
 
-/*
- * Try to open an included file, and tokenize it using the given context.
- * Returns NULL if no error happens during tokenization, otherwise the error.
- */
-static char *
-process_included_authfile(const char *inc_filename, bool strict,
-						  const char *outer_filename, int depth, int elevel,
-						  MemoryContext linecxt, List **tok_lines)
-{
-	char	   *inc_fullname;
-	FILE	   *inc_file;
-	char	   *err_msg = NULL;
-
-	inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
-	inc_file = open_auth_file(inc_fullname, elevel, depth, &err_msg);
-
-	if (inc_file == NULL)
-	{
-		if (strict)
-		{
-			/* open_auth_file should have reported an error. */
-			Assert(err_msg != NULL);
-			return err_msg;
-		}
-		else
-		{
-			ereport(LOG,
-					(errmsg("skipping missing authentication file \"%s\"",
-							inc_fullname)));
-			return NULL;
-		}
-	}
-	else
-	{
-		/* No error message should have been reported. */
-		Assert(err_msg == NULL);
-	}
-
-	tokenize_file_with_context(linecxt, inc_fullname, inc_file,
-							   tok_lines, elevel, depth);
-
-	FreeFile(inc_file);
-	pfree(inc_fullname);
-
-	return NULL;
-}
-
 /*
  * Parse one tokenised line from the ident config file and store the result in
  * an IdentLine structure.
@@ -2953,7 +2939,6 @@ load_ident(void)
 			   *parsed_line_cell;
 	List	   *new_parsed_lines = NIL;
 	bool		ok = true;
-	MemoryContext linecxt;
 	MemoryContext oldcxt;
 	MemoryContext ident_context;
 	IdentLine  *newline;
@@ -2966,7 +2951,8 @@ load_ident(void)
 		return false;
 	}
 
-	linecxt = tokenize_auth_file(IdentFileName, file, &ident_lines, LOG, 0);
+	tokenize_init_context();
+	tokenize_auth_file(IdentFileName, file, &ident_lines, LOG, 0);
 	FreeFile(file);
 
 	/* Now parse all the lines */
@@ -3003,7 +2989,7 @@ load_ident(void)
 	}
 
 	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
+	tokenize_reset_context();
 	MemoryContextSwitchTo(oldcxt);
 
 	if (!ok)
diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample
index 7433050112..f72f471d23 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -5,31 +5,24 @@
 # documentation for a complete description of this file.  A short
 # synopsis follows.
 #
+# -------------------------------
+# Authentication records
+# -------------------------------
+#
 # This file controls: which hosts are allowed to connect, how clients
 # are authenticated, which PostgreSQL user names they can use, which
 # databases they can access.  Records take one of these forms:
 #
-# include           FILE
-# include_if_exists FILE
-# include_dir       DIRECTORY
-# local             DATABASE  USER  METHOD  [OPTIONS]
-# host              DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
-# hostssl           DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
-# hostnossl         DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
-# hostgssenc        DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
-# hostnogssenc      DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# local         DATABASE  USER  METHOD  [OPTIONS]
+# host          DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# hostssl       DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# hostnossl     DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# hostgssenc    DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
+# hostnogssenc  DATABASE  USER  ADDRESS  METHOD  [OPTIONS]
 #
 # (The uppercase items must be replaced by actual values.)
 #
-# If the first field is "include", "include_if_exists" or "include_dir", it's
-# not a mapping record but a directive to include records from respectively
-# another file, another file if it exists or all the files in the given
-# directory ending in '.conf'.  FILE is the file name to include, and
-# DIR is the directory name containing the file(s) to include. FILE and
-# DIRECTORY can be specified with a relative or absolute path, and can be
-# double quoted if they contains spaces.
-#
-# Otherwise the first field is the connection type:
+# 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
@@ -75,11 +68,34 @@
 # its special character, and just match a database or username with
 # that name.
 #
+# --------------------------------
+# Inclusion records
+# ---------------------------------
+#
+# This files allow the inclusion of external files or directories holding
+# extra authentication records, using the following keywords:
+#
+# include           FILE
+# include_if_exists FILE
+# include_dir       DIRECTORY
+#
+# FILE is the file name to include, and DIR is the directory name containing
+# the file(s) to include.  Any file in a directory will be loaded if suffixed
+# with ".conf".  The files of a directory are ordered by name.
+# include_if_exists ignored missing files.  FILE and DIRECTORY can be
+# specified as a relative or absolute path, and can be double-quoted if they
+# contain spaces.
+#
+# -------------------------------
+# Miscellaneous
+# -------------------------------
+#
 # This file is read on server startup and when the server receives a
 # SIGHUP signal.  If you edit the file on a running system, you have to
 # SIGHUP the server for the changes to take effect, run "pg_ctl reload",
 # or execute "SELECT pg_reload_conf()".
 #
+# ---------------------------------
 # Put your actual configuration here
 # ----------------------------------
 #
diff --git a/src/backend/libpq/pg_ident.conf.sample b/src/backend/libpq/pg_ident.conf.sample
index 8e3fa29135..8d9b028aa3 100644
--- a/src/backend/libpq/pg_ident.conf.sample
+++ b/src/backend/libpq/pg_ident.conf.sample
@@ -1,17 +1,18 @@
 # PostgreSQL User Name Maps
 # =========================
 #
+# ------------------------------
+# Ident Records
+# ------------------------------
+#
 # Refer to the PostgreSQL documentation, chapter "Client
 # Authentication" for a complete description.  A short synopsis
 # follows.
 #
 # This file controls PostgreSQL user name mapping.  It maps external
 # user names to their corresponding PostgreSQL user names.  Records
-# are one of these forms:
+# are of the form:
 #
-# include           FILE
-# include_if_exists FILE
-# include_dir       DIRECTORY
 # MAPNAME           SYSTEM-USERNAME  PG-USERNAME
 #
 # (The uppercase quantities must be replaced by actual values.)
@@ -42,6 +43,28 @@
 # system user names and PostgreSQL user names are the same, you don't
 # need anything in this file.
 #
+# ------------------------------
+# Inclusion records
+# ------------------------------
+#
+# This files allow the inclusion of external files or directories holding
+# extra records, using the following keywords:
+#
+# include           FILE
+# include_if_exists FILE
+# include_dir       DIRECTORY
+#
+# FILE is the file name to include, and DIR is the directory name containing
+# the file(s) to include.  Any file in a directory will be loaded if suffixed
+# with ".conf".  The files of a directory are ordered by name.
+# include_if_exists ignored missing files.  FILE and DIRECTORY can be
+# specified as a relative or absolute path, and can be double-quoted if they
+# contain spaces.
+#
+# -------------------------------
+# Miscellaneous
+# -------------------------------
+#
 # This file is read on server startup and when the postmaster receives
 # a SIGHUP signal.  If you edit the file on a running system, you have
 # to SIGHUP the postmaster for the changes to take effect.  You can
diff --git a/src/backend/utils/adt/hbafuncs.c b/src/backend/utils/adt/hbafuncs.c
index f9c99d41c6..bab6c3176c 100644
--- a/src/backend/utils/adt/hbafuncs.c
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -374,7 +374,6 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 	List	   *hba_lines = NIL;
 	ListCell   *line;
 	int			rule_number = 0;
-	MemoryContext linecxt;
 	MemoryContext hbacxt;
 	MemoryContext oldcxt;
 
@@ -386,7 +385,8 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 	 */
 	file = open_auth_file(HbaFileName, ERROR, 0, NULL);
 
-	linecxt = tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3, 0);
+	tokenize_init_context();
+	tokenize_auth_file(HbaFileName, file, &hba_lines, DEBUG3, 0);
 	FreeFile(file);
 
 	/* Now parse all the lines */
@@ -413,7 +413,7 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 	}
 
 	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
+	tokenize_reset_context();
 	/* Free parse_hba_line memory */
 	MemoryContextSwitchTo(oldcxt);
 	MemoryContextDelete(hbacxt);
@@ -523,7 +523,6 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 	List	   *ident_lines = NIL;
 	ListCell   *line;
 	int			map_number = 0;
-	MemoryContext linecxt;
 	MemoryContext identcxt;
 	MemoryContext oldcxt;
 
@@ -534,8 +533,9 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 	 * entry.)
 	 */
 	file = open_auth_file(IdentFileName, ERROR, 0, NULL);
+	tokenize_init_context();
 
-	linecxt = tokenize_auth_file(IdentFileName, file, &ident_lines, DEBUG3, 0);
+	tokenize_auth_file(IdentFileName, file, &ident_lines, DEBUG3, 0);
 	FreeFile(file);
 
 	/* Now parse all the lines */
@@ -562,7 +562,7 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 	}
 
 	/* Free tokenizer memory */
-	MemoryContextDelete(linecxt);
+	tokenize_reset_context();
 	/* Free parse_ident_line memory */
 	MemoryContextSwitchTo(oldcxt);
 	MemoryContextDelete(identcxt);
diff --git a/src/test/authentication/meson.build b/src/test/authentication/meson.build
index c2b48c43c9..cfc23fa213 100644
--- a/src/test/authentication/meson.build
+++ b/src/test/authentication/meson.build
@@ -7,6 +7,7 @@ tests += {
       't/001_password.pl',
       't/002_saslprep.pl',
       't/003_peer.pl',
+      't/004_file_inclusion.pl',
     ],
   },
 }
diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 2ae723de66..e5815a5390 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -88,21 +88,6 @@
    Backslash line continuation applies even within quoted text or comments.
   </para>
 
-  <para>
-   Each record can either be an inclusion directive or an authentication
-   record.  Inclusion directives specify 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>, <literal>include_if_exists</literal> or
-   <literal>include_dir</literal> directive and the file or directory to be
-   included.  The file or directory can be a relative of absolute path, and can
-   be double quoted if needed.  For the <literal>include_dir</literal> form,
-   all files not starting with a <literal>.</literal> and ending with
-   <literal>.conf</literal> will be included.  Multiple files within an include
-   directory are processed in file name order (according to C locale rules,
-   i.e., numbers before letters, and uppercase letters before lowercase ones).
-  </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,
@@ -115,12 +100,24 @@
    access is denied.
   </para>
 
+  <para>
+   Each record can be an inclusion directive or an authentication record.
+   Inclusion directives specify files that can be included, that contain
+   additional records.  The records will be inserted in place of the
+   inclusion records.  Those records only contains two fields:
+   <literal>include</literal>, <literal>include_if_exists</literal> or
+   <literal>include_dir</literal> directive and the file or directory to be
+   included.  The file or directory can be a relative of absolute path, and can
+   be double-quoted.  For the <literal>include_dir</literal> form, all files
+   not starting with a <literal>.</literal> and ending with
+   <literal>.conf</literal> will be included.  Multiple files within an include
+   directory are processed in file name order (according to C locale rules,
+   i.e., numbers before letters, and uppercase letters before lowercase ones).
+  </para>
+
   <para>
    A record can have several formats:
 <synopsis>
-include             <replaceable>file</replaceable>
-include_if_exists   <replaceable>file</replaceable>
-include_dir         <replaceable>directory</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>
@@ -132,43 +129,13 @@ hostssl             <replaceable>database</replaceable>  <replaceable>user</repl
 hostnossl           <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>      <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
 hostgssenc          <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>      <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
 hostnogssenc        <replaceable>database</replaceable>  <replaceable>user</replaceable>  <replaceable>IP-address</replaceable>  <replaceable>IP-mask</replaceable>      <replaceable>auth-method</replaceable>  <optional><replaceable>auth-options</replaceable></optional>
+include             <replaceable>file</replaceable>
+include_if_exists   <replaceable>file</replaceable>
+include_dir         <replaceable>directory</replaceable>
 </synopsis>
    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>include_if_exists</literal></term>
-     <listitem>
-      <para>
-       This line will be replaced with the content of the given file if the
-       file exists and can be read.  Otherwise, a message will be logged to
-       indicate that the file is skipped.
-      </para>
-     </listitem>
-    </varlistentry>
-
-    <varlistentry>
-     <term><literal>include_dir</literal></term>
-     <listitem>
-      <para>
-       This line will be replaced with the content of all the files found in
-       the directory, if they don't start with a <literal>.</literal> and end
-       with <literal>.conf</literal>, processed in file name order (according
-       to C locale rules, i.e., numbers before letters, and uppercase letters
-       before lowercase ones).
-      </para>
-     </listitem>
-    </varlistentry>
-
     <varlistentry>
      <term><literal>local</literal></term>
      <listitem>
@@ -706,6 +673,39 @@ openssl x509 -in myclient.crt -noout --subject -nameopt RFC2253 | sed "s/^subjec
       </para>
      </listitem>
     </varlistentry>
+
+    <varlistentry>
+     <term><literal>include</literal></term>
+     <listitem>
+      <para>
+       This line will be replaced by the contents of the given file.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>include_if_exists</literal></term>
+     <listitem>
+      <para>
+       This line will be replaced with the content of the given file if the
+       file exists and can be read.  Otherwise, a message will be logged to
+       indicate that the file is skipped.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><literal>include_dir</literal></term>
+     <listitem>
+      <para>
+       This line will be replaced with the content of all the files found in
+       the directory, if they don't start with a <literal>.</literal> and end
+       with <literal>.conf</literal>, processed in file name order (according
+       to C locale rules, i.e., numbers before letters, and uppercase letters
+       before lowercase ones).
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
   </para>
 
@@ -916,9 +916,9 @@ local   db1,db2,@demodbs  all                                   md5
    configuration parameter.)
    The ident map file contains lines of two general form:
 <synopsis>
+<replaceable>map-name</replaceable> <replaceable>system-username</replaceable> <replaceable>database-username</replaceable>
 <replaceable>include</replaceable> <replaceable>file</replaceable>
 <replaceable>include_dir</replaceable> <replaceable>directory</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
    <filename>pg_hba.conf</filename>.  The
@@ -929,9 +929,8 @@ local   db1,db2,@demodbs  all                                   md5
    used repeatedly to specify multiple user-mappings within a single map.
   </para>
   <para>
-   As for <filename>pg_hba.conf</filename>, the lines in this file can either
-   be inclusion directives or user name map records, and follow the same
-   rules.
+   As for <filename>pg_hba.conf</filename>, the lines in this file can
+   be inclusion directives, following the same rules.
   </para>
   <para>
    There is no restriction regarding how many database users a given
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index a21c3fee15..d38b42c5cd 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -1016,7 +1016,7 @@
        <structfield>line_number</structfield> <type>int4</type>
       </para>
       <para>
-       Line number of this rule the given <literal>file_name</literal>
+       Line number of this rule in <literal>file_name</literal>
       </para></entry>
      </row>
 
@@ -1175,8 +1175,7 @@
        <structfield>line_number</structfield> <type>int4</type>
       </para>
       <para>
-       Line number of this map in the corresponding
-       <literal>file_name</literal>
+       Line number of this map in <literal>file_name</literal>
       </para></entry>
      </row>
 
-- 
2.38.1

