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 pg_hba.conf file is a set of records, one per line. Blank lines are ignored, as is any text after the # 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., all or replication) 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. @@ -821,7 +823,7 @@ local db1,db2,@demodbs all md5 map-name system-username database-username - Comments and whitespace are handled in the same way as in + Comments, whitespace and line continuations are handled in the same way as in pg_hba.conf. The map-name is an arbitrary name that will be used to refer to this mapping in pg_hba.conf. 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)) + /* Collect the next input line, handling backslash continuations */ + resetStringInfo(&buf); + + while (!feof(file) && !ferror(file)) { - int save_errno = errno; + /* Make sure there's a reasonable amount of room in the buffer */ + enlargeStringInfo(&buf, 128); - if (!ferror(file)) - break; /* normal EOF */ - /* I/O error! */ - ereport(elevel, - (errcode_for_file_access(), - errmsg("could not read file \"%s\": %m", filename))); - err_msg = psprintf("could not read file \"%s\": %s", - filename, strerror(save_errno)); - rawline[0] = '\0'; - } - if (strlen(rawline) == MAX_LINE - 1) - { - /* Line too long! */ - ereport(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"; - } + /* 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; - /* Strip trailing linebreak from rawline */ - lineptr = rawline + strlen(rawline) - 1; - while (lineptr >= rawline && (*lineptr == '\n' || *lineptr == '\r')) - *lineptr-- = '\0'; + 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; + } + + /* 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; }