Re: Allow continuations in "pg_hba.conf" files - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Allow continuations in "pg_hba.conf" files
Date
Msg-id 425349.1599072793@sss.pgh.pa.us
Whole thread Raw
In response to Re: Allow continuations in "pg_hba.conf" files  (Fabien COELHO <coelho@cri.ensmp.fr>)
Responses Re: Allow continuations in "pg_hba.conf" files
List pgsql-hackers
Fabien COELHO <coelho@cri.ensmp.fr> writes:
> [ pg-hba-cont-4.patch ]

I looked this over and I think it seems reasonable, but there's
something else we should do.  If people are writing lines long
enough that they need to continue them, how long will it be
before they overrun the line length limit?  Admittedly, there's
a good deal of daylight between 80 characters and 8K, but if
we're busy removing restrictions on password length in an adjacent
thread [1], I think we ought to get rid of pg_hba.conf's line length
restriction while we're at it.

Accordingly, I borrowed some code from that thread and present
the attached revision.  I also added some test coverage, since
that was lacking before, and wordsmithed docs and comments slightly.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/09512C4F-8CB9-4021-B455-EF4C4F0D55A0%40amazon.com

diff --git a/doc/src/sgml/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 5cd88b462d..d62d1a061c 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -77,13 +77,15 @@
    The general format of the <filename>pg_hba.conf</filename> file is
    a set of records, one per line. Blank lines are ignored, as is any
    text after the <literal>#</literal> comment character.
-   Records cannot be continued across lines.
+   A record can be continued onto the next line by ending the line with
+   a backslash. (Backslashes are not special except at the end of a line.)
    A record is made
    up of a number of fields which are separated by spaces and/or tabs.
    Fields can contain white space if the field value is double-quoted.
    Quoting one of the keywords in a database, user, or address field (e.g.,
    <literal>all</literal> or <literal>replication</literal>) makes the word lose its special
    meaning, and just match a database, user, or host with that name.
+   Backslash line continuation applies even within quoted text or comments.
   </para>

   <para>
@@ -821,7 +823,7 @@ local   db1,db2,@demodbs  all                                   md5
 <synopsis>
 <replaceable>map-name</replaceable> <replaceable>system-username</replaceable>
<replaceable>database-username</replaceable>
 </synopsis>
-   Comments and whitespace are handled in the same way as in
+   Comments, whitespace and line continuations are handled in the same way as in
    <filename>pg_hba.conf</filename>.  The
    <replaceable>map-name</replaceable> is an arbitrary name that will be used to
    refer to this mapping in <filename>pg_hba.conf</filename>. The other
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index 9d63830553..5991a21cf2 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -29,6 +29,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "common/ip.h"
+#include "common/string.h"
 #include "funcapi.h"
 #include "libpq/ifaddr.h"
 #include "libpq/libpq.h"
@@ -54,7 +55,6 @@


 #define MAX_TOKEN    256
-#define MAX_LINE    8192

 /* callback data for check_network_callback */
 typedef struct check_network_data
@@ -166,11 +166,19 @@ pg_isblank(const char c)
 /*
  * Grab one token out of the string pointed to by *lineptr.
  *
- * Tokens are strings of non-blank
- * characters bounded by blank characters, commas, beginning of line, and
- * end of line. Blank means space or tab. Tokens can be delimited by
- * double quotes (this allows the inclusion of blanks, but not newlines).
- * Comments (started by an unquoted '#') are skipped.
+ * Tokens are strings of non-blank characters bounded by blank characters,
+ * commas, beginning of line, and end of line.  Blank means space or tab.
+ *
+ * Tokens can be delimited by double quotes (this allows the inclusion of
+ * blanks or '#', but not newlines).  As in SQL, write two double-quotes
+ * to represent a double quote.
+ *
+ * Comments (started by an unquoted '#') are skipped, i.e. the remainder
+ * of the line is ignored.
+ *
+ * (Note that line continuation processing happens before tokenization.
+ * Thus, if a continuation occurs within quoted text or a comment, the
+ * quoted text or comment is considered to continue to the next line.)
  *
  * The token, if any, is returned at *buf (a buffer of size bufsz), and
  * *lineptr is advanced past the token.
@@ -470,6 +478,7 @@ static MemoryContext
 tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
 {
     int            line_number = 1;
+    StringInfoData buf;
     MemoryContext linecxt;
     MemoryContext oldcxt;

@@ -478,47 +487,72 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
                                     ALLOCSET_SMALL_SIZES);
     oldcxt = MemoryContextSwitchTo(linecxt);

+    initStringInfo(&buf);
+
     *tok_lines = NIL;

     while (!feof(file) && !ferror(file))
     {
-        char        rawline[MAX_LINE];
         char       *lineptr;
         List       *current_line = NIL;
         char       *err_msg = NULL;
+        int            last_backslash_buflen = 0;
+        int            continuations = 0;

-        if (!fgets(rawline, sizeof(rawline), file))
-        {
-            int            save_errno = errno;
+        /* Collect the next input line, handling backslash continuations */
+        resetStringInfo(&buf);

