public inbox for [email protected]  
help / color / mirror / Atom feed
From: Julien Rouhaud <[email protected]>
Subject: [PATCH v2 3/4] Allow file inclusion in pg_hba and pg_ident files.
Date: Mon, 21 Feb 2022 15:45:26 +0800

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                | 373 ++++++++++++++++---------
 src/backend/libpq/pg_hba.conf.sample   |   8 +-
 src/backend/libpq/pg_ident.conf.sample |   8 +-
 src/backend/utils/adt/hbafuncs.c       |  53 +++-
 src/include/catalog/pg_proc.dat        |  12 +-
 src/include/libpq/hba.h                |   2 +
 src/test/regress/expected/rules.out    |  12 +-
 9 files changed, 395 insertions(+), 155 deletions(-)

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 0de40a9626..07c6679a52 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -10430,12 +10430,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>
 
@@ -10571,6 +10590,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 e673ff5474..85988fdeda 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,6 +118,12 @@ static const char *const UserAuthName[] =
 };
 
 
+static FILE *open_inc_file(HbaIncludeKind kind, const char *inc_filename,
+						   const char *outer_filename, int elevel,
+						   char **err_msg, char **inc_fullname);
+static void tokenize_auth_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,
@@ -327,6 +339,68 @@ next_field_expand(const char *filename, char **lineptr,
 	return tokens;
 }
 
+/*
+ * 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;
+}
+
 /*
  * tokenize_inc_file
  *		Expand a file included from another file into an hba "field"
@@ -355,36 +429,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,6 +472,169 @@ tokenize_inc_file(List *tokens,
 	return tokens;
 }
 
+/*
+ * 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
+ * elevel: message logging level
+ *
+ * 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.
+ *
+ */
+static void
+tokenize_auth_file_with_context(MemoryContext linecxt, const char *filename,
+						   FILE *file, List **tok_lines, int elevel)
+{
+	StringInfoData buf;
+	int			line_number = 1;
+	MemoryContext oldcxt;
+
+	oldcxt = MemoryContextSwitchTo(linecxt);
+
+	initStringInfo(&buf);
+
+	while (!feof(file) && !ferror(file))
+	{
+		TokenizedAuthLine *tok_line;
+		char	   *lineptr;
+		List	   *current_line = NIL;
+		char	   *err_msg = NULL;
+		int			last_backslash_buflen = 0;
+		int			continuations = 0;
+
+		/* Collect the next input line, handling backslash continuations */
+		resetStringInfo(&buf);
+
+		while (pg_get_line_append(file, &buf, NULL))
+		{
+			/* Strip trailing newline, including \r in case we're on Windows */
+			buf.len = pg_strip_crlf(buf.data);
+
+			/*
+			 * Check for backslash continuation.  The backslash must be after
+			 * the last place we found a continuation, else two backslashes
+			 * followed by two \n's would behave surprisingly.
+			 */
+			if (buf.len > last_backslash_buflen &&
+				buf.data[buf.len - 1] == '\\')
+			{
+				/* Continuation, so strip it and keep reading */
+				buf.data[--buf.len] = '\0';
+				last_backslash_buflen = buf.len;
+				continuations++;
+				continue;
+			}
+
+			/* Nope, so we have the whole line */
+			break;
+		}
+
+		if (ferror(file))
+		{
+			/* I/O error! */
+			int			save_errno = errno;
+
+			ereport(elevel,
+					(errcode_for_file_access(),
+					 errmsg("could not read file \"%s\": %m", filename)));
+			err_msg = psprintf("could not read file \"%s\": %s",
+							   filename, strerror(save_errno));
+			break;
+		}
+
+		/* Parse fields */
+		lineptr = buf.data;
+		while (*lineptr && err_msg == NULL)
+		{
+			List	   *current_field;
+
+			current_field = next_field_expand(filename, &lineptr,
+											  elevel, &err_msg);
+			/* add field to line, unless we are at EOL or comment start */
+			if (current_field != NIL)
+				current_line = lappend(current_line, current_field);
+		}
+
+		/*
+		 * Reached EOL; no need to emit line to TokenizedAuthLine list if it's
+		 * boring.
+		 */
+		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)
+		{
+			HbaToken *first, *second;
+
+			first = linitial(linitial_node(List, current_line));
+			second = linitial(lsecond_node(List, current_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_auth_file_with_context(linecxt, inc_fullname,
+													inc_file, tok_lines,
+													elevel);
+
+					FreeFile(inc_file);
+					pfree(inc_fullname);
+
+					/*
+					 * The line is fulle 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);
+}
 
 /*
  * Does user belong to role?
@@ -1796,6 +2008,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);
 
@@ -2602,118 +2815,26 @@ parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
 /*
  * Tokenize the given file.
  *
- * The output is a list of TokenizedAuthLine structs; see struct definition
- * above.
+ * Wrapper around tokenize_auth_file_with_context, creating a dedicated memory
+ * context.
  *
- * filename: the absolute path to the target file
- * file: the already-opened target file
- * tok_lines: receives output list
- * elevel: message logging level
- *
- * Errors are reported by logging messages at ereport level elevel and by
- * adding TokenizedAuthLine structs containing non-null err_msg fields to the
- * output list.
- *
- * Return value is a memory context which contains all memory allocated by
+ * 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)
+tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
+				   int elevel)
 {
-	int			line_number = 1;
-	StringInfoData buf;
 	MemoryContext linecxt;
-	MemoryContext oldcxt;
 
 	linecxt = AllocSetContextCreate(CurrentMemoryContext,
 									"tokenize_auth_file",
 									ALLOCSET_SMALL_SIZES);
-	oldcxt = MemoryContextSwitchTo(linecxt);
-
-	initStringInfo(&buf);
 
 	*tok_lines = NIL;
 
-	while (!feof(file) && !ferror(file))
-	{
-		char	   *lineptr;
-		List	   *current_line = NIL;
-		char	   *err_msg = NULL;
-		int			last_backslash_buflen = 0;
-		int			continuations = 0;
-
-		/* Collect the next input line, handling backslash continuations */
-		resetStringInfo(&buf);
-
-		while (pg_get_line_append(file, &buf, NULL))
-		{
-			/* Strip trailing newline, including \r in case we're on Windows */
-			buf.len = pg_strip_crlf(buf.data);
-
-			/*
-			 * Check for backslash continuation.  The backslash must be after
-			 * the last place we found a continuation, else two backslashes
-			 * followed by two \n's would behave surprisingly.
-			 */
-			if (buf.len > last_backslash_buflen &&
-				buf.data[buf.len - 1] == '\\')
-			{
-				/* Continuation, so strip it and keep reading */
-				buf.data[--buf.len] = '\0';
-				last_backslash_buflen = buf.len;
-				continuations++;
-				continue;
-			}
-
-			/* Nope, so we have the whole line */
-			break;
-		}
-
-		if (ferror(file))
-		{
-			/* I/O error! */
-			int			save_errno = errno;
-
-			ereport(elevel,
-					(errcode_for_file_access(),
-					 errmsg("could not read file \"%s\": %m", filename)));
-			err_msg = psprintf("could not read file \"%s\": %s",
-							   filename, strerror(save_errno));
-			break;
-		}
-
-		/* Parse fields */
-		lineptr = buf.data;
-		while (*lineptr && err_msg == NULL)
-		{
-			List	   *current_field;
-
-			current_field = next_field_expand(filename, &lineptr,
-											  elevel, &err_msg);
-			/* add field to line, unless we are at EOL or comment start */
-			if (current_field != NIL)
-				current_line = lappend(current_line, current_field);
-		}
-
-		/*
-		 * Reached EOL; emit line to TokenizedAuthLine list unless it's boring
-		 * */
-		if (current_line != NIL || err_msg != NULL)
-		{
-			TokenizedAuthLine *tok_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);
-		}
-
-		line_number += continuations + 1;
-	}
-
-	MemoryContextSwitchTo(oldcxt);
+	tokenize_auth_file_with_context(linecxt, filename, file, tok_lines,
+									elevel);
 
 	return linecxt;
 }
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 2189c298b8..4c9df29427 100644
--- a/src/backend/utils/adt/hbafuncs.c
+++ b/src/backend/utils/adt/hbafuncs.c
@@ -26,16 +26,18 @@
 
 
 static void fill_hba_line(Tuplestorestate *tuple_store, TupleDesc tupdesc,
-						  int lineno, HbaLine *hba, const char *err_msg);
+						  int tule_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 mappint_number, const char *filename,
 							int lineno, IdentLine *ident, const char *err_msg);
 static void fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc);
 static ArrayType *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