-            if (!ferror(file))
-                break;            /* normal EOF */
-            /* I/O error! */
-            ereport(elevel,
-                    (errcode_for_file_access(),
-                     errmsg("could not read file \"%s\": %m", filename)));
-            err_msg = psprintf("could not read file \"%s\": %s",
-                               filename, strerror(save_errno));
-            rawline[0] = '\0';
-        }
-        if (strlen(rawline) == MAX_LINE - 1)
+        while (!feof(file) && !ferror(file))
         {
-            /* Line too long! */
-            ereport(elevel,
-                    (errcode(ERRCODE_CONFIG_FILE_ERROR),
-                     errmsg("authentication file line too long"),
-                     errcontext("line %d of configuration file \"%s\"",
-                                line_number, filename)));
-            err_msg = "authentication file line too long";
-        }
+            /* Make sure there's a reasonable amount of room in the buffer */
+            enlargeStringInfo(&buf, 128);
+
+            /* Read some data, appending it to what we already have */
+            if (fgets(buf.data + buf.len, buf.maxlen - buf.len, file) == NULL)
+            {
+                int            save_errno = errno;
+
+                if (!ferror(file))
+                    break;        /* normal EOF */
+                /* I/O error! */
+                ereport(elevel,
+                        (errcode_for_file_access(),
+                         errmsg("could not read file \"%s\": %m", filename)));
+                err_msg = psprintf("could not read file \"%s\": %s",
+                                   filename, strerror(save_errno));
+                resetStringInfo(&buf);
+                break;
+            }
+            buf.len += strlen(buf.data + buf.len);
+
+            /* If we haven't got a whole line, loop to read more */
+            if (!(buf.len > 0 && buf.data[buf.len - 1] == '\n'))
+                continue;
+
+            /* 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;
+            }

-        /* Strip trailing linebreak from rawline */
-        lineptr = rawline + strlen(rawline) - 1;
-        while (lineptr >= rawline && (*lineptr == '\n' || *lineptr == '\r'))
-            *lineptr-- = '\0';
+            /* Nope, so we have the whole line */
+            break;
+        }

         /* Parse fields */
-        lineptr = rawline;
+        lineptr = buf.data;
         while (*lineptr && err_msg == NULL)
         {
             List       *current_field;
@@ -538,12 +572,12 @@ tokenize_file(const char *filename, FILE *file, List **tok_lines, int elevel)
             tok_line = (TokenizedLine *) palloc(sizeof(TokenizedLine));
             tok_line->fields = current_line;
             tok_line->line_num = line_number;
-            tok_line->raw_line = pstrdup(rawline);
+            tok_line->raw_line = pstrdup(buf.data);
             tok_line->err_msg = err_msg;
             *tok_lines = lappend(*tok_lines, tok_line);
         }

-        line_number++;
+        line_number += continuations + 1;
     }

     MemoryContextSwitchTo(oldcxt);
diff --git a/src/test/authentication/t/001_password.pl b/src/test/authentication/t/001_password.pl
index 1b4323fe2a..59b1b403c5 100644
--- a/src/test/authentication/t/001_password.pl
+++ b/src/test/authentication/t/001_password.pl
@@ -29,7 +29,8 @@ sub reset_pg_hba
     my $hba_method = shift;

     unlink($node->data_dir . '/pg_hba.conf');
-    $node->append_conf('pg_hba.conf', "local all all $hba_method");
+    # just for testing purposes, use a continuation line
+    $node->append_conf('pg_hba.conf', "local all all\\\n $hba_method");
     $node->reload;
     return;
 }

pgsql-hackers by date:

Previous
From: Dave Page
Date:
Subject: Re: Kerberos support broken on MSVC builds for Windows x64?
Next
From: Jesse Zhang
Date:
Subject: Re: Fix for configure error in 9.5/9.6 on macOS 11.0 Big Sur