@@ -51,7 +53,8 @@ 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)
 {
 	Datum		values[NUM_PG_HBA_FILE_RULES_ATTS];
 	bool		nulls[NUM_PG_HBA_FILE_RULES_ATTS];
@@ -70,6 +73,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);
 
@@ -213,7 +223,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 */
@@ -235,6 +245,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;
@@ -265,12 +276,15 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
 		HbaLine    *hbaline = NULL;
 
-		/* don't parse lines that already have errors */
+		/* Only parse lines that don't already have an error */
 		if (tok_line->err_msg == NULL)
+		{
 			hbaline = parse_hba_line(tok_line, DEBUG3);
+			rule_number++;
+		}
 
-		fill_hba_line(tuple_store, tupdesc, tok_line->line_num,
-					  hbaline, tok_line->err_msg);
+		fill_hba_line(tuple_store, tupdesc, rule_number, tok_line->file_name,
+					  tok_line->line_num, hbaline, tok_line->err_msg);
 	}
 
 	/* Free tokenizer memory */
@@ -281,7 +295,7 @@ fill_hba_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 }
 
 /* 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
@@ -298,7 +312,8 @@ 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)
+				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];
@@ -311,6 +326,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);
 
@@ -323,7 +345,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 */
@@ -345,6 +367,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;
@@ -375,12 +398,16 @@ fill_ident_view(Tuplestorestate *tuple_store, TupleDesc tupdesc)
 		TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
 		IdentLine   *identline = NULL;
 
-		/* don't parse lines that already have errors */
+		/* Only parse lines that don't already have an error */
 		if (tok_line->err_msg == NULL)
+		{
 			identline = parse_ident_line(tok_line, DEBUG3);
+			mapping_number++;
+		}
 
-		fill_ident_line(tuple_store, tupdesc, tok_line->line_num, identline,
-					  tok_line->err_msg);
+		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 04d4edb228..e3baafc3e3 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6116,16 +6116,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 62cf0d8674..e6f274cb59 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


--uqjaptwdgaq4xou6
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment;
	filename="v2-0004-POC-Add-a-pg_hba_matches-function.patch"



view thread (33+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [PATCH v2 3/4] Allow file inclusion in pg_hba and pg_ident files.
  In-Reply-To: <no-message-id-1860165@localhost>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox