Re: Keywords in pg_hba.conf should be field-specific

Started by Pavel Stehuleover 14 years ago36 messages
#1Pavel Stehule
pavel.stehule@gmail.com

2011/6/15 Pavel Stehule <pavel.stehule@gmail.com>:

Show quoted text

Hello

I try to apply your patch, but it is finished with some failed hinks.

Please, can you refresh your patch

Regards

Pavel

[pavel@nemesis postgresql]$ patch -p1 < pghba.patch
patching file src/backend/libpq/hba.c
Hunk #1 succeeded at 45 (offset 2 lines).
Hunk #2 succeeded at 56 (offset 2 lines).
Hunk #3 succeeded at 120 (offset 2 lines).
Hunk #4 succeeded at 212 (offset 2 lines).
Hunk #5 succeeded at 244 (offset 2 lines).
Hunk #6 succeeded at 286 (offset 2 lines).
Hunk #7 succeeded at 327 (offset 2 lines).
Hunk #8 succeeded at 363 (offset 2 lines).
Hunk #9 succeeded at 372 (offset 2 lines).
Hunk #10 succeeded at 411 (offset 2 lines).
Hunk #11 FAILED at 470.
Hunk #12 succeeded at 783 (offset 4 lines).
Hunk #13 FAILED at 795.
Hunk #14 succeeded at 866 (offset 25 lines).
Hunk #15 succeeded at 882 (offset 25 lines).
Hunk #16 succeeded at 899 (offset 25 lines).
Hunk #17 succeeded at 918 (offset 25 lines).
Hunk #18 succeeded at 939 (offset 25 lines).
Hunk #19 succeeded at 972 (offset 25 lines).
Hunk #20 succeeded at 989 (offset 25 lines).
Hunk #21 FAILED at 995.
Hunk #22 succeeded at 1034 (offset 25 lines).
Hunk #23 succeeded at 1102 (offset 25 lines).
Hunk #24 succeeded at 1112 (offset 25 lines).
Hunk #25 succeeded at 1167 (offset 25 lines).
Hunk #26 FAILED at 1178.
Hunk #27 succeeded at 1255 (offset 25 lines).
Hunk #28 succeeded at 1267 (offset 25 lines).
Hunk #29 succeeded at 1360 (offset 25 lines).
Hunk #30 succeeded at 1657 (offset 25 lines).
Hunk #31 succeeded at 1686 (offset 25 lines).
Hunk #32 succeeded at 1834 (offset 25 lines).
Hunk #33 succeeded at 1845 (offset 25 lines).
Hunk #34 succeeded at 2006 (offset 25 lines).
Hunk #35 succeeded at 2074 (offset 25 lines).
4 out of 35 hunks FAILED -- saving rejects to file src/backend/libpq/hba.c.rej
patching file src/include/libpq/hba.h

#2Brendan Jurd
direvus@gmail.com
In reply to: Pavel Stehule (#1)
1 attachment(s)
Fwd: Keywords in pg_hba.conf should be field-specific

On 16 June 2011 00:22, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I try to apply your patch, but it is finished with some failed hinks.

Please, can you refresh your patch

Hi Pavel,

Thanks for taking a look.  I have attached v2 of the patch, as against
current HEAD.  I've also added the new patch to the CF app.  I look
forward to your comments.

Cheers,
BJ

Attachments:

hba-keywords-v2.diff.bz2application/x-bzip2; name=hba-keywords-v2.diff.bz2Download
#3Alvaro Herrera
alvherre@commandprompt.com
In reply to: Brendan Jurd (#2)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Brendan Jurd's message of vie jun 17 19:31:41 -0400 2011:

On 16 June 2011 00:22, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I try to apply your patch, but it is finished with some failed hinks.

Please, can you refresh your patch

Hi Pavel,

Thanks for taking a look.  I have attached v2 of the patch, as against
current HEAD.  I've also added the new patch to the CF app.  I look
forward to your comments.

Is this really a WIP patch? I'm playing a bit with it currently, seems
fairly sane.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#4Brendan Jurd
direvus@gmail.com
In reply to: Alvaro Herrera (#3)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 18 June 2011 13:43, Alvaro Herrera <alvherre@commandprompt.com> wrote:

Is this really a WIP patch?  I'm playing a bit with it currently, seems
fairly sane.

In this case, the WIP designation is meant to convey "warning: only
casual testing has beeen done". I tried it out with various
permutations of pg_hba.conf, and it worked as advertised in those
tests, but I have not made any attempt to formulate a more rigorous
testing regimen.

In particular I haven't tested that the more exotic authentication
methods still work properly, and I can't recall whether I tested
recursive file inclusion and group membership.

Is that a wrongful use of the WIP designation?

Cheers,
BJ

#5Pavel Stehule
pavel.stehule@gmail.com
In reply to: Brendan Jurd (#4)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Hello Brendan,

I checked your patch, it is applied cleanly and I don't see any mayor
problem. This patch does all what is expected.

I have two minor comments

a) you don't use macro "token_matches" consistently

func: parse_hba_line

<------>if (strcmp(token->string, "local") == 0)

should be
if (token_is_keyword(token, "local"))
...

I don't see any sense when somebody use a quotes there.

b) probably you can simplify a memory management using own two
persistent memory context - and you can swap it. Then functions like
free_hba_record, clean_hba_list, free_lines should be removed.

Regards

Pavel Stehule

2011/6/18 Brendan Jurd <direvus@gmail.com>:

Show quoted text

On 18 June 2011 13:43, Alvaro Herrera <alvherre@commandprompt.com> wrote:

Is this really a WIP patch?  I'm playing a bit with it currently, seems
fairly sane.

In this case, the WIP designation is meant to convey "warning: only
casual testing has beeen done".  I tried it out with various
permutations of pg_hba.conf, and it worked as advertised in those
tests, but I have not made any attempt to formulate a more rigorous
testing regimen.

In particular I haven't tested that the more exotic authentication
methods still work properly, and I can't recall whether I tested
recursive file inclusion and group membership.

Is that a wrongful use of the WIP designation?

Cheers,
BJ

#6Pavel Stehule
pavel.stehule@gmail.com
In reply to: Pavel Stehule (#5)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

sorry

a) you don't use macro "token_matches" consistently

should be

a) you don't use macro "token_is_keyword" consistently

it should be used for all keywords

Regards

Pavel Stehule

#7Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#5)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Pavel Stehule's message of lun jun 20 11:34:25 -0400 2011:

b) probably you can simplify a memory management using own two
persistent memory context - and you can swap it. Then functions like
free_hba_record, clean_hba_list, free_lines should be removed.

Yeah, I reworked the patch with something like that over the weekend.
Not all of it though. I'll send an updated version shortly.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#8Alvaro Herrera
alvherre@commandprompt.com
In reply to: Alvaro Herrera (#7)
1 attachment(s)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Alvaro Herrera's message of lun jun 20 12:19:37 -0400 2011:

Excerpts from Pavel Stehule's message of lun jun 20 11:34:25 -0400 2011:

b) probably you can simplify a memory management using own two
persistent memory context - and you can swap it. Then functions like
free_hba_record, clean_hba_list, free_lines should be removed.

Yeah, I reworked the patch with something like that over the weekend.
Not all of it though. I'll send an updated version shortly.

Here it is, please let me know what you think. I took the liberty of
cleaning up some things that were clearly historical leftovers.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

hba-keywords-v3.diffapplication/octet-stream; name=hba-keywords-v3.diffDownload
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 315,329 **** ClientAuthentication(Port *port)
  
  	/*
  	 * Get the authentication method to use for this frontend/database
! 	 * combination.  Note: a failure return indicates a problem with the hba
! 	 * config file, not with the request.  hba.c should have dropped an error
! 	 * message into the postmaster logfile if it failed.
  	 */
! 	if (hba_getauthmethod(port) != STATUS_OK)
! 		ereport(FATAL,
! 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 				 errmsg("missing or erroneous pg_hba.conf file"),
! 				 errhint("See server log for details.")));
  
  	/*
  	 * Enable immediate response to SIGTERM/SIGINT/timeout interrupts. (We
--- 315,325 ----
  
  	/*
  	 * Get the authentication method to use for this frontend/database
! 	 * combination.  Note: we do not parse the file at this point; this has
! 	 * already been done elsewhere.  hba.c dropped an error message
! 	 * into the server logfile if parsing the hba config file failed.
  	 */
! 	hba_getauthmethod(port);
  
  	/*
  	 * Enable immediate response to SIGTERM/SIGINT/timeout interrupts. (We
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 35,50 ****
  #include "utils/acl.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  
  
  #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
  #define atoxid(x)  ((TransactionId) strtoul((x), NULL, 10))
  
- /* This is used to separate values in multi-valued column strings */
- #define MULTI_VALUE_SEP "\001"
- 
  #define MAX_TOKEN	256
  
  /* callback data for check_network_callback */
  typedef struct check_network_data
  {
--- 35,51 ----
  #include "utils/acl.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  
  
  #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
  #define atoxid(x)  ((TransactionId) strtoul((x), NULL, 10))
  
  #define MAX_TOKEN	256
  
+ #define token_is_keyword(t, k)	(!t->quoted && strcmp(t->string, k) == 0)
+ #define token_matches(t, k)  (strcmp(t->string, k) == 0)
+ 
  /* callback data for check_network_callback */
  typedef struct check_network_data
  {
***************
*** 53,78 **** typedef struct check_network_data
  	bool		result;			/* set to true if match */
  } check_network_data;
  
! /* pre-parsed content of HBA config file: list of HbaLine structs */
  static List *parsed_hba_lines = NIL;
  
  /*
   * These variables hold the pre-parsed contents of the ident usermap
!  * configuration file.	ident_lines is a list of sublists, one sublist for
!  * each (non-empty, non-comment) line of the file.	The sublist items are
!  * palloc'd strings, one string per token on the line.  Note there will always
!  * be at least one token, since blank lines are not entered in the data
!  * structure.  ident_line_nums is an integer list containing the actual line
!  * number for each line represented in ident_lines.
   */
  static List *ident_lines = NIL;
  static List *ident_line_nums = NIL;
  
  
! static void tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums);
! static char *tokenize_inc_file(const char *outer_filename,
! 				  const char *inc_filename);
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
--- 54,98 ----
  	bool		result;			/* set to true if match */
  } check_network_data;
  
! 
! /*
!  * A single string token lexed from the HBA config file, together with whether
!  * the token had been quoted.
!  */
! typedef struct HbaToken
! {
! 	char   *string;
! 	bool	quoted;
! } HbaToken;
! 
! /*
!  * pre-parsed content of HBA config file: list of HbaLine structs.
!  * parsed_hba_context is the memory context where it lives.
!  */
  static List *parsed_hba_lines = NIL;
+ static MemoryContext parsed_hba_context = NULL;
  
  /*
   * These variables hold the pre-parsed contents of the ident usermap
!  * configuration file.	ident_lines is a triple-nested list of lines, fields
!  * and tokens, as returned by tokenize_file.  There will be one line in
!  * ident_lines for each (non-empty, non-comment) line of the file.  Note there
!  * will always be at least one field, since blank lines are not entered in the
!  * data structure.  ident_line_nums is an integer list containing the actual
!  * line number for each line represented in ident_lines.  ident_context is
!  * the memory context holding all this.
   */
  static List *ident_lines = NIL;
  static List *ident_line_nums = NIL;
+ static MemoryContext ident_context = NULL;
  
  
! static MemoryContext tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums);
! static List *tokenize_inc_file(List *tokens,
! 				  const char *outer_filename, const char *inc_filename);
! static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
! 				   int line_num);
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
***************
*** 104,113 **** pg_isblank(const char c)
   * whichever comes first. If no more tokens on line, position the file to the
   * beginning of the next line or EOF, whichever comes first.
   *
!  * Handle comments. Treat unquoted keywords that might be role, database, or
!  * host names specially, by appending a newline to them.  Also, when
!  * a token is terminated by a comma, the comma is included in the returned
!  * token.
   */
  static bool
  next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
--- 124,131 ----
   * whichever comes first. If no more tokens on line, position the file to the
   * beginning of the next line or EOF, whichever comes first.
   *
!  * Handle comments. Also, when a token is terminated by a comma, the comma is
!  * included in the returned token.
   */
  static bool
  next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
***************
*** 198,245 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
  
  	*buf = '\0';
  
- 	if (!saw_quote &&
- 		(strcmp(start_buf, "all") == 0 ||
- 		 strcmp(start_buf, "samehost") == 0 ||
- 		 strcmp(start_buf, "samenet") == 0 ||
- 		 strcmp(start_buf, "sameuser") == 0 ||
- 		 strcmp(start_buf, "samegroup") == 0 ||
- 		 strcmp(start_buf, "samerole") == 0 ||
- 		 strcmp(start_buf, "replication") == 0))
- 	{
- 		/* append newline to a magical keyword */
- 		*buf++ = '\n';
- 		*buf = '\0';
- 	}
- 
  	return (saw_quote || buf > start_buf);
  }
  
  /*
!  *	 Tokenize file and handle file inclusion and comma lists. We have
!  *	 to  break	apart  the	commas	to	expand	any  file names then
!  *	 reconstruct with commas.
   *
!  * The result is a palloc'd string, or NULL if we have reached EOL.
   */
! static char *
! next_token_expand(const char *filename, FILE *file)
  {
  	char		buf[MAX_TOKEN];
- 	char	   *comma_str = pstrdup("");
- 	bool		got_something = false;
  	bool		trailing_comma;
  	bool		initial_quote;
! 	char	   *incbuf;
! 	int			needed;
  
  	do
  	{
  		if (!next_token(file, buf, sizeof(buf), &initial_quote))
  			break;
  
- 		got_something = true;
- 
  		if (strlen(buf) > 0 && buf[strlen(buf) - 1] == ',')
  		{
  			trailing_comma = true;
--- 216,243 ----
  
  	*buf = '\0';
  
  	return (saw_quote || buf > start_buf);
  }
  
  /*
!  * Tokenize one HBA field from a file, handling file inclusion and comma lists.
   *
!  * The result is a List of HbaToken structs for each individual token,
!  * or NIL if we reached EOL.
   */
! static List *
! next_field_expand(const char *filename, FILE *file)
  {
  	char		buf[MAX_TOKEN];
  	bool		trailing_comma;
  	bool		initial_quote;
! 	List	   *tokens = NIL;
  
  	do
  	{
  		if (!next_token(file, buf, sizeof(buf), &initial_quote))
  			break;
  
  		if (strlen(buf) > 0 && buf[strlen(buf) - 1] == ',')
  		{
  			trailing_comma = true;
***************
*** 250,335 **** next_token_expand(const char *filename, FILE *file)
  
  		/* Is this referencing a file? */
  		if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! 			incbuf = tokenize_inc_file(filename, buf + 1);
  		else
! 			incbuf = pstrdup(buf);
! 
! 		needed = strlen(comma_str) + strlen(incbuf) + 1;
! 		if (trailing_comma)
! 			needed++;
! 		comma_str = repalloc(comma_str, needed);
! 		strcat(comma_str, incbuf);
! 		if (trailing_comma)
! 			strcat(comma_str, MULTI_VALUE_SEP);
! 		pfree(incbuf);
! 	} while (trailing_comma);
  
! 	if (!got_something)
! 	{
! 		pfree(comma_str);
! 		return NULL;
! 	}
  
! 	return comma_str;
  }
  
- 
  /*
!  * Free memory used by lines/tokens (i.e., structure built by tokenize_file)
   */
! static void
! free_lines(List **lines, List **line_nums)
  {
! 	/*
! 	 * Either both must be non-NULL, or both must be NULL
! 	 */
! 	Assert((*lines != NIL && *line_nums != NIL) ||
! 		   (*lines == NIL && *line_nums == NIL));
! 
! 	if (*lines)
! 	{
! 		/*
! 		 * "lines" is a list of lists; each of those sublists consists of
! 		 * palloc'ed tokens, so we want to free each pointed-to token in a
! 		 * sublist, followed by the sublist itself, and finally the whole
! 		 * list.
! 		 */
! 		ListCell   *line;
  
! 		foreach(line, *lines)
! 		{
! 			List	   *ln = lfirst(line);
! 			ListCell   *token;
! 
! 			foreach(token, ln)
! 				pfree(lfirst(token));
! 			/* free the sublist structure itself */
! 			list_free(ln);
! 		}
! 		/* free the list structure itself */
! 		list_free(*lines);
! 		/* clear the static variable */
! 		*lines = NIL;
! 	}
! 
! 	if (*line_nums)
! 	{
! 		list_free(*line_nums);
! 		*line_nums = NIL;
! 	}
  }
  
  
! static char *
! tokenize_inc_file(const char *outer_filename,
  				  const char *inc_filename)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
  	List	   *inc_lines;
  	List	   *inc_line_nums;
! 	ListCell   *line;
! 	char	   *comma_str;
  
  	if (is_absolute_path(inc_filename))
  	{
--- 248,293 ----
  
  		/* Is this referencing a file? */
  		if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! 			tokens = tokenize_inc_file(tokens, filename, buf + 1);
  		else
! 		{
! 			HbaToken *token = palloc0(sizeof(HbaToken));
  
! 			token->string = pstrdup(buf);
! 			token->quoted = initial_quote;
! 			tokens = lappend(tokens, token);
! 		}
! 	} while (trailing_comma);
  
! 	return tokens;
  }
  
  /*
!  * Copy a HbaToken struct into freshly palloc'd memory.
   */
! static HbaToken *
! copy_hba_token(HbaToken *in)
  {
! 	HbaToken *out = palloc0(sizeof(HbaToken));
  
! 	/* XXX --- reduce palloc overhead here? */
! 	out->string = pstrdup(in->string);
! 	out->quoted = in->quoted;
! 	return out;
  }
  
  
! static List *
! tokenize_inc_file(List *tokens,
! 				  const char *outer_filename,
  				  const char *inc_filename)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
  	List	   *inc_lines;
  	List	   *inc_line_nums;
! 	ListCell   *inc_line;
! 	MemoryContext linecxt;
  
  	if (is_absolute_path(inc_filename))
  	{
***************
*** 355,451 **** tokenize_inc_file(const char *outer_filename,
  				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
  						inc_filename, inc_fullname)));
  		pfree(inc_fullname);
! 
! 		/* return single space, it matches nothing */
! 		return pstrdup(" ");
  	}
  
  	/* There is possible recursion here if the file contains @ */
! 	tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);
  
  	FreeFile(inc_file);
  	pfree(inc_fullname);
  
! 	/* Create comma-separated string from List */
! 	comma_str = pstrdup("");
! 	foreach(line, inc_lines)
  	{
! 		List	   *token_list = (List *) lfirst(line);
! 		ListCell   *token;
  
! 		foreach(token, token_list)
  		{
! 			int			oldlen = strlen(comma_str);
! 			int			needed;
! 
! 			needed = oldlen + strlen(lfirst(token)) + 1;
! 			if (oldlen > 0)
! 				needed++;
! 			comma_str = repalloc(comma_str, needed);
! 			if (oldlen > 0)
! 				strcat(comma_str, MULTI_VALUE_SEP);
! 			strcat(comma_str, lfirst(token));
! 		}
! 	}
  
! 	free_lines(&inc_lines, &inc_line_nums);
  
! 	/* if file is empty, return single space rather than empty string */
! 	if (strlen(comma_str) == 0)
! 	{
! 		pfree(comma_str);
! 		return pstrdup(" ");
  	}
  
! 	return comma_str;
  }
  
  
  /*
!  * Tokenize the given file, storing the resulting data into two lists:
!  * a list of sublists, each sublist containing the tokens in a line of
!  * the file, and a list of line numbers.
   *
   * filename must be the absolute path to the target file.
   */
! static void
  tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums)
  {
  	List	   *current_line = NIL;
  	int			line_number = 1;
! 	char	   *buf;
  
  	*lines = *line_nums = NIL;
  
  	while (!feof(file) && !ferror(file))
  	{
! 		buf = next_token_expand(filename, file);
  
! 		/* add token to list, unless we are at EOL or comment start */
! 		if (buf)
  		{
  			if (current_line == NIL)
  			{
  				/* make a new line List, record its line number */
! 				current_line = lappend(current_line, buf);
  				*lines = lappend(*lines, current_line);
  				*line_nums = lappend_int(*line_nums, line_number);
  			}
  			else
  			{
! 				/* append token to current line's list */
! 				current_line = lappend(current_line, buf);
  			}
  		}
  		else
  		{
  			/* we are at real or logical EOL, so force a new line List */
  			current_line = NIL;
- 			/* Advance line number whenever we reach EOL */
  			line_number++;
  		}
  	}
  }
  
  
--- 313,413 ----
  				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
  						inc_filename, inc_fullname)));
  		pfree(inc_fullname);
! 		return tokens;
  	}
  
  	/* There is possible recursion here if the file contains @ */
! 	linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);
  
  	FreeFile(inc_file);
  	pfree(inc_fullname);
  
! 	foreach(inc_line, inc_lines)
  	{
! 		List	   *inc_fields = lfirst(inc_line);
! 		ListCell   *inc_field;
  
! 		foreach(inc_field, inc_fields)
  		{
! 			List	   *inc_tokens = lfirst(inc_field);
! 			ListCell   *inc_token;
  
! 			foreach(inc_token, inc_tokens)
! 			{
! 				HbaToken   *token = lfirst(inc_token);
  
! 				tokens = lappend(tokens, copy_hba_token(token));
! 			}
! 		}
  	}
  
! 	MemoryContextDelete(linecxt);
! 	return tokens;
  }
  
  
  /*
!  * Tokenize the given file, storing the resulting data into two Lists: a
!  * List of lines, and a List of line numbers.
!  *
!  * The list of lines is a triple-nested List structure.  Each line is a List of
!  * fields, and each field is a List of HbaTokens.
   *
   * filename must be the absolute path to the target file.
+  *
+  * Return value is a memory context which contains all memory allocated by
+  * this function.
   */
! static MemoryContext
  tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums)
  {
  	List	   *current_line = NIL;
+ 	List	   *current_field = NIL;
  	int			line_number = 1;
! 	MemoryContext	linecxt;
! 	MemoryContext	oldcxt;
! 
! 	linecxt = AllocSetContextCreate(TopMemoryContext,
! 									"tokenize file cxt",
! 									ALLOCSET_DEFAULT_MINSIZE,
! 									ALLOCSET_DEFAULT_INITSIZE,
! 									ALLOCSET_DEFAULT_MAXSIZE);
! 	oldcxt = MemoryContextSwitchTo(linecxt);
  
  	*lines = *line_nums = NIL;
  
  	while (!feof(file) && !ferror(file))
  	{
! 		current_field = next_field_expand(filename, file);
  
! 		/* add tokens to list, unless we are at EOL or comment start */
! 		if (list_length(current_field) > 0)
  		{
  			if (current_line == NIL)
  			{
  				/* make a new line List, record its line number */
! 				current_line = lappend(current_line, current_field);
  				*lines = lappend(*lines, current_line);
  				*line_nums = lappend_int(*line_nums, line_number);
  			}
  			else
  			{
! 				/* append tokens to current line's list */
! 				current_line = lappend(current_line, current_field);
  			}
  		}
  		else
  		{
  			/* we are at real or logical EOL, so force a new line List */
  			current_line = NIL;
  			line_number++;
  		}
  	}
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ 
+ 	return linecxt;
  }
  
  
***************
*** 473,546 **** is_member(Oid userid, const char *role)
  }
  
  /*
!  * Check comma-separated list for a match to role, allowing group names.
!  *
!  * NB: param_str is destructively modified!  In current usage, this is
!  * okay only because this code is run after forking off from the postmaster,
!  * and so it doesn't matter that we clobber the stored hba info.
   */
  static bool
! check_role(const char *role, Oid roleid, char *param_str)
  {
! 	char	   *tok;
  
! 	for (tok = strtok(param_str, MULTI_VALUE_SEP);
! 		 tok != NULL;
! 		 tok = strtok(NULL, MULTI_VALUE_SEP))
  	{
! 		if (tok[0] == '+')
  		{
! 			if (is_member(roleid, tok + 1))
  				return true;
  		}
! 		else if (strcmp(tok, role) == 0 ||
! 				 (strcmp(tok, "replication\n") == 0 &&
! 				  strcmp(role, "replication") == 0) ||
! 				 strcmp(tok, "all\n") == 0)
  			return true;
  	}
- 
  	return false;
  }
  
  /*
!  * Check to see if db/role combination matches param string.
!  *
!  * NB: param_str is destructively modified!  In current usage, this is
!  * okay only because this code is run after forking off from the postmaster,
!  * and so it doesn't matter that we clobber the stored hba info.
   */
  static bool
! check_db(const char *dbname, const char *role, Oid roleid, char *param_str)
  {
! 	char	   *tok;
  
! 	for (tok = strtok(param_str, MULTI_VALUE_SEP);
! 		 tok != NULL;
! 		 tok = strtok(NULL, MULTI_VALUE_SEP))
  	{
  		if (am_walsender)
  		{
  			/* walsender connections can only match replication keyword */
! 			if (strcmp(tok, "replication\n") == 0)
  				return true;
  		}
! 		else if (strcmp(tok, "all\n") == 0)
  			return true;
! 		else if (strcmp(tok, "sameuser\n") == 0)
  		{
  			if (strcmp(dbname, role) == 0)
  				return true;
  		}
! 		else if (strcmp(tok, "samegroup\n") == 0 ||
! 				 strcmp(tok, "samerole\n") == 0)
  		{
  			if (is_member(roleid, dbname))
  				return true;
  		}
! 		else if (strcmp(tok, "replication\n") == 0)
  			continue;			/* never match this if not walsender */
! 		else if (strcmp(tok, dbname) == 0)
  			return true;
  	}
  	return false;
--- 435,497 ----
  }
  
  /*
!  * Check HbaToken list for a match to role, allowing group names.
   */
  static bool
! check_role(const char *role, Oid roleid, List *tokens)
  {
! 	ListCell	   *cell;
! 	HbaToken	   *tok;
  
! 	foreach(cell, tokens)
  	{
! 		tok = lfirst(cell);
! 		if (!tok->quoted && tok->string[0] == '+')
  		{
! 			if (is_member(roleid, tok->string + 1))
  				return true;
  		}
! 		else if (token_matches(tok, role) ||
! 				 token_is_keyword(tok, "all"))
  			return true;
  	}
  	return false;
  }
  
  /*
!  * Check to see if db/role combination matches HbaToken list.
   */
  static bool
! check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
  {
! 	ListCell	   *cell;
! 	HbaToken	   *tok;
  
! 	foreach(cell, tokens)
  	{
+ 		tok = lfirst(cell);
  		if (am_walsender)
  		{
  			/* walsender connections can only match replication keyword */
! 			if (token_is_keyword(tok, "replication"))
  				return true;
  		}
! 		else if (token_is_keyword(tok, "all"))
  			return true;
! 		else if (token_is_keyword(tok, "sameuser"))
  		{
  			if (strcmp(dbname, role) == 0)
  				return true;
  		}
! 		else if (token_is_keyword(tok, "samegroup") ||
! 				 token_is_keyword(tok, "samerole"))
  		{
  			if (is_member(roleid, dbname))
  				return true;
  		}
! 		else if (token_is_keyword(tok, "replication"))
  			continue;			/* never match this if not walsender */
! 		else if (token_matches(tok, dbname))
  			return true;
  	}
  	return false;
***************
*** 784,790 **** check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
  } while (0);
  
  #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
! 	if (parsedline->auth_method != methodval) \
  		INVALID_AUTH_OPTION(optname, validmethods); \
  } while (0);
  
--- 735,741 ----
  } while (0);
  
  #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
! 	if (hbaline->auth_method != methodval) \
  		INVALID_AUTH_OPTION(optname, validmethods); \
  } while (0);
  
***************
*** 800,828 **** check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
  	} \
  } while (0);
  
  
  /*
!  * Parse one line in the hba config file and store the result in
!  * a HbaLine structure.
   */
! static bool
! parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  {
! 	char	   *token;
  	struct addrinfo *gai_result;
  	struct addrinfo hints;
  	int			ret;
  	char	   *cidr_slash;
  	char	   *unsupauth;
! 	ListCell   *line_item;
! 
! 	line_item = list_head(line);
  
  	parsedline->linenumber = line_num;
  
  	/* Check the record type. */
! 	token = lfirst(line_item);
! 	if (strcmp(token, "local") == 0)
  	{
  #ifdef HAVE_UNIX_SOCKETS
  		parsedline->conntype = ctLocal;
--- 751,833 ----
  	} \
  } while (0);
  
+ /*
+  * IDENT_FIELD_ABSENT:
+  * Throw an error and exit the function if the given ident field ListCell is
+  * not populated.
+  *
+  * IDENT_MULTI_VALUE:
+  * Throw an error and exit the function if the given ident token List has more
+  * than one element.
+  */
+ #define IDENT_FIELD_ABSENT(field) do {\
+ 	if (!field) { \
+ 		ereport(LOG, \
+ 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ 				 errmsg("missing entry in file \"%s\" at end of line %d", \
+ 						IdentFileName, line_number))); \
+ 		*error_p = true; \
+ 		return; \
+ 	} \
+ } while (0);
+ 
+ #define IDENT_MULTI_VALUE(tokens) do {\
+ 	if (tokens->length > 1) { \
+ 		ereport(LOG, \
+ 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ 				 errmsg("multiple values in ident field"), \
+ 				 errcontext("line %d of configuration file \"%s\"", \
+ 						line_number, IdentFileName))); \
+ 		*error_p = true; \
+ 		return; \
+ 	} \
+ } while (0);
+ 
  
  /*
!  * Parse one tokenised line from the hba config file and store the result in a
!  * HbaLine structure, or NULL if parsing fails.
!  *
!  * The tokenised line is a List of fields, each field being a List of
!  * HbaTokens.
!  *
!  * Note: this function leaks memory when an error occurs.  Caller is expected
!  * to have set a memory context that will be reset if this function returns
!  * NULL.
   */
! static HbaLine *
! parse_hba_line(List *line, int line_num)
  {
! 	char	   *str;
  	struct addrinfo *gai_result;
  	struct addrinfo hints;
  	int			ret;
  	char	   *cidr_slash;
  	char	   *unsupauth;
! 	ListCell   *field;
! 	List	   *tokens;
! 	ListCell   *tokencell;
! 	HbaToken   *token;
! 	HbaLine	   *parsedline;
  
+ 	parsedline = palloc0(sizeof(HbaLine));
  	parsedline->linenumber = line_num;
  
  	/* Check the record type. */
! 	field = list_head(line);
! 	tokens = lfirst(field);
! 	if (tokens->length > 1)
! 	{
! 		ereport(LOG,
! 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 				 errmsg("multiple values specified for connection type"),
! 				 errhint("Specify exactly one connection type per line."),
! 				 errcontext("line %d of configuration file \"%s\"",
! 							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	token = linitial(tokens);
! 	if (strcmp(token->string, "local") == 0)
  	{
  #ifdef HAVE_UNIX_SOCKETS
  		parsedline->conntype = ctLocal;
***************
*** 832,846 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  				 errmsg("local connections are not supported by this build"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  #endif
  	}
! 	else if (strcmp(token, "host") == 0
! 			 || strcmp(token, "hostssl") == 0
! 			 || strcmp(token, "hostnossl") == 0)
  	{
  
! 		if (token[4] == 's')	/* "hostssl" */
  		{
  			/* SSL support must be actually active, else complain */
  #ifdef USE_SSL
--- 837,851 ----
  				 errmsg("local connections are not supported by this build"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  #endif
  	}
! 	else if (strcmp(token->string, "host") == 0 ||
! 			 strcmp(token->string, "hostssl") == 0 ||
! 			 strcmp(token->string, "hostnossl") == 0)
  	{
  
! 		if (token->string[4] == 's')	/* "hostssl" */
  		{
  			/* SSL support must be actually active, else complain */
  #ifdef USE_SSL
***************
*** 854,860 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  						 errhint("Set ssl = on in postgresql.conf."),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return false;
  			}
  #else
  			ereport(LOG,
--- 859,865 ----
  						 errhint("Set ssl = on in postgresql.conf."),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return NULL;
  			}
  #else
  			ereport(LOG,
***************
*** 863,873 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			  errhint("Compile with --with-openssl to use SSL connections."),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  #endif
  		}
  #ifdef USE_SSL
! 		else if (token[4] == 'n')		/* "hostnossl" */
  		{
  			parsedline->conntype = ctHostNoSSL;
  		}
--- 868,878 ----
  			  errhint("Compile with --with-openssl to use SSL connections."),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
  #endif
  		}
  #ifdef USE_SSL
! 		else if (token->string[4] == 'n')		/* "hostnossl" */
  		{
  			parsedline->conntype = ctHostNoSSL;
  		}
***************
*** 883,945 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid connection type \"%s\"",
! 						token),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
! 	/* Get the database. */
! 	line_item = lnext(line_item);
! 	if (!line_item)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before database specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
- 	parsedline->database = pstrdup(lfirst(line_item));
  
! 	/* Get the role. */
! 	line_item = lnext(line_item);
! 	if (!line_item)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before role specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
- 	parsedline->role = pstrdup(lfirst(line_item));
  
  	if (parsedline->conntype != ctLocal)
  	{
  		/* Read the IP address field. (with or without CIDR netmask) */
! 		line_item = lnext(line_item);
! 		if (!line_item)
  		{
  			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("end-of-line before IP address specification"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  		}
! 		token = lfirst(line_item);
  
! 		if (strcmp(token, "all\n") == 0)
  		{
  			parsedline->ip_cmp_method = ipCmpAll;
  		}
! 		else if (strcmp(token, "samehost\n") == 0)
  		{
  			/* Any IP on this host is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameHost;
  		}
! 		else if (strcmp(token, "samenet\n") == 0)
  		{
  			/* Any IP on the host's subnets is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameNet;
--- 888,973 ----
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid connection type \"%s\"",
! 						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
! 	/* Get the databases. */
! 	field = lnext(field);
! 	if (!field)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before database specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	parsedline->databases = NIL;
! 	tokens = lfirst(field);
! 	foreach(tokencell, tokens)
! 	{
! 		parsedline->databases = lappend(parsedline->databases,
! 										copy_hba_token(lfirst(tokencell)));
  	}
  
! 	/* Get the roles. */
! 	field = lnext(field);
! 	if (!field)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before role specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	parsedline->roles = NIL;
! 	tokens = lfirst(field);
! 	foreach(tokencell, tokens)
! 	{
! 		parsedline->roles = lappend(parsedline->roles,
! 									copy_hba_token(lfirst(tokencell)));
  	}
  
  	if (parsedline->conntype != ctLocal)
  	{
  		/* Read the IP address field. (with or without CIDR netmask) */
! 		field = lnext(field);
! 		if (!field)
  		{
  			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("end-of-line before IP address specification"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
! 		}
! 		tokens = lfirst(field);
! 		if (tokens->length > 1)
! 		{
! 			ereport(LOG,
! 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 					 errmsg("multiple values specified for host address"),
! 					 errhint("Specify one address range per line."),
! 					 errcontext("line %d of configuration file \"%s\"",
! 								line_num, HbaFileName)));
! 			return NULL;
  		}
! 		token = linitial(tokens);
  
! 		if (token_is_keyword(token, "all"))
  		{
  			parsedline->ip_cmp_method = ipCmpAll;
  		}
! 		else if (token_is_keyword(token, "samehost"))
  		{
  			/* Any IP on this host is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameHost;
  		}
! 		else if (token_is_keyword(token, "samenet"))
  		{
  			/* Any IP on the host's subnets is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameNet;
***************
*** 950,959 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			parsedline->ip_cmp_method = ipCmpMask;
  
  			/* need a modifiable copy of token */
! 			token = pstrdup(token);
  
  			/* Check if it has a CIDR suffix and if so isolate it */
! 			cidr_slash = strchr(token, '/');
  			if (cidr_slash)
  				*cidr_slash = '\0';
  
--- 978,987 ----
  			parsedline->ip_cmp_method = ipCmpMask;
  
  			/* need a modifiable copy of token */
! 			str = pstrdup(token->string);
  
  			/* Check if it has a CIDR suffix and if so isolate it */
! 			cidr_slash = strchr(str, '/');
  			if (cidr_slash)
  				*cidr_slash = '\0';
  
***************
*** 967,990 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			hints.ai_addr = NULL;
  			hints.ai_next = NULL;
  
! 			ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
  			if (ret == 0 && gai_result)
  				memcpy(&parsedline->addr, gai_result->ai_addr,
  					   gai_result->ai_addrlen);
  			else if (ret == EAI_NONAME)
! 				parsedline->hostname = token;
  			else
  			{
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						 errmsg("invalid IP address \"%s\": %s",
! 								token, gai_strerror(ret)),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
  				if (gai_result)
  					pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 				pfree(token);
! 				return false;
  			}
  
  			pg_freeaddrinfo_all(hints.ai_family, gai_result);
--- 995,1017 ----
  			hints.ai_addr = NULL;
  			hints.ai_next = NULL;
  
! 			ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result);
  			if (ret == 0 && gai_result)
  				memcpy(&parsedline->addr, gai_result->ai_addr,
  					   gai_result->ai_addrlen);
  			else if (ret == EAI_NONAME)
! 				parsedline->hostname = str;
  			else
  			{
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						 errmsg("invalid IP address \"%s\": %s",
! 								str, gai_strerror(ret)),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
  				if (gai_result)
  					pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 				return NULL;
  			}
  
  			pg_freeaddrinfo_all(hints.ai_family, gai_result);
***************
*** 994,1053 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			{
  				if (parsedline->hostname)
  				{
- 					*cidr_slash = '/';	/* restore token for message */
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
! 									token),
! 						   errcontext("line %d of configuration file \"%s\"",
! 									  line_num, HbaFileName)));
! 					pfree(token);
! 					return false;
  				}
  
  				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
  										  parsedline->addr.ss_family) < 0)
  				{
- 					*cidr_slash = '/';	/* restore token for message */
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid CIDR mask in address \"%s\"",
! 									token),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					pfree(token);
! 					return false;
  				}
! 				pfree(token);
  			}
  			else if (!parsedline->hostname)
  			{
  				/* Read the mask field. */
! 				pfree(token);
! 				line_item = lnext(line_item);
! 				if (!line_item)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						  errmsg("end-of-line before netmask specification"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return false;
  				}
! 				token = lfirst(line_item);
  
! 				ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
  				if (ret || !gai_result)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid IP mask \"%s\": %s",
! 									token, gai_strerror(ret)),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					if (gai_result)
  						pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 					return false;
  				}
  
  				memcpy(&parsedline->mask, gai_result->ai_addr,
--- 1021,1088 ----
  			{
  				if (parsedline->hostname)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
! 									token->string),
! 							 errcontext("line %d of configuration file \"%s\"",
! 										line_num, HbaFileName)));
! 					return NULL;
  				}
  
  				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
  										  parsedline->addr.ss_family) < 0)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid CIDR mask in address \"%s\"",
! 									token->string),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return NULL;
  				}
! 				pfree(str);
  			}
  			else if (!parsedline->hostname)
  			{
  				/* Read the mask field. */
! 				pfree(str);
! 				field = lnext(field);
! 				if (!field)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						  errmsg("end-of-line before netmask specification"),
+ 							 errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return NULL;
  				}
! 				tokens = lfirst(field);
! 				if (tokens->length > 1)
! 				{
! 					ereport(LOG,
! 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 						     errmsg("multiple values specified for netmask"),
! 							 errcontext("line %d of configuration file \"%s\"",
! 										line_num, HbaFileName)));
! 					return NULL;
! 				}
! 				token = linitial(tokens);
  
! 				ret = pg_getaddrinfo_all(token->string, NULL,
! 										 &hints, &gai_result);
  				if (ret || !gai_result)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid IP mask \"%s\": %s",
! 									token->string, gai_strerror(ret)),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					if (gai_result)
  						pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 					return NULL;
  				}
  
  				memcpy(&parsedline->mask, gai_result->ai_addr,
***************
*** 1061,1115 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  							 errmsg("IP address and mask do not match"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return false;
  				}
  			}
  		}
  	}							/* != ctLocal */
  
  	/* Get the authentication method */
! 	line_item = lnext(line_item);
! 	if (!line_item)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before authentication method"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
! 	token = lfirst(line_item);
  
  	unsupauth = NULL;
! 	if (strcmp(token, "trust") == 0)
  		parsedline->auth_method = uaTrust;
! 	else if (strcmp(token, "ident") == 0)
  		parsedline->auth_method = uaIdent;
! 	else if (strcmp(token, "peer") == 0)
  		parsedline->auth_method = uaPeer;
! 	else if (strcmp(token, "password") == 0)
  		parsedline->auth_method = uaPassword;
! 	else if (strcmp(token, "krb5") == 0)
  #ifdef KRB5
  		parsedline->auth_method = uaKrb5;
  #else
  		unsupauth = "krb5";
  #endif
! 	else if (strcmp(token, "gss") == 0)
  #ifdef ENABLE_GSS
  		parsedline->auth_method = uaGSS;
  #else
  		unsupauth = "gss";
  #endif
! 	else if (strcmp(token, "sspi") == 0)
  #ifdef ENABLE_SSPI
  		parsedline->auth_method = uaSSPI;
  #else
  		unsupauth = "sspi";
  #endif
! 	else if (strcmp(token, "reject") == 0)
  		parsedline->auth_method = uaReject;
! 	else if (strcmp(token, "md5") == 0)
  	{
  		if (Db_user_namespace)
  		{
--- 1096,1161 ----
  							 errmsg("IP address and mask do not match"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return NULL;
  				}
  			}
  		}
  	}							/* != ctLocal */
  
  	/* Get the authentication method */
! 	field = lnext(field);
! 	if (!field)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before authentication method"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
! 	tokens = lfirst(field);
! 	if (tokens->length > 1)
! 	{
! 		ereport(LOG,
! 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 				 errmsg("multiple values specified for authentication type"),
! 				 errhint("Specify exactly one authentication type per line."),
! 				 errcontext("line %d of configuration file \"%s\"",
! 							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	token = linitial(tokens);
  
  	unsupauth = NULL;
! 	if (strcmp(token->string, "trust") == 0)
  		parsedline->auth_method = uaTrust;
! 	else if (strcmp(token->string, "ident") == 0)
  		parsedline->auth_method = uaIdent;
! 	else if (strcmp(token->string, "peer") == 0)
  		parsedline->auth_method = uaPeer;
! 	else if (strcmp(token->string, "password") == 0)
  		parsedline->auth_method = uaPassword;
! 	else if (strcmp(token->string, "krb5") == 0)
  #ifdef KRB5
  		parsedline->auth_method = uaKrb5;
  #else
  		unsupauth = "krb5";
  #endif
! 	else if (strcmp(token->string, "gss") == 0)
  #ifdef ENABLE_GSS
  		parsedline->auth_method = uaGSS;
  #else
  		unsupauth = "gss";
  #endif
! 	else if (strcmp(token->string, "sspi") == 0)
  #ifdef ENABLE_SSPI
  		parsedline->auth_method = uaSSPI;
  #else
  		unsupauth = "sspi";
  #endif
! 	else if (strcmp(token->string, "reject") == 0)
  		parsedline->auth_method = uaReject;
! 	else if (strcmp(token->string, "md5") == 0)
  	{
  		if (Db_user_namespace)
  		{
***************
*** 1118,1156 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  		}
  		parsedline->auth_method = uaMD5;
  	}
! 	else if (strcmp(token, "pam") == 0)
  #ifdef USE_PAM
  		parsedline->auth_method = uaPAM;
  #else
  		unsupauth = "pam";
  #endif
! 	else if (strcmp(token, "ldap") == 0)
  #ifdef USE_LDAP
  		parsedline->auth_method = uaLDAP;
  #else
  		unsupauth = "ldap";
  #endif
! 	else if (strcmp(token, "cert") == 0)
  #ifdef USE_SSL
  		parsedline->auth_method = uaCert;
  #else
  		unsupauth = "cert";
  #endif
! 	else if (strcmp(token, "radius") == 0)
  		parsedline->auth_method = uaRADIUS;
  	else
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\"",
! 						token),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	if (unsupauth)
--- 1164,1202 ----
  					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
  		}
  		parsedline->auth_method = uaMD5;
  	}
! 	else if (strcmp(token->string, "pam") == 0)
  #ifdef USE_PAM
  		parsedline->auth_method = uaPAM;
  #else
  		unsupauth = "pam";
  #endif
! 	else if (strcmp(token->string, "ldap") == 0)
  #ifdef USE_LDAP
  		parsedline->auth_method = uaLDAP;
  #else
  		unsupauth = "ldap";
  #endif
! 	else if (strcmp(token->string, "cert") == 0)
  #ifdef USE_SSL
  		parsedline->auth_method = uaCert;
  #else
  		unsupauth = "cert";
  #endif
! 	else if (strcmp(token->string, "radius") == 0)
  		parsedline->auth_method = uaRADIUS;
  	else
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\"",
! 						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	if (unsupauth)
***************
*** 1158,1167 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\": not supported by this build",
! 						token),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	/*
--- 1204,1213 ----
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\": not supported by this build",
! 						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	/*
***************
*** 1181,1187 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			 errmsg("krb5 authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	if (parsedline->conntype == ctLocal &&
--- 1227,1233 ----
  			 errmsg("krb5 authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	if (parsedline->conntype == ctLocal &&
***************
*** 1192,1198 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		   errmsg("gssapi authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	if (parsedline->conntype != ctLocal &&
--- 1238,1244 ----
  		   errmsg("gssapi authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	if (parsedline->conntype != ctLocal &&
***************
*** 1203,1217 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			errmsg("peer authentication is only supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	/*
  	 * SSPI authentication can never be enabled on ctLocal connections,
  	 * because it's only supported on Windows, where ctLocal isn't supported.
  	 */
- 
- 
  	if (parsedline->conntype != ctHostSSL &&
  		parsedline->auth_method == uaCert)
  	{
--- 1249,1261 ----
  			errmsg("peer authentication is only supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	/*
  	 * SSPI authentication can never be enabled on ctLocal connections,
  	 * because it's only supported on Windows, where ctLocal isn't supported.
  	 */
  	if (parsedline->conntype != ctHostSSL &&
  		parsedline->auth_method == uaCert)
  	{
***************
*** 1220,1450 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  				 errmsg("cert authentication is only supported on hostssl connections"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	/* Parse remaining arguments */
! 	while ((line_item = lnext(line_item)) != NULL)
  	{
! 		char	   *c;
! 
! 		token = lfirst(line_item);
! 
! 		c = strchr(token, '=');
! 		if (c == NULL)
! 		{
! 			/*
! 			 * Got something that's not a name=value pair.
! 			 */
! 			ereport(LOG,
! 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 					 errmsg("authentication option not in name=value format: %s", token),
! 					 errcontext("line %d of configuration file \"%s\"",
! 								line_num, HbaFileName)));
! 			return false;
! 		}
! 		else
  		{
! 			*c++ = '\0';		/* token now holds "name", c holds "value" */
! 			if (strcmp(token, "map") == 0)
! 			{
! 				if (parsedline->auth_method != uaIdent &&
! 					parsedline->auth_method != uaPeer &&
! 					parsedline->auth_method != uaKrb5 &&
! 					parsedline->auth_method != uaGSS &&
! 					parsedline->auth_method != uaSSPI &&
! 					parsedline->auth_method != uaCert)
! 					INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, krb5, gssapi, sspi and cert"));
! 				parsedline->usermap = pstrdup(c);
! 			}
! 			else if (strcmp(token, "clientcert") == 0)
  			{
  				/*
! 				 * Since we require ctHostSSL, this really can never happen on
! 				 * non-SSL-enabled builds, so don't bother checking for
! 				 * USE_SSL.
  				 */
- 				if (parsedline->conntype != ctHostSSL)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("clientcert can only be configured for \"hostssl\" rows"),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					return false;
- 				}
- 				if (strcmp(c, "1") == 0)
- 				{
- 					if (!secure_loaded_verify_locations())
- 					{
- 						ereport(LOG,
- 								(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 								 errmsg("client certificates can only be checked if a root certificate store is available"),
- 								 errhint("Make sure the root.crt file is present and readable."),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 						return false;
- 					}
- 					parsedline->clientcert = true;
- 				}
- 				else
- 				{
- 					if (parsedline->auth_method == uaCert)
- 					{
- 						ereport(LOG,
- 								(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 								 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 						return false;
- 					}
- 					parsedline->clientcert = false;
- 				}
- 			}
- 			else if (strcmp(token, "pamservice") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
- 				parsedline->pamservice = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldaptls") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
- 				if (strcmp(c, "1") == 0)
- 					parsedline->ldaptls = true;
- 				else
- 					parsedline->ldaptls = false;
- 			}
- 			else if (strcmp(token, "ldapserver") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
- 				parsedline->ldapserver = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapport") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
- 				parsedline->ldapport = atoi(c);
- 				if (parsedline->ldapport == 0)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("invalid LDAP port number: \"%s\"", c),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					return false;
- 				}
- 			}
- 			else if (strcmp(token, "ldapbinddn") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
- 				parsedline->ldapbinddn = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapbindpasswd") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
- 				parsedline->ldapbindpasswd = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapsearchattribute") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
- 				parsedline->ldapsearchattribute = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapbasedn") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
- 				parsedline->ldapbasedn = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapprefix") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
- 				parsedline->ldapprefix = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapsuffix") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
- 				parsedline->ldapsuffix = pstrdup(c);
- 			}
- 			else if (strcmp(token, "krb_server_hostname") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaKrb5, "krb_server_hostname", "krb5");
- 				parsedline->krb_server_hostname = pstrdup(c);
- 			}
- 			else if (strcmp(token, "krb_realm") == 0)
- 			{
- 				if (parsedline->auth_method != uaKrb5 &&
- 					parsedline->auth_method != uaGSS &&
- 					parsedline->auth_method != uaSSPI)
- 					INVALID_AUTH_OPTION("krb_realm", gettext_noop("krb5, gssapi and sspi"));
- 				parsedline->krb_realm = pstrdup(c);
- 			}
- 			else if (strcmp(token, "include_realm") == 0)
- 			{
- 				if (parsedline->auth_method != uaKrb5 &&
- 					parsedline->auth_method != uaGSS &&
- 					parsedline->auth_method != uaSSPI)
- 					INVALID_AUTH_OPTION("include_realm", gettext_noop("krb5, gssapi and sspi"));
- 				if (strcmp(c, "1") == 0)
- 					parsedline->include_realm = true;
- 				else
- 					parsedline->include_realm = false;
- 			}
- 			else if (strcmp(token, "radiusserver") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
- 
- 				MemSet(&hints, 0, sizeof(hints));
- 				hints.ai_socktype = SOCK_DGRAM;
- 				hints.ai_family = AF_UNSPEC;
- 
- 				ret = pg_getaddrinfo_all(c, NULL, &hints, &gai_result);
- 				if (ret || !gai_result)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
- 									c, gai_strerror(ret)),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					if (gai_result)
- 						pg_freeaddrinfo_all(hints.ai_family, gai_result);
- 					return false;
- 				}
- 				pg_freeaddrinfo_all(hints.ai_family, gai_result);
- 				parsedline->radiusserver = pstrdup(c);
- 			}
- 			else if (strcmp(token, "radiusport") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
- 				parsedline->radiusport = atoi(c);
- 				if (parsedline->radiusport == 0)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("invalid RADIUS port number: \"%s\"", c),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					return false;
- 				}
- 			}
- 			else if (strcmp(token, "radiussecret") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
- 				parsedline->radiussecret = pstrdup(c);
- 			}
- 			else if (strcmp(token, "radiusidentifier") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
- 				parsedline->radiusidentifier = pstrdup(c);
- 			}
- 			else
- 			{
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 					errmsg("unrecognized authentication option name: \"%s\"",
! 						   token),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return false;
  			}
  		}
  	}
  
--- 1264,1301 ----
  				 errmsg("cert authentication is only supported on hostssl connections"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	/* Parse remaining arguments */
! 	while ((field = lnext(field)) != NULL)
  	{
! 		tokens = lfirst(field);
! 		foreach(tokencell, tokens)
  		{
! 			char	   *val;
! 			token = lfirst(tokencell);
! 
! 			str = pstrdup(token->string);
! 			val = strchr(str, '=');
! 			if (val == NULL)
  			{
  				/*
! 				 * Got something that's not a name=value pair.
  				 */
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 						 errmsg("authentication option not in name=value format: %s", token->string),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return NULL;
  			}
+ 
+ 			*val++ = '\0';	/* str now holds "name", val holds "value" */
+ 			if (!parse_hba_auth_opt(str, val, parsedline, line_num))
+ 				/* parse_hba_auth_opt already logged the error message */
+ 				return NULL;
+ 			pfree(str);
  		}
  	}
  
***************
*** 1474,1480 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, or ldapsearchattribute together with ldapprefix"),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return false;
  			}
  		}
  		else if (!parsedline->ldapbasedn)
--- 1325,1331 ----
  						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, or ldapsearchattribute together with ldapprefix"),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return NULL;
  			}
  		}
  		else if (!parsedline->ldapbasedn)
***************
*** 1484,1490 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  		}
  	}
  
--- 1335,1341 ----
  					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
  		}
  	}
  
***************
*** 1502,1516 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		parsedline->clientcert = true;
  	}
  
! 	return true;
  }
  
- 
  /*
!  *	Scan the (pre-parsed) hba file line by line, looking for a match
!  *	to the port's connection request.
   */
  static bool
  check_hba(hbaPort *port)
  {
  	Oid			roleid;
--- 1353,1580 ----
  		parsedline->clientcert = true;
  	}
  
! 	return parsedline;
  }
  
  /*
!  * Parse one name-value pair as an authentication option into the given
!  * HbaLine.  Return true if we successfully parse the option, false if we
!  * encounter an error.
   */
  static bool
+ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
+ {
+ 	if (strcmp(name, "map") == 0)
+ 	{
+ 		if (hbaline->auth_method != uaIdent &&
+ 			hbaline->auth_method != uaPeer &&
+ 			hbaline->auth_method != uaKrb5 &&
+ 			hbaline->auth_method != uaGSS &&
+ 			hbaline->auth_method != uaSSPI &&
+ 			hbaline->auth_method != uaCert)
+ 			INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, krb5, gssapi, sspi and cert"));
+ 		hbaline->usermap = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "clientcert") == 0)
+ 	{
+ 		/*
+ 		 * Since we require ctHostSSL, this really can never happen
+ 		 * on non-SSL-enabled builds, so don't bother checking for
+ 		 * USE_SSL.
+ 		 */
+ 		if (hbaline->conntype != ctHostSSL)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("clientcert can only be configured for \"hostssl\" rows"),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			return false;
+ 		}
+ 		if (strcmp(val, "1") == 0)
+ 		{
+ 			if (!secure_loaded_verify_locations())
+ 			{
+ 				ereport(LOG,
+ 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 						 errmsg("client certificates can only be checked if a root certificate store is available"),
+ 						 errhint("Make sure the root.crt file is present and readable."),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 				return false;
+ 			}
+ 			hbaline->clientcert = true;
+ 		}
+ 		else
+ 		{
+ 			if (hbaline->auth_method == uaCert)
+ 			{
+ 				ereport(LOG,
+ 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 						 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 				return false;
+ 			}
+ 			hbaline->clientcert = false;
+ 		}
+ 	}
+ 	else if (strcmp(name, "pamservice") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
+ 		hbaline->pamservice = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldaptls") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
+ 		if (strcmp(val, "1") == 0)
+ 			hbaline->ldaptls = true;
+ 		else
+ 			hbaline->ldaptls = false;
+ 	}
+ 	else if (strcmp(name, "ldapserver") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
+ 		hbaline->ldapserver = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapport") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
+ 		hbaline->ldapport = atoi(val);
+ 		if (hbaline->ldapport == 0)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("invalid LDAP port number: \"%s\"", val),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			return false;
+ 		}
+ 	}
+ 	else if (strcmp(name, "ldapbinddn") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
+ 		hbaline->ldapbinddn = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapbindpasswd") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
+ 		hbaline->ldapbindpasswd = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapsearchattribute") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
+ 		hbaline->ldapsearchattribute = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapbasedn") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
+ 		hbaline->ldapbasedn = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapprefix") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
+ 		hbaline->ldapprefix = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapsuffix") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
+ 		hbaline->ldapsuffix = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "krb_server_hostname") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaKrb5, "krb_server_hostname", "krb5");
+ 		hbaline->krb_server_hostname = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "krb_realm") == 0)
+ 	{
+ 		if (hbaline->auth_method != uaKrb5 &&
+ 			hbaline->auth_method != uaGSS &&
+ 			hbaline->auth_method != uaSSPI)
+ 			INVALID_AUTH_OPTION("krb_realm", gettext_noop("krb5, gssapi and sspi"));
+ 		hbaline->krb_realm = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "include_realm") == 0)
+ 	{
+ 		if (hbaline->auth_method != uaKrb5 &&
+ 			hbaline->auth_method != uaGSS &&
+ 			hbaline->auth_method != uaSSPI)
+ 			INVALID_AUTH_OPTION("include_realm", gettext_noop("krb5, gssapi and sspi"));
+ 		if (strcmp(val, "1") == 0)
+ 			hbaline->include_realm = true;
+ 		else
+ 			hbaline->include_realm = false;
+ 	}
+ 	else if (strcmp(name, "radiusserver") == 0)
+ 	{
+ 		struct addrinfo *gai_result;
+ 		struct addrinfo hints;
+ 		int ret;
+ 
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
+ 
+ 		MemSet(&hints, 0, sizeof(hints));
+ 		hints.ai_socktype = SOCK_DGRAM;
+ 		hints.ai_family = AF_UNSPEC;
+ 
+ 		ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
+ 		if (ret || !gai_result)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
+ 							val, gai_strerror(ret)),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			if (gai_result)
+ 				pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ 			return false;
+ 		}
+ 		pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ 		hbaline->radiusserver = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "radiusport") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
+ 		hbaline->radiusport = atoi(val);
+ 		if (hbaline->radiusport == 0)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("invalid RADIUS port number: \"%s\"", val),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			return false;
+ 		}
+ 	}
+ 	else if (strcmp(name, "radiussecret") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
+ 		hbaline->radiussecret = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "radiusidentifier") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
+ 		hbaline->radiusidentifier = pstrdup(val);
+ 	}
+ 	else
+ 	{
+ 		ereport(LOG,
+ 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 			errmsg("unrecognized authentication option name: \"%s\"",
+ 				   name),
+ 				 errcontext("line %d of configuration file \"%s\"",
+ 							line_num, HbaFileName)));
+ 		return false;
+ 	}
+ 	return true;
+ }
+ 
+ /*
+  *	Scan the pre-parsed hba file, looking for a match to the port's connection
+  *	request.
+  */
+ static void
  check_hba(hbaPort *port)
  {
  	Oid			roleid;
***************
*** 1589,1660 **** check_hba(hbaPort *port)
  
  		/* Check database and role */
  		if (!check_db(port->database_name, port->user_name, roleid,
! 					  hba->database))
  			continue;
  
! 		if (!check_role(port->user_name, roleid, hba->role))
  			continue;
  
  		/* Found a record that matched! */
  		port->hba = hba;
! 		return true;
  	}
  
  	/* If no matching entry was found, then implicitly reject. */
  	hba = palloc0(sizeof(HbaLine));
  	hba->auth_method = uaImplicitReject;
  	port->hba = hba;
- 	return true;
- 
- 	/*
- 	 * XXX: Return false only happens if we have a parsing error, which we can
- 	 * no longer have (parsing now in postmaster). Consider changing API.
- 	 */
- }
- 
- /*
-  * Free an HbaLine structure
-  */
- static void
- free_hba_record(HbaLine *record)
- {
- 	if (record->database)
- 		pfree(record->database);
- 	if (record->role)
- 		pfree(record->role);
- 	if (record->usermap)
- 		pfree(record->usermap);
- 	if (record->pamservice)
- 		pfree(record->pamservice);
- 	if (record->ldapserver)
- 		pfree(record->ldapserver);
- 	if (record->ldapprefix)
- 		pfree(record->ldapprefix);
- 	if (record->ldapsuffix)
- 		pfree(record->ldapsuffix);
- 	if (record->krb_server_hostname)
- 		pfree(record->krb_server_hostname);
- 	if (record->krb_realm)
- 		pfree(record->krb_realm);
- 	pfree(record);
- }
- 
- /*
-  * Free all records on the parsed HBA list
-  */
- static void
- clean_hba_list(List *lines)
- {
- 	ListCell   *line;
- 
- 	foreach(line, lines)
- 	{
- 		HbaLine    *parsed = (HbaLine *) lfirst(line);
- 
- 		if (parsed)
- 			free_hba_record(parsed);
- 	}
- 	list_free(lines);
  }
  
  /*
--- 1653,1673 ----
  
  		/* Check database and role */
  		if (!check_db(port->database_name, port->user_name, roleid,
! 					  hba->databases))
  			continue;
  
! 		if (!check_role(port->user_name, roleid, hba->roles))
  			continue;
  
  		/* Found a record that matched! */
  		port->hba = hba;
! 		return;
  	}
  
  	/* If no matching entry was found, then implicitly reject. */
  	hba = palloc0(sizeof(HbaLine));
  	hba->auth_method = uaImplicitReject;
  	port->hba = hba;
  }
  
  /*
***************
*** 1674,1679 **** load_hba(void)
--- 1687,1695 ----
  			   *line_num;
  	List	   *new_parsed_lines = NIL;
  	bool		ok = true;
+ 	MemoryContext	linecxt;
+ 	MemoryContext	oldcxt;
+ 	MemoryContext	hbacxt;
  
  	file = AllocateFile(HbaFileName, "r");
  	if (file == NULL)
***************
*** 1691,1710 **** load_hba(void)
  		return false;
  	}
  
! 	tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
  	FreeFile(file);
  
  	/* Now parse all the lines */
  	forboth(line, hba_lines, line_num, hba_line_nums)
  	{
  		HbaLine    *newline;
  
! 		newline = palloc0(sizeof(HbaLine));
! 
! 		if (!parse_hba_line(lfirst(line), lfirst_int(line_num), newline))
  		{
! 			/* Parse error in the file, so indicate there's a problem */
! 			free_hba_record(newline);
  			ok = false;
  
  			/*
--- 1707,1735 ----
  		return false;
  	}
  
! 	linecxt = tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
  	FreeFile(file);
  
  	/* Now parse all the lines */
+ 	hbacxt = AllocSetContextCreate(TopMemoryContext,
+ 								   "hba parser context",
+ 								   ALLOCSET_DEFAULT_MINSIZE,
+ 								   ALLOCSET_DEFAULT_MINSIZE,
+ 								   ALLOCSET_DEFAULT_MAXSIZE);
+ 	oldcxt = MemoryContextSwitchTo(hbacxt);
  	forboth(line, hba_lines, line_num, hba_line_nums)
  	{
  		HbaLine    *newline;
  
! 		if ((newline = parse_hba_line(lfirst(line), lfirst_int(line_num))) == NULL)
  		{
! 			/*
! 			 * Parse error in the file, so indicate there's a problem.  NB: a
! 			 * problem in a line will free the memory for all previous lines as
! 			 * well!
! 			 */
! 			MemoryContextReset(hbacxt);
! 			new_parsed_lines = NIL;
  			ok = false;
  
  			/*
***************
*** 1718,1735 **** load_hba(void)
  		new_parsed_lines = lappend(new_parsed_lines, newline);
  	}
  
! 	/* Free the temporary lists */
! 	free_lines(&hba_lines, &hba_line_nums);
  
  	if (!ok)
  	{
  		/* Parsing failed at one or more rows, so bail out */
! 		clean_hba_list(new_parsed_lines);
  		return false;
  	}
  
  	/* Loaded new file successfully, replace the one we use */
! 	clean_hba_list(parsed_hba_lines);
  	parsed_hba_lines = new_parsed_lines;
  
  	return true;
--- 1743,1763 ----
  		new_parsed_lines = lappend(new_parsed_lines, newline);
  	}
  
! 	/* Free tokenizer memory */
! 	MemoryContextDelete(linecxt);
! 	MemoryContextSwitchTo(oldcxt);
  
  	if (!ok)
  	{
  		/* Parsing failed at one or more rows, so bail out */
! 		MemoryContextDelete(hbacxt);
  		return false;
  	}
  
  	/* Loaded new file successfully, replace the one we use */
! 	if (parsed_hba_context != NULL)
! 		MemoryContextDelete(parsed_hba_context);
! 	parsed_hba_context = hbacxt;
  	parsed_hba_lines = new_parsed_lines;
  
  	return true;
***************
*** 1746,1753 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
  					const char *pg_role, const char *ident_user,
  					bool case_insensitive, bool *found_p, bool *error_p)
  {
! 	ListCell   *line_item;
! 	char	   *token;
  	char	   *file_map;
  	char	   *file_pgrole;
  	char	   *file_ident_user;
--- 1774,1782 ----
  					const char *pg_role, const char *ident_user,
  					bool case_insensitive, bool *found_p, bool *error_p)
  {
! 	ListCell   *field;
! 	List	   *tokens;
! 	HbaToken   *token;
  	char	   *file_map;
  	char	   *file_pgrole;
  	char	   *file_ident_user;
***************
*** 1756,1780 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
  	*error_p = false;
  
  	Assert(line != NIL);
! 	line_item = list_head(line);
  
  	/* Get the map token (must exist) */
! 	token = lfirst(line_item);
! 	file_map = token;
  
  	/* Get the ident user token */
! 	line_item = lnext(line_item);
! 	if (!line_item)
! 		goto ident_syntax;
! 	token = lfirst(line_item);
! 	file_ident_user = token;
  
  	/* Get the PG rolename token */
! 	line_item = lnext(line_item);
! 	if (!line_item)
! 		goto ident_syntax;
! 	token = lfirst(line_item);
! 	file_pgrole = token;
  
  	if (strcmp(file_map, usermap_name) != 0)
  		/* Line does not match the map name we're looking for, so just abort */
--- 1785,1813 ----
  	*error_p = false;
  
  	Assert(line != NIL);
! 	field = list_head(line);
  
  	/* Get the map token (must exist) */
! 	tokens = lfirst(field);
! 	IDENT_MULTI_VALUE(tokens);
! 	token = linitial(tokens);
! 	file_map = token->string;
  
  	/* Get the ident user token */
! 	field = lnext(field);
! 	IDENT_FIELD_ABSENT(field);
! 	tokens = lfirst(field);
! 	IDENT_MULTI_VALUE(tokens);
! 	token = linitial(tokens);
! 	file_ident_user = token->string;
  
  	/* Get the PG rolename token */
! 	field = lnext(field);
! 	IDENT_FIELD_ABSENT(field);
! 	tokens = lfirst(field);
! 	IDENT_MULTI_VALUE(tokens);
! 	token = linitial(tokens);
! 	file_pgrole = token->string;
  
  	if (strcmp(file_map, usermap_name) != 0)
  		/* Line does not match the map name we're looking for, so just abort */
***************
*** 1913,1927 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
  				*found_p = true;
  		}
  	}
- 
  	return;
- 
- ident_syntax:
- 	ereport(LOG,
- 			(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 			 errmsg("missing entry in file \"%s\" at end of line %d",
- 					IdentFileName, line_number)));
- 	*error_p = true;
  }
  
  
--- 1946,1952 ----
***************
*** 1989,2003 **** check_usermap(const char *usermap_name,
  
  
  /*
!  * Read the ident config file and create a List of Lists of tokens in the file.
   */
  void
  load_ident(void)
  {
  	FILE	   *file;
  
! 	if (ident_lines || ident_line_nums)
! 		free_lines(&ident_lines, &ident_line_nums);
  
  	file = AllocateFile(IdentFileName, "r");
  	if (file == NULL)
--- 2014,2034 ----
  
  
  /*
!  * Read the ident config file and populate ident_lines and ident_line_nums.
!  *
!  * Like parsed_hba_lines, ident_lines is a triple-nested List of lines, fields
!  * and tokens.
   */
  void
  load_ident(void)
  {
  	FILE	   *file;
  
! 	if (ident_context != NULL)
! 	{
! 		MemoryContextDelete(ident_context);
! 		ident_context = NULL;
! 	}
  
  	file = AllocateFile(IdentFileName, "r");
  	if (file == NULL)
***************
*** 2010,2016 **** load_ident(void)
  	}
  	else
  	{
! 		tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
  		FreeFile(file);
  	}
  }
--- 2041,2048 ----
  	}
  	else
  	{
! 		ident_context = tokenize_file(IdentFileName, file, &ident_lines,
! 									  &ident_line_nums);
  		FreeFile(file);
  	}
  }
***************
*** 2022,2036 **** load_ident(void)
   *	"database" from frontend "raddr", user "user".	Return the method and
   *	an optional argument (stored in fields of *port), and STATUS_OK.
   *
!  *	Note that STATUS_ERROR indicates a problem with the hba config file.
!  *	If the file is OK but does not contain any entry matching the request,
!  *	we return STATUS_OK and method = uaImplicitReject.
   */
! int
  hba_getauthmethod(hbaPort *port)
  {
! 	if (check_hba(port))
! 		return STATUS_OK;
! 	else
! 		return STATUS_ERROR;
  }
--- 2054,2064 ----
   *	"database" from frontend "raddr", user "user".	Return the method and
   *	an optional argument (stored in fields of *port), and STATUS_OK.
   *
!  *	If the file does not contain any entry matching the request, we return
!  *	method = uaImplicitReject.
   */
! void
  hba_getauthmethod(hbaPort *port)
  {
! 	check_hba(port);
  }
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 49,60 **** typedef enum ConnType
  	ctHostNoSSL
  } ConnType;
  
! typedef struct
  {
  	int			linenumber;
  	ConnType	conntype;
! 	char	   *database;
! 	char	   *role;
  	struct sockaddr_storage addr;
  	struct sockaddr_storage mask;
  	IPCompareMethod ip_cmp_method;
--- 49,60 ----
  	ctHostNoSSL
  } ConnType;
  
! typedef struct HbaLine
  {
  	int			linenumber;
  	ConnType	conntype;
! 	List	   *databases;
! 	List	   *roles;
  	struct sockaddr_storage addr;
  	struct sockaddr_storage mask;
  	IPCompareMethod ip_cmp_method;
***************
*** 87,93 **** typedef struct Port hbaPort;
  
  extern bool load_hba(void);
  extern void load_ident(void);
! extern int	hba_getauthmethod(hbaPort *port);
  extern int check_usermap(const char *usermap_name,
  			  const char *pg_role, const char *auth_user,
  			  bool case_sensitive);
--- 87,93 ----
  
  extern bool load_hba(void);
  extern void load_ident(void);
! 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);
#9Brendan Jurd
direvus@gmail.com
In reply to: Alvaro Herrera (#8)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 21 June 2011 06:06, Alvaro Herrera <alvherre@commandprompt.com> wrote:

Excerpts from Alvaro Herrera's message of lun jun 20 12:19:37 -0400 2011:

Excerpts from Pavel Stehule's message of lun jun 20 11:34:25 -0400 2011:

b) probably you can simplify a memory management using own two
persistent memory context - and you can swap it. Then functions like
free_hba_record, clean_hba_list, free_lines should be removed.

Yeah, I reworked the patch with something like that over the weekend.
Not all of it though.  I'll send an updated version shortly.

Here it is, please let me know what you think.  I took the liberty of
cleaning up some things that were clearly historical leftovers.

Okay, yeah, the MemoryContext approach is far more elegant than what I
had. Boy was I ever barking up the wrong tree.

Cheers,
BJ

#10Alvaro Herrera
alvherre@commandprompt.com
In reply to: Brendan Jurd (#9)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Brendan Jurd's message of lun jun 20 20:06:39 -0400 2011:

On 21 June 2011 06:06, Alvaro Herrera <alvherre@commandprompt.com> wrote:

Excerpts from Alvaro Herrera's message of lun jun 20 12:19:37 -0400 2011:

Excerpts from Pavel Stehule's message of lun jun 20 11:34:25 -0400 2011:

b) probably you can simplify a memory management using own two
persistent memory context - and you can swap it. Then functions like
free_hba_record, clean_hba_list, free_lines should be removed.

Yeah, I reworked the patch with something like that over the weekend.
Not all of it though.  I'll send an updated version shortly.

Here it is, please let me know what you think.  I took the liberty of
cleaning up some things that were clearly historical leftovers.

Okay, yeah, the MemoryContext approach is far more elegant than what I
had. Boy was I ever barking up the wrong tree.

Eh, whoever wrote the original code was barking up the same tree, so I
don't blame you for following the well-trodden path.

I realize I took out most of the fun of this patch from you, but -- are
you still planning to do some more exhaustive testing of it? I checked
some funny scenarios (including include files and groups) but it's not
all that unlikely that I missed some cases. I also didn't check any
auth method other than ident, md5 and trust, 'cause I don't have what's
required for anything else. (It's a pity that the regression tests
don't exercise anything else.)

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#11Brendan Jurd
direvus@gmail.com
In reply to: Alvaro Herrera (#10)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 21 June 2011 11:11, Alvaro Herrera <alvherre@commandprompt.com> wrote:

I realize I took out most of the fun of this patch from you, but -- are
you still planning to do some more exhaustive testing of it?  I checked
some funny scenarios (including include files and groups) but it's not
all that unlikely that I missed some cases.  I also didn't check any
auth method other than ident, md5 and trust, 'cause I don't have what's
required for anything else.  (It's a pity that the regression tests
don't exercise anything else.)

I've pretty much tested all the things I can think to test, and I'm in
the same boat as you re auth methods. If there are bugs, I think they
will be of the obscure kind, that only reveal themselves under
peculiar circumstances.

Cheers,
BJ

#12Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#8)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/20 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Alvaro Herrera's message of lun jun 20 12:19:37 -0400 2011:

Excerpts from Pavel Stehule's message of lun jun 20 11:34:25 -0400 2011:

b) probably you can simplify a memory management using own two
persistent memory context - and you can swap it. Then functions like
free_hba_record, clean_hba_list, free_lines should be removed.

Yeah, I reworked the patch with something like that over the weekend.
Not all of it though.  I'll send an updated version shortly.

Here it is, please let me know what you think.  I took the liberty of
cleaning up some things that were clearly historical leftovers.

I have one question. I can't find any rules for work with tokens, etc,
where is quotes allowed and disallowed?

I don't see any other issues.

Regards

Pavel Stehule

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#13Brendan Jurd
direvus@gmail.com
In reply to: Pavel Stehule (#12)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 21 June 2011 13:51, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I have one question. I can't find any rules for work with tokens, etc,
where is quotes allowed and disallowed?

I don't see any other issues.

I'm not sure I understand your question, but quotes are allowed
anywhere and they always act to remove any special meaning the token
might otherwise have had. It's much like quoting a column name in
SQL.

I didn't change any of the hba parsing rules, so escaping and whatnot
should all work the way it did before. The only difference should be
that after parsing, keywords will only be evaluated for fields where
they matter.

Cheers,
BJ

#14Pavel Stehule
pavel.stehule@gmail.com
In reply to: Brendan Jurd (#13)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/21 Brendan Jurd <direvus@gmail.com>:

On 21 June 2011 13:51, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I have one question. I can't find any rules for work with tokens, etc,
where is quotes allowed and disallowed?

I don't see any other issues.

I'm not sure I understand your question, but quotes are allowed
anywhere and they always act to remove any special meaning the token
might otherwise have had.  It's much like quoting a column name in
SQL.

I don't understand to using a macro

#define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)

because you disallowed a quoting?

Regards

Pavel

Show quoted text

I didn't change any of the hba parsing rules, so escaping and whatnot
should all work the way it did before.  The only difference should be
that after parsing, keywords will only be evaluated for fields where
they matter.

Cheers,
BJ

#15Brendan Jurd
direvus@gmail.com
In reply to: Pavel Stehule (#14)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 21 June 2011 14:34, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I don't understand to using a macro

#define token_is_keyword(t, k)  (!t->quoted && strcmp(t->string, k) == 0)

because you disallowed a quoting?

Well, a token can only be treated as a special keyword if it is unquoted.

As an example, in the 'database' field, the bare token 'replication'
is a keyword meaning the pseudo-database for streaming rep. Whereas
the quoted token "replication" would mean a real database which is
called 'replication'.

Likewise, the bare token 'all' in the username field is a keyword --
it matches any username. Whereas the quoted token "all" would only
match a user named 'all'.

Therefore, token_is_keyword only returns true where the token matches
the given string as is also unquoted.

Does that make sense?

Cheers,
BJ

#16Pavel Stehule
pavel.stehule@gmail.com
In reply to: Brendan Jurd (#15)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/21 Brendan Jurd <direvus@gmail.com>:

On 21 June 2011 14:34, Pavel Stehule <pavel.stehule@gmail.com> wrote:

I don't understand to using a macro

#define token_is_keyword(t, k)  (!t->quoted && strcmp(t->string, k) == 0)

because you disallowed a quoting?

Well, a token can only be treated as a special keyword if it is unquoted.

As an example, in the 'database' field, the bare token 'replication'
is a keyword meaning the pseudo-database for streaming rep.  Whereas
the quoted token "replication" would mean a real database which is
called 'replication'.

Likewise, the bare token 'all' in the username field is a keyword --
it matches any username.  Whereas the quoted token "all" would only
match a user named 'all'.

Therefore, token_is_keyword only returns true where the token matches
the given string as is also unquoted.

Does that make sense?

yes - it has a sense. Quoting changes sense from keyword to literal.
But then I see a significant inconsistency - every know keywords
should be only tokens.

else if (strcmp(token, "pamservice") == 0)
- {
- REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
- parsedline->pamservice = pstrdup(c);
- }

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

??

Regards

Pavel

Show quoted text

Cheers,
BJ

#17Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#16)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

yes - it has a sense. Quoting changes sense from keyword to literal.
But then I see a significant inconsistency - every know keywords
should be only tokens.

else if (strcmp(token, "pamservice") == 0)
- {
- REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
- parsedline->pamservice = pstrdup(c);
- }

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?). I opted for
leaving it alone, but maybe this needs to be fixed. (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#18Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#17)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/21 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

yes - it has a sense. Quoting changes sense from keyword to literal.
But then I see a significant inconsistency - every know keywords
should be only tokens.

        else if (strcmp(token, "pamservice") == 0)
-             {
-                 REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
-                 parsedline->pamservice = pstrdup(c);
-             }

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?).  I opted for
leaving it alone, but maybe this needs to be fixed.  (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

It's question about compatibility - sure. But a description inside
pg_hba.conf speaks cleanly - quoting means a lost of original
semantic.

And if we allow a quoting somewhere, then I can't imagine a
description - "somewhere quoting means a string literal, somewhere
have not impact?"

Regards

Pavel Stehule

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#19Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#18)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Pavel Stehule's message of mar jun 21 10:04:26 -0400 2011:

2011/6/21 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

yes - it has a sense. Quoting changes sense from keyword to literal.
But then I see a significant inconsistency - every know keywords
should be only tokens.

        else if (strcmp(token, "pamservice") == 0)
-             {
-                 REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
-                 parsedline->pamservice = pstrdup(c);
-             }

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?).  I opted for
leaving it alone, but maybe this needs to be fixed.  (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

I tested it and it works: This line

"local" @dbs +b "trust"

is accepted and it works in the unpatched code. I don't think we want
to break people's existing pg_hba.conf files for no reason. I doubt
that many people are using pg_hba.conf tokens with quotes, mind you, but
there might be some ...

In any case, if people here thinks we should tighten this, it's easy to
do on top of this patch by changing the strcmp() calls to
token_is_keyword, as you say. Let's not burden this patch with the
responsibility of doing so, because that's likely to get it punted.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#20Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#19)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/21 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 21 10:04:26 -0400 2011:

2011/6/21 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

yes - it has a sense. Quoting changes sense from keyword to literal.
But then I see a significant inconsistency - every know keywords
should be only tokens.

        else if (strcmp(token, "pamservice") == 0)
-             {
-                 REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
-                 parsedline->pamservice = pstrdup(c);
-             }

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?).  I opted for
leaving it alone, but maybe this needs to be fixed.  (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

I tested it and it works: This line

"local" @dbs +b "trust"

is accepted and it works in the unpatched code.  I don't think we want
to break people's existing pg_hba.conf files for no reason.  I doubt
that many people are using pg_hba.conf tokens with quotes, mind you, but
there might be some ...

In any case, if people here thinks we should tighten this, it's easy to
do on top of this patch by changing the strcmp() calls to
token_is_keyword, as you say.  Let's not burden this patch with the
responsibility of doing so, because that's likely to get it punted.

It is time to discuss about it.

I thinking so current behave is strange and should be fixed - it
doesn't respect a description stored in pg_hba.conf. I agree, so this
will have impact on compatibility, but pg_hba is config file so this
impact is not too hard. The cleaning now can carry a benefit in
future, when pg_hba can be more complex.

Regards

Pavel

#21Ross J. Reedstrom
reedstrm@rice.edu
In reply to: Alvaro Herrera (#19)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On Tue, Jun 21, 2011 at 10:15:50AM -0400, Alvaro Herrera wrote:

Excerpts from Pavel Stehule's message of mar jun 21 10:04:26 -0400 2011:

2011/6/21 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

yes - it has a sense. Quoting changes sense from keyword to literal.
But then I see a significant inconsistency - every know keywords
should be only tokens.

� � � � else if (strcmp(token, "pamservice") == 0)
- � � � � � � {
- � � � � � � � � REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
- � � � � � � � � parsedline->pamservice = pstrdup(c);
- � � � � � � }

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?). �I opted for
leaving it alone, but maybe this needs to be fixed. �(Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

I tested it and it works: This line

"local" @dbs +b "trust"

is accepted and it works in the unpatched code. I don't think we want
to break people's existing pg_hba.conf files for no reason. I doubt
that many people are using pg_hba.conf tokens with quotes, mind you, but
there might be some ...

In any case, if people here thinks we should tighten this, it's easy to
do on top of this patch by changing the strcmp() calls to
token_is_keyword, as you say. Let's not burden this patch with the
responsibility of doing so, because that's likely to get it punted.

Hmm, would it be possible to add some deprecation warnings for this case
without making the code too messy? Perhaps with a macro
"token_should_be_keyword". That's the usual path to tightening syntax.

Ross
--
Ross Reedstrom, Ph.D. reedstrm@rice.edu
Systems Engineer & Admin, Research Scientist phone: 713-348-6166
Connexions http://cnx.org fax: 713-348-3665
Rice University MS-375, Houston, TX 77005
GPG Key fingerprint = F023 82C8 9B0E 2CC6 0D8E F888 D3AE 810E 88F0 BEDE

#22Tom Lane
tgl@sss.pgh.pa.us
In reply to: Alvaro Herrera (#17)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Alvaro Herrera <alvherre@commandprompt.com> writes:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?). I opted for
leaving it alone, but maybe this needs to be fixed. (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

AFAICS, this is only important in places where the syntax allows either
a keyword or an identifier. If only a keyword is possible, there is no
value in rejecting it because it's quoted. And, when you do the test,
I think you'll find that it would be breaking hba files that used to
work (though admittedly, it's doubtful that there are any such in the
field).

regards, tom lane

#23Pavel Stehule
pavel.stehule@gmail.com
In reply to: Tom Lane (#22)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/21 Tom Lane <tgl@sss.pgh.pa.us>:

Alvaro Herrera <alvherre@commandprompt.com> writes:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?).  I opted for
leaving it alone, but maybe this needs to be fixed.  (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

AFAICS, this is only important in places where the syntax allows either
a keyword or an identifier.  If only a keyword is possible, there is no
value in rejecting it because it's quoted.  And, when you do the test,
I think you'll find that it would be breaking hba files that used to
work (though admittedly, it's doubtful that there are any such in the
field).

It should be better documented. I don't think so this is good
solution, but this is not too important.

regards

Pavel

Show quoted text

                       regards, tom lane

#24Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#23)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Pavel Stehule's message of mar jun 21 11:04:11 -0400 2011:

2011/6/21 Tom Lane <tgl@sss.pgh.pa.us>:

AFAICS, this is only important in places where the syntax allows either
a keyword or an identifier.  If only a keyword is possible, there is no
value in rejecting it because it's quoted.  And, when you do the test,
I think you'll find that it would be breaking hba files that used to
work (though admittedly, it's doubtful that there are any such in the
field).

It should be better documented. I don't think so this is good
solution, but this is not too important.

On the contrary -- we should support it but not document it. I mean,
what good would that do? If someone is so silly to uselessly quote
keywords, let them do it, but let's not encourage it.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#25Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#24)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/21 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 21 11:04:11 -0400 2011:

2011/6/21 Tom Lane <tgl@sss.pgh.pa.us>:

AFAICS, this is only important in places where the syntax allows either
a keyword or an identifier.  If only a keyword is possible, there is no
value in rejecting it because it's quoted.  And, when you do the test,
I think you'll find that it would be breaking hba files that used to
work (though admittedly, it's doubtful that there are any such in the
field).

It should be better documented. I don't think so this is good
solution, but this is not too important.

On the contrary -- we should support it but not document it.  I mean,
what good would that do?  If someone is so silly to uselessly quote
keywords, let them do it, but let's not encourage it.

it is argument too :)

It has not good solution - one break compatibility, second is strange
and undocumented :(

Actually I don't remember a issues about pg_hba.conf - probably 99%
users work with default configuration, so we can leave this file in
current state.

I am thinking so a notice in pg_hba.conf can be redesigned - almost
all people don't read it, but if someone read it, then he needs a
correct information - in sense, so on quotes works only where literal
or known literal can be entered.

Regards

Pavel Stehule

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#26Robert Haas
robertmhaas@gmail.com
In reply to: Alvaro Herrera (#24)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On Jun 21, 2011, at 12:41 PM, Alvaro Herrera <alvherre@commandprompt.com> wrote:

On the contrary -- we should support it but not document it.

+1.

...Robert

#27Brendan Jurd
direvus@gmail.com
In reply to: Tom Lane (#22)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 22 June 2011 00:47, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@commandprompt.com> writes:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?).  I opted for
leaving it alone, but maybe this needs to be fixed.  (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

AFAICS, this is only important in places where the syntax allows either
a keyword or an identifier.  If only a keyword is possible, there is no
value in rejecting it because it's quoted.  And, when you do the test,
I think you'll find that it would be breaking hba files that used to
work (though admittedly, it's doubtful that there are any such in the
field).

Yes, I agree, and this was my thinking when I came up against this
while writing the original patch. It doesn't help to treat "hostssl"
differently than hostssl, because quoted identifiers are meaningless
in that context.

Cheers,
BJ

#28Pavel Stehule
pavel.stehule@gmail.com
In reply to: Brendan Jurd (#27)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/22 Brendan Jurd <direvus@gmail.com>:

On 22 June 2011 00:47, Tom Lane <tgl@sss.pgh.pa.us> wrote:

Alvaro Herrera <alvherre@commandprompt.com> writes:

Excerpts from Pavel Stehule's message of mar jun 21 00:59:44 -0400 2011:

because >>pamservice<< - is known keyword, but 'pamservice' is some
literal without any mean. You should to use a makro token_is_keyword
more often.

Yeah, I wondered about this too (same with auth types, i.e. do we accept
quoted "hostssl" and so on or should that by rejected?).  I opted for
leaving it alone, but maybe this needs to be fixed.  (Now that I think
about it, what we should do first is verify whether it works with quotes
in the unpatched code).

AFAICS, this is only important in places where the syntax allows either
a keyword or an identifier.  If only a keyword is possible, there is no
value in rejecting it because it's quoted.  And, when you do the test,
I think you'll find that it would be breaking hba files that used to
work (though admittedly, it's doubtful that there are any such in the
field).

Yes, I agree, and this was my thinking when I came up against this
while writing the original patch.  It doesn't help to treat "hostssl"
differently than hostssl, because quoted identifiers are meaningless
in that context.

ook, now is clean, so this is majority opinion.

Please, can you send a final patch.

Regards

Pavel

Show quoted text

Cheers,
BJ

#29Brendan Jurd
direvus@gmail.com
In reply to: Pavel Stehule (#28)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 22 June 2011 14:01, Pavel Stehule <pavel.stehule@gmail.com> wrote:

ook, now is clean, so this is majority opinion.

Please, can you send a final patch.

I don't have any further changes to add to Alvaro's version 3, which
is already up on the CF app.

Cheers,
BJ

#30Alvaro Herrera
alvherre@commandprompt.com
In reply to: Brendan Jurd (#29)
1 attachment(s)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Brendan Jurd's message of mié jun 22 00:12:38 -0400 2011:

On 22 June 2011 14:01, Pavel Stehule <pavel.stehule@gmail.com> wrote:

ook, now is clean, so this is majority opinion.

Please, can you send a final patch.

I don't have any further changes to add to Alvaro's version 3, which
is already up on the CF app.

I have touched next_token() and next_token_expand() a bit more, because
it seemed to me that they could be simplified further (the bit about
returning the comma in the token, instead of being a boolean return,
seemed strange). Please let me know what you think.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

Attachments:

hba-keywords-v4.diffapplication/octet-stream; name=hba-keywords-v4.diffDownload
*** a/src/backend/libpq/auth.c
--- b/src/backend/libpq/auth.c
***************
*** 315,329 **** ClientAuthentication(Port *port)
  
  	/*
  	 * Get the authentication method to use for this frontend/database
! 	 * combination.  Note: a failure return indicates a problem with the hba
! 	 * config file, not with the request.  hba.c should have dropped an error
! 	 * message into the postmaster logfile if it failed.
  	 */
! 	if (hba_getauthmethod(port) != STATUS_OK)
! 		ereport(FATAL,
! 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 				 errmsg("missing or erroneous pg_hba.conf file"),
! 				 errhint("See server log for details.")));
  
  	/*
  	 * Enable immediate response to SIGTERM/SIGINT/timeout interrupts. (We
--- 315,325 ----
  
  	/*
  	 * Get the authentication method to use for this frontend/database
! 	 * combination.  Note: we do not parse the file at this point; this has
! 	 * already been done elsewhere.  hba.c dropped an error message
! 	 * into the server logfile if parsing the hba config file failed.
  	 */
! 	hba_getauthmethod(port);
  
  	/*
  	 * Enable immediate response to SIGTERM/SIGINT/timeout interrupts. (We
*** a/src/backend/libpq/hba.c
--- b/src/backend/libpq/hba.c
***************
*** 35,48 ****
  #include "utils/acl.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
  
  
  #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
  #define atoxid(x)  ((TransactionId) strtoul((x), NULL, 10))
  
- /* This is used to separate values in multi-valued column strings */
- #define MULTI_VALUE_SEP "\001"
- 
  #define MAX_TOKEN	256
  
  /* callback data for check_network_callback */
--- 35,46 ----
  #include "utils/acl.h"
  #include "utils/guc.h"
  #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
  
  
  #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
  #define atoxid(x)  ((TransactionId) strtoul((x), NULL, 10))
  
  #define MAX_TOKEN	256
  
  /* callback data for check_network_callback */
***************
*** 53,78 **** typedef struct check_network_data
  	bool		result;			/* set to true if match */
  } check_network_data;
  
! /* pre-parsed content of HBA config file: list of HbaLine structs */
  static List *parsed_hba_lines = NIL;
  
  /*
   * These variables hold the pre-parsed contents of the ident usermap
!  * configuration file.	ident_lines is a list of sublists, one sublist for
!  * each (non-empty, non-comment) line of the file.	The sublist items are
!  * palloc'd strings, one string per token on the line.  Note there will always
!  * be at least one token, since blank lines are not entered in the data
!  * structure.  ident_line_nums is an integer list containing the actual line
!  * number for each line represented in ident_lines.
   */
  static List *ident_lines = NIL;
  static List *ident_line_nums = NIL;
  
  
! static void tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums);
! static char *tokenize_inc_file(const char *outer_filename,
  				  const char *inc_filename);
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
--- 51,98 ----
  	bool		result;			/* set to true if match */
  } 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 the HBA config file, together with whether
!  * the token had been quoted.
!  */
! typedef struct HbaToken
! {
! 	char   *string;
! 	bool	quoted;
! } HbaToken;
! 
! /*
!  * pre-parsed content of HBA config file: list of HbaLine structs.
!  * parsed_hba_context is the memory context where it lives.
!  */
  static List *parsed_hba_lines = NIL;
+ static MemoryContext parsed_hba_context = NULL;
  
  /*
   * These variables hold the pre-parsed contents of the ident usermap
!  * configuration file.	ident_lines is a triple-nested list of lines, fields
!  * and tokens, as returned by tokenize_file.  There will be one line in
!  * ident_lines for each (non-empty, non-comment) line of the file.  Note there
!  * will always be at least one field, since blank lines are not entered in the
!  * data structure.  ident_line_nums is an integer list containing the actual
!  * line number for each line represented in ident_lines.  ident_context is
!  * the memory context holding all this.
   */
  static List *ident_lines = NIL;
  static List *ident_line_nums = NIL;
+ static MemoryContext ident_context = NULL;
  
  
! static MemoryContext tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums);
! static List *tokenize_inc_file(List *tokens, const char *outer_filename,
  				  const char *inc_filename);
+ static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
+ 				   int line_num);
  
  /*
   * isblank() exists in the ISO C99 spec, but it's not very portable yet,
***************
*** 96,101 **** pg_isblank(const char c)
--- 116,123 ----
   * the first character.  (We use that to prevent "@x" from being treated
   * as a file inclusion request.  Note that @"x" should be so treated;
   * we want to allow that to support embedded spaces in file paths.)
+  * We set *terminating_comma to indicate whether the token is terminated by a
+  * comma (which is not returned.)
   *
   * If successful: store null-terminated token at *buf and return TRUE.
   * If no more tokens on line: set *buf = '\0' and return FALSE.
***************
*** 104,116 **** pg_isblank(const char c)
   * whichever comes first. If no more tokens on line, position the file to the
   * beginning of the next line or EOF, whichever comes first.
   *
!  * Handle comments. Treat unquoted keywords that might be role, database, or
!  * host names specially, by appending a newline to them.  Also, when
!  * a token is terminated by a comma, the comma is included in the returned
!  * token.
   */
  static bool
! next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
  {
  	int			c;
  	char	   *start_buf = buf;
--- 126,136 ----
   * whichever comes first. If no more tokens on line, position the file to the
   * beginning of the next line or EOF, whichever comes first.
   *
!  * Handle comments.
   */
  static bool
! next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote,
! 		   bool *terminating_comma)
  {
  	int			c;
  	char	   *start_buf = buf;
***************
*** 123,128 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
--- 143,149 ----
  	Assert(end_buf > start_buf);
  
  	*initial_quote = false;
+ 	*terminating_comma = false;
  
  	/* Move over initial whitespace and commas */
  	while ((c = getc(fp)) != EOF && (pg_isblank(c) || c == ','))
***************
*** 165,176 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
  			break;
  		}
  
! 		if (c != '"' || was_quote)
! 			*buf++ = c;
! 
! 		/* We pass back the comma so the caller knows there is more */
  		if (c == ',' && !in_quote)
  			break;
  
  		/* Literal double-quote is two double-quotes */
  		if (in_quote && c == '"')
--- 186,200 ----
  			break;
  		}
  
! 		/* we do not pass back the comma in the token */
  		if (c == ',' && !in_quote)
+ 		{
+ 			*terminating_comma = true;
  			break;
+ 		}
+ 
+ 		if (c != '"' || was_quote)
+ 			*buf++ = c;
  
  		/* Literal double-quote is two double-quotes */
  		if (in_quote && c == '"')
***************
*** 198,335 **** next_token(FILE *fp, char *buf, int bufsz, bool *initial_quote)
  
  	*buf = '\0';
  
- 	if (!saw_quote &&
- 		(strcmp(start_buf, "all") == 0 ||
- 		 strcmp(start_buf, "samehost") == 0 ||
- 		 strcmp(start_buf, "samenet") == 0 ||
- 		 strcmp(start_buf, "sameuser") == 0 ||
- 		 strcmp(start_buf, "samegroup") == 0 ||
- 		 strcmp(start_buf, "samerole") == 0 ||
- 		 strcmp(start_buf, "replication") == 0))
- 	{
- 		/* append newline to a magical keyword */
- 		*buf++ = '\n';
- 		*buf = '\0';
- 	}
- 
  	return (saw_quote || buf > start_buf);
  }
  
  /*
!  *	 Tokenize file and handle file inclusion and comma lists. We have
!  *	 to  break	apart  the	commas	to	expand	any  file names then
!  *	 reconstruct with commas.
   *
!  * The result is a palloc'd string, or NULL if we have reached EOL.
   */
! static char *
! next_token_expand(const char *filename, FILE *file)
  {
  	char		buf[MAX_TOKEN];
- 	char	   *comma_str = pstrdup("");
- 	bool		got_something = false;
  	bool		trailing_comma;
  	bool		initial_quote;
! 	char	   *incbuf;
! 	int			needed;
  
  	do
  	{
! 		if (!next_token(file, buf, sizeof(buf), &initial_quote))
  			break;
  
- 		got_something = true;
- 
- 		if (strlen(buf) > 0 && buf[strlen(buf) - 1] == ',')
- 		{
- 			trailing_comma = true;
- 			buf[strlen(buf) - 1] = '\0';
- 		}
- 		else
- 			trailing_comma = false;
- 
  		/* Is this referencing a file? */
  		if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! 			incbuf = tokenize_inc_file(filename, buf + 1);
  		else
! 			incbuf = pstrdup(buf);
! 
! 		needed = strlen(comma_str) + strlen(incbuf) + 1;
! 		if (trailing_comma)
! 			needed++;
! 		comma_str = repalloc(comma_str, needed);
! 		strcat(comma_str, incbuf);
! 		if (trailing_comma)
! 			strcat(comma_str, MULTI_VALUE_SEP);
! 		pfree(incbuf);
  	} while (trailing_comma);
  
! 	if (!got_something)
! 	{
! 		pfree(comma_str);
! 		return NULL;
! 	}
! 
! 	return comma_str;
  }
  
  
- /*
-  * Free memory used by lines/tokens (i.e., structure built by tokenize_file)
-  */
- static void
- free_lines(List **lines, List **line_nums)
- {
- 	/*
- 	 * Either both must be non-NULL, or both must be NULL
- 	 */
- 	Assert((*lines != NIL && *line_nums != NIL) ||
- 		   (*lines == NIL && *line_nums == NIL));
  
! 	if (*lines)
! 	{
! 		/*
! 		 * "lines" is a list of lists; each of those sublists consists of
! 		 * palloc'ed tokens, so we want to free each pointed-to token in a
! 		 * sublist, followed by the sublist itself, and finally the whole
! 		 * list.
! 		 */
! 		ListCell   *line;
! 
! 		foreach(line, *lines)
! 		{
! 			List	   *ln = lfirst(line);
! 			ListCell   *token;
! 
! 			foreach(token, ln)
! 				pfree(lfirst(token));
! 			/* free the sublist structure itself */
! 			list_free(ln);
! 		}
! 		/* free the list structure itself */
! 		list_free(*lines);
! 		/* clear the static variable */
! 		*lines = NIL;
! 	}
! 
! 	if (*line_nums)
! 	{
! 		list_free(*line_nums);
! 		*line_nums = NIL;
! 	}
! }
! 
! 
! static char *
! tokenize_inc_file(const char *outer_filename,
  				  const char *inc_filename)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
  	List	   *inc_lines;
  	List	   *inc_line_nums;
! 	ListCell   *line;
! 	char	   *comma_str;
  
  	if (is_absolute_path(inc_filename))
  	{
--- 222,299 ----
  
  	*buf = '\0';
  
  	return (saw_quote || buf > start_buf);
  }
  
+ static HbaToken *
+ make_hba_token(char *token, bool quoted)
+ {
+ 	HbaToken   *hbatoken;
+ 	int			toklen;
+ 
+ 	toklen = strlen(token);
+ 	hbatoken = (HbaToken *) palloc(sizeof(HbaToken) + toklen + 1);
+ 	hbatoken->string = (char *) hbatoken + sizeof(HbaToken);
+ 	hbatoken->quoted = quoted;
+ 	memcpy(hbatoken->string, token, toklen + 1);
+ 
+ 	return hbatoken;
+ }
+ 
  /*
!  * Copy a HbaToken struct into freshly palloc'd memory.
!  */
! static HbaToken *
! copy_hba_token(HbaToken *in)
! {
! 	HbaToken *out = make_hba_token(in->string, in->quoted);
! 
! 	return out;
! }
! 
! 
! /*
!  * Tokenize one HBA field from a file, handling file inclusion and comma lists.
   *
!  * The result is a List of HbaToken structs for each individual token,
!  * or NIL if we reached EOL.
   */
! static List *
! next_field_expand(const char *filename, FILE *file)
  {
  	char		buf[MAX_TOKEN];
  	bool		trailing_comma;
  	bool		initial_quote;
! 	List	   *tokens = NIL;
  
  	do
  	{
! 		if (!next_token(file, buf, sizeof(buf), &initial_quote, &trailing_comma))
  			break;
  
  		/* Is this referencing a file? */
  		if (!initial_quote && buf[0] == '@' && buf[1] != '\0')
! 			tokens = tokenize_inc_file(tokens, filename, buf + 1);
  		else
! 			tokens = lappend(tokens, make_hba_token(buf, initial_quote));
  	} while (trailing_comma);
  
! 	return tokens;
  }
  
  
  
! static List *
! tokenize_inc_file(List *tokens,
! 				  const char *outer_filename,
  				  const char *inc_filename)
  {
  	char	   *inc_fullname;
  	FILE	   *inc_file;
  	List	   *inc_lines;
  	List	   *inc_line_nums;
! 	ListCell   *inc_line;
! 	MemoryContext linecxt;
  
  	if (is_absolute_path(inc_filename))
  	{
***************
*** 355,451 **** tokenize_inc_file(const char *outer_filename,
  				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
  						inc_filename, inc_fullname)));
  		pfree(inc_fullname);
! 
! 		/* return single space, it matches nothing */
! 		return pstrdup(" ");
  	}
  
  	/* There is possible recursion here if the file contains @ */
! 	tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);
  
  	FreeFile(inc_file);
  	pfree(inc_fullname);
  
! 	/* Create comma-separated string from List */
! 	comma_str = pstrdup("");
! 	foreach(line, inc_lines)
  	{
! 		List	   *token_list = (List *) lfirst(line);
! 		ListCell   *token;
  
! 		foreach(token, token_list)
  		{
! 			int			oldlen = strlen(comma_str);
! 			int			needed;
! 
! 			needed = oldlen + strlen(lfirst(token)) + 1;
! 			if (oldlen > 0)
! 				needed++;
! 			comma_str = repalloc(comma_str, needed);
! 			if (oldlen > 0)
! 				strcat(comma_str, MULTI_VALUE_SEP);
! 			strcat(comma_str, lfirst(token));
! 		}
! 	}
  
! 	free_lines(&inc_lines, &inc_line_nums);
  
! 	/* if file is empty, return single space rather than empty string */
! 	if (strlen(comma_str) == 0)
! 	{
! 		pfree(comma_str);
! 		return pstrdup(" ");
  	}
  
! 	return comma_str;
  }
  
  
  /*
!  * Tokenize the given file, storing the resulting data into two lists:
!  * a list of sublists, each sublist containing the tokens in a line of
!  * the file, and a list of line numbers.
   *
   * filename must be the absolute path to the target file.
   */
! static void
  tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums)
  {
  	List	   *current_line = NIL;
  	int			line_number = 1;
! 	char	   *buf;
  
  	*lines = *line_nums = NIL;
  
  	while (!feof(file) && !ferror(file))
  	{
! 		buf = next_token_expand(filename, file);
  
! 		/* add token to list, unless we are at EOL or comment start */
! 		if (buf)
  		{
  			if (current_line == NIL)
  			{
  				/* make a new line List, record its line number */
! 				current_line = lappend(current_line, buf);
  				*lines = lappend(*lines, current_line);
  				*line_nums = lappend_int(*line_nums, line_number);
  			}
  			else
  			{
! 				/* append token to current line's list */
! 				current_line = lappend(current_line, buf);
  			}
  		}
  		else
  		{
  			/* we are at real or logical EOL, so force a new line List */
  			current_line = NIL;
- 			/* Advance line number whenever we reach EOL */
  			line_number++;
  		}
  	}
  }
  
  
--- 319,419 ----
  				 errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
  						inc_filename, inc_fullname)));
  		pfree(inc_fullname);
! 		return tokens;
  	}
  
  	/* There is possible recursion here if the file contains @ */
! 	linecxt = tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);
  
  	FreeFile(inc_file);
  	pfree(inc_fullname);
  
! 	foreach(inc_line, inc_lines)
  	{
! 		List	   *inc_fields = lfirst(inc_line);
! 		ListCell   *inc_field;
  
! 		foreach(inc_field, inc_fields)
  		{
! 			List	   *inc_tokens = lfirst(inc_field);
! 			ListCell   *inc_token;
  
! 			foreach(inc_token, inc_tokens)
! 			{
! 				HbaToken   *token = lfirst(inc_token);
  
! 				tokens = lappend(tokens, copy_hba_token(token));
! 			}
! 		}
  	}
  
! 	MemoryContextDelete(linecxt);
! 	return tokens;
  }
  
  
  /*
!  * Tokenize the given file, storing the resulting data into two Lists: a
!  * List of lines, and a List of line numbers.
!  *
!  * The list of lines is a triple-nested List structure.  Each line is a List of
!  * fields, and each field is a List of HbaTokens.
   *
   * filename must be the absolute path to the target file.
+  *
+  * Return value is a memory context which contains all memory allocated by
+  * this function.
   */
! static MemoryContext
  tokenize_file(const char *filename, FILE *file,
  			  List **lines, List **line_nums)
  {
  	List	   *current_line = NIL;
+ 	List	   *current_field = NIL;
  	int			line_number = 1;
! 	MemoryContext	linecxt;
! 	MemoryContext	oldcxt;
! 
! 	linecxt = AllocSetContextCreate(TopMemoryContext,
! 									"tokenize file cxt",
! 									ALLOCSET_DEFAULT_MINSIZE,
! 									ALLOCSET_DEFAULT_INITSIZE,
! 									ALLOCSET_DEFAULT_MAXSIZE);
! 	oldcxt = MemoryContextSwitchTo(linecxt);
  
  	*lines = *line_nums = NIL;
  
  	while (!feof(file) && !ferror(file))
  	{
! 		current_field = next_field_expand(filename, file);
  
! 		/* add tokens to list, unless we are at EOL or comment start */
! 		if (list_length(current_field) > 0)
  		{
  			if (current_line == NIL)
  			{
  				/* make a new line List, record its line number */
! 				current_line = lappend(current_line, current_field);
  				*lines = lappend(*lines, current_line);
  				*line_nums = lappend_int(*line_nums, line_number);
  			}
  			else
  			{
! 				/* append tokens to current line's list */
! 				current_line = lappend(current_line, current_field);
  			}
  		}
  		else
  		{
  			/* we are at real or logical EOL, so force a new line List */
  			current_line = NIL;
  			line_number++;
  		}
  	}
+ 
+ 	MemoryContextSwitchTo(oldcxt);
+ 
+ 	return linecxt;
  }
  
  
***************
*** 473,546 **** is_member(Oid userid, const char *role)
  }
  
  /*
!  * Check comma-separated list for a match to role, allowing group names.
!  *
!  * NB: param_str is destructively modified!  In current usage, this is
!  * okay only because this code is run after forking off from the postmaster,
!  * and so it doesn't matter that we clobber the stored hba info.
   */
  static bool
! check_role(const char *role, Oid roleid, char *param_str)
  {
! 	char	   *tok;
  
! 	for (tok = strtok(param_str, MULTI_VALUE_SEP);
! 		 tok != NULL;
! 		 tok = strtok(NULL, MULTI_VALUE_SEP))
  	{
! 		if (tok[0] == '+')
  		{
! 			if (is_member(roleid, tok + 1))
  				return true;
  		}
! 		else if (strcmp(tok, role) == 0 ||
! 				 (strcmp(tok, "replication\n") == 0 &&
! 				  strcmp(role, "replication") == 0) ||
! 				 strcmp(tok, "all\n") == 0)
  			return true;
  	}
- 
  	return false;
  }
  
  /*
!  * Check to see if db/role combination matches param string.
!  *
!  * NB: param_str is destructively modified!  In current usage, this is
!  * okay only because this code is run after forking off from the postmaster,
!  * and so it doesn't matter that we clobber the stored hba info.
   */
  static bool
! check_db(const char *dbname, const char *role, Oid roleid, char *param_str)
  {
! 	char	   *tok;
  
! 	for (tok = strtok(param_str, MULTI_VALUE_SEP);
! 		 tok != NULL;
! 		 tok = strtok(NULL, MULTI_VALUE_SEP))
  	{
  		if (am_walsender)
  		{
  			/* walsender connections can only match replication keyword */
! 			if (strcmp(tok, "replication\n") == 0)
  				return true;
  		}
! 		else if (strcmp(tok, "all\n") == 0)
  			return true;
! 		else if (strcmp(tok, "sameuser\n") == 0)
  		{
  			if (strcmp(dbname, role) == 0)
  				return true;
  		}
! 		else if (strcmp(tok, "samegroup\n") == 0 ||
! 				 strcmp(tok, "samerole\n") == 0)
  		{
  			if (is_member(roleid, dbname))
  				return true;
  		}
! 		else if (strcmp(tok, "replication\n") == 0)
  			continue;			/* never match this if not walsender */
! 		else if (strcmp(tok, dbname) == 0)
  			return true;
  	}
  	return false;
--- 441,503 ----
  }
  
  /*
!  * Check HbaToken list for a match to role, allowing group names.
   */
  static bool
! check_role(const char *role, Oid roleid, List *tokens)
  {
! 	ListCell	   *cell;
! 	HbaToken	   *tok;
  
! 	foreach(cell, tokens)
  	{
! 		tok = lfirst(cell);
! 		if (!tok->quoted && tok->string[0] == '+')
  		{
! 			if (is_member(roleid, tok->string + 1))
  				return true;
  		}
! 		else if (token_matches(tok, role) ||
! 				 token_is_keyword(tok, "all"))
  			return true;
  	}
  	return false;
  }
  
  /*
!  * Check to see if db/role combination matches HbaToken list.
   */
  static bool
! check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
  {
! 	ListCell	   *cell;
! 	HbaToken	   *tok;
  
! 	foreach(cell, tokens)
  	{
+ 		tok = lfirst(cell);
  		if (am_walsender)
  		{
  			/* walsender connections can only match replication keyword */
! 			if (token_is_keyword(tok, "replication"))
  				return true;
  		}
! 		else if (token_is_keyword(tok, "all"))
  			return true;
! 		else if (token_is_keyword(tok, "sameuser"))
  		{
  			if (strcmp(dbname, role) == 0)
  				return true;
  		}
! 		else if (token_is_keyword(tok, "samegroup") ||
! 				 token_is_keyword(tok, "samerole"))
  		{
  			if (is_member(roleid, dbname))
  				return true;
  		}
! 		else if (token_is_keyword(tok, "replication"))
  			continue;			/* never match this if not walsender */
! 		else if (token_matches(tok, dbname))
  			return true;
  	}
  	return false;
***************
*** 784,790 **** check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
  } while (0);
  
  #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
! 	if (parsedline->auth_method != methodval) \
  		INVALID_AUTH_OPTION(optname, validmethods); \
  } while (0);
  
--- 741,747 ----
  } while (0);
  
  #define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
! 	if (hbaline->auth_method != methodval) \
  		INVALID_AUTH_OPTION(optname, validmethods); \
  } while (0);
  
***************
*** 800,828 **** check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
  	} \
  } while (0);
  
  
  /*
!  * Parse one line in the hba config file and store the result in
!  * a HbaLine structure.
   */
! static bool
! parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  {
! 	char	   *token;
  	struct addrinfo *gai_result;
  	struct addrinfo hints;
  	int			ret;
  	char	   *cidr_slash;
  	char	   *unsupauth;
! 	ListCell   *line_item;
! 
! 	line_item = list_head(line);
  
  	parsedline->linenumber = line_num;
  
  	/* Check the record type. */
! 	token = lfirst(line_item);
! 	if (strcmp(token, "local") == 0)
  	{
  #ifdef HAVE_UNIX_SOCKETS
  		parsedline->conntype = ctLocal;
--- 757,839 ----
  	} \
  } while (0);
  
+ /*
+  * IDENT_FIELD_ABSENT:
+  * Throw an error and exit the function if the given ident field ListCell is
+  * not populated.
+  *
+  * IDENT_MULTI_VALUE:
+  * Throw an error and exit the function if the given ident token List has more
+  * than one element.
+  */
+ #define IDENT_FIELD_ABSENT(field) do {\
+ 	if (!field) { \
+ 		ereport(LOG, \
+ 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ 				 errmsg("missing entry in file \"%s\" at end of line %d", \
+ 						IdentFileName, line_number))); \
+ 		*error_p = true; \
+ 		return; \
+ 	} \
+ } while (0);
+ 
+ #define IDENT_MULTI_VALUE(tokens) do {\
+ 	if (tokens->length > 1) { \
+ 		ereport(LOG, \
+ 				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ 				 errmsg("multiple values in ident field"), \
+ 				 errcontext("line %d of configuration file \"%s\"", \
+ 						line_number, IdentFileName))); \
+ 		*error_p = true; \
+ 		return; \
+ 	} \
+ } while (0);
+ 
  
  /*
!  * Parse one tokenised line from the hba config file and store the result in a
!  * HbaLine structure, or NULL if parsing fails.
!  *
!  * The tokenised line is a List of fields, each field being a List of
!  * HbaTokens.
!  *
!  * Note: this function leaks memory when an error occurs.  Caller is expected
!  * to have set a memory context that will be reset if this function returns
!  * NULL.
   */
! static HbaLine *
! parse_hba_line(List *line, int line_num)
  {
! 	char	   *str;
  	struct addrinfo *gai_result;
  	struct addrinfo hints;
  	int			ret;
  	char	   *cidr_slash;
  	char	   *unsupauth;
! 	ListCell   *field;
! 	List	   *tokens;
! 	ListCell   *tokencell;
! 	HbaToken   *token;
! 	HbaLine	   *parsedline;
  
+ 	parsedline = palloc0(sizeof(HbaLine));
  	parsedline->linenumber = line_num;
  
  	/* Check the record type. */
! 	field = list_head(line);
! 	tokens = lfirst(field);
! 	if (tokens->length > 1)
! 	{
! 		ereport(LOG,
! 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 				 errmsg("multiple values specified for connection type"),
! 				 errhint("Specify exactly one connection type per line."),
! 				 errcontext("line %d of configuration file \"%s\"",
! 							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	token = linitial(tokens);
! 	if (strcmp(token->string, "local") == 0)
  	{
  #ifdef HAVE_UNIX_SOCKETS
  		parsedline->conntype = ctLocal;
***************
*** 832,846 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  				 errmsg("local connections are not supported by this build"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  #endif
  	}
! 	else if (strcmp(token, "host") == 0
! 			 || strcmp(token, "hostssl") == 0
! 			 || strcmp(token, "hostnossl") == 0)
  	{
  
! 		if (token[4] == 's')	/* "hostssl" */
  		{
  			/* SSL support must be actually active, else complain */
  #ifdef USE_SSL
--- 843,857 ----
  				 errmsg("local connections are not supported by this build"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  #endif
  	}
! 	else if (strcmp(token->string, "host") == 0 ||
! 			 strcmp(token->string, "hostssl") == 0 ||
! 			 strcmp(token->string, "hostnossl") == 0)
  	{
  
! 		if (token->string[4] == 's')	/* "hostssl" */
  		{
  			/* SSL support must be actually active, else complain */
  #ifdef USE_SSL
***************
*** 854,860 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  						 errhint("Set ssl = on in postgresql.conf."),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return false;
  			}
  #else
  			ereport(LOG,
--- 865,871 ----
  						 errhint("Set ssl = on in postgresql.conf."),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return NULL;
  			}
  #else
  			ereport(LOG,
***************
*** 863,873 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			  errhint("Compile with --with-openssl to use SSL connections."),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  #endif
  		}
  #ifdef USE_SSL
! 		else if (token[4] == 'n')		/* "hostnossl" */
  		{
  			parsedline->conntype = ctHostNoSSL;
  		}
--- 874,884 ----
  			  errhint("Compile with --with-openssl to use SSL connections."),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
  #endif
  		}
  #ifdef USE_SSL
! 		else if (token->string[4] == 'n')		/* "hostnossl" */
  		{
  			parsedline->conntype = ctHostNoSSL;
  		}
***************
*** 883,945 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid connection type \"%s\"",
! 						token),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
! 	/* Get the database. */
! 	line_item = lnext(line_item);
! 	if (!line_item)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before database specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
- 	parsedline->database = pstrdup(lfirst(line_item));
  
! 	/* Get the role. */
! 	line_item = lnext(line_item);
! 	if (!line_item)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before role specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
- 	parsedline->role = pstrdup(lfirst(line_item));
  
  	if (parsedline->conntype != ctLocal)
  	{
  		/* Read the IP address field. (with or without CIDR netmask) */
! 		line_item = lnext(line_item);
! 		if (!line_item)
  		{
  			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("end-of-line before IP address specification"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  		}
! 		token = lfirst(line_item);
  
! 		if (strcmp(token, "all\n") == 0)
  		{
  			parsedline->ip_cmp_method = ipCmpAll;
  		}
! 		else if (strcmp(token, "samehost\n") == 0)
  		{
  			/* Any IP on this host is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameHost;
  		}
! 		else if (strcmp(token, "samenet\n") == 0)
  		{
  			/* Any IP on the host's subnets is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameNet;
--- 894,979 ----
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid connection type \"%s\"",
! 						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
! 	/* Get the databases. */
! 	field = lnext(field);
! 	if (!field)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before database specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	parsedline->databases = NIL;
! 	tokens = lfirst(field);
! 	foreach(tokencell, tokens)
! 	{
! 		parsedline->databases = lappend(parsedline->databases,
! 										copy_hba_token(lfirst(tokencell)));
  	}
  
! 	/* Get the roles. */
! 	field = lnext(field);
! 	if (!field)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before role specification"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	parsedline->roles = NIL;
! 	tokens = lfirst(field);
! 	foreach(tokencell, tokens)
! 	{
! 		parsedline->roles = lappend(parsedline->roles,
! 									copy_hba_token(lfirst(tokencell)));
  	}
  
  	if (parsedline->conntype != ctLocal)
  	{
  		/* Read the IP address field. (with or without CIDR netmask) */
! 		field = lnext(field);
! 		if (!field)
  		{
  			ereport(LOG,
  					(errcode(ERRCODE_CONFIG_FILE_ERROR),
  					 errmsg("end-of-line before IP address specification"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
! 		}
! 		tokens = lfirst(field);
! 		if (tokens->length > 1)
! 		{
! 			ereport(LOG,
! 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 					 errmsg("multiple values specified for host address"),
! 					 errhint("Specify one address range per line."),
! 					 errcontext("line %d of configuration file \"%s\"",
! 								line_num, HbaFileName)));
! 			return NULL;
  		}
! 		token = linitial(tokens);
  
! 		if (token_is_keyword(token, "all"))
  		{
  			parsedline->ip_cmp_method = ipCmpAll;
  		}
! 		else if (token_is_keyword(token, "samehost"))
  		{
  			/* Any IP on this host is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameHost;
  		}
! 		else if (token_is_keyword(token, "samenet"))
  		{
  			/* Any IP on the host's subnets is allowed to connect */
  			parsedline->ip_cmp_method = ipCmpSameNet;
***************
*** 950,959 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			parsedline->ip_cmp_method = ipCmpMask;
  
  			/* need a modifiable copy of token */
! 			token = pstrdup(token);
  
  			/* Check if it has a CIDR suffix and if so isolate it */
! 			cidr_slash = strchr(token, '/');
  			if (cidr_slash)
  				*cidr_slash = '\0';
  
--- 984,993 ----
  			parsedline->ip_cmp_method = ipCmpMask;
  
  			/* need a modifiable copy of token */
! 			str = pstrdup(token->string);
  
  			/* Check if it has a CIDR suffix and if so isolate it */
! 			cidr_slash = strchr(str, '/');
  			if (cidr_slash)
  				*cidr_slash = '\0';
  
***************
*** 967,990 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			hints.ai_addr = NULL;
  			hints.ai_next = NULL;
  
! 			ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
  			if (ret == 0 && gai_result)
  				memcpy(&parsedline->addr, gai_result->ai_addr,
  					   gai_result->ai_addrlen);
  			else if (ret == EAI_NONAME)
! 				parsedline->hostname = token;
  			else
  			{
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						 errmsg("invalid IP address \"%s\": %s",
! 								token, gai_strerror(ret)),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
  				if (gai_result)
  					pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 				pfree(token);
! 				return false;
  			}
  
  			pg_freeaddrinfo_all(hints.ai_family, gai_result);
--- 1001,1023 ----
  			hints.ai_addr = NULL;
  			hints.ai_next = NULL;
  
! 			ret = pg_getaddrinfo_all(str, NULL, &hints, &gai_result);
  			if (ret == 0 && gai_result)
  				memcpy(&parsedline->addr, gai_result->ai_addr,
  					   gai_result->ai_addrlen);
  			else if (ret == EAI_NONAME)
! 				parsedline->hostname = str;
  			else
  			{
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						 errmsg("invalid IP address \"%s\": %s",
! 								str, gai_strerror(ret)),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
  				if (gai_result)
  					pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 				return NULL;
  			}
  
  			pg_freeaddrinfo_all(hints.ai_family, gai_result);
***************
*** 994,1053 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			{
  				if (parsedline->hostname)
  				{
- 					*cidr_slash = '/';	/* restore token for message */
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
! 									token),
! 						   errcontext("line %d of configuration file \"%s\"",
! 									  line_num, HbaFileName)));
! 					pfree(token);
! 					return false;
  				}
  
  				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
  										  parsedline->addr.ss_family) < 0)
  				{
- 					*cidr_slash = '/';	/* restore token for message */
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid CIDR mask in address \"%s\"",
! 									token),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					pfree(token);
! 					return false;
  				}
! 				pfree(token);
  			}
  			else if (!parsedline->hostname)
  			{
  				/* Read the mask field. */
! 				pfree(token);
! 				line_item = lnext(line_item);
! 				if (!line_item)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						  errmsg("end-of-line before netmask specification"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return false;
  				}
! 				token = lfirst(line_item);
  
! 				ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
  				if (ret || !gai_result)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid IP mask \"%s\": %s",
! 									token, gai_strerror(ret)),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					if (gai_result)
  						pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 					return false;
  				}
  
  				memcpy(&parsedline->mask, gai_result->ai_addr,
--- 1027,1094 ----
  			{
  				if (parsedline->hostname)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("specifying both host name and CIDR mask is invalid: \"%s\"",
! 									token->string),
! 							 errcontext("line %d of configuration file \"%s\"",
! 										line_num, HbaFileName)));
! 					return NULL;
  				}
  
  				if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
  										  parsedline->addr.ss_family) < 0)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid CIDR mask in address \"%s\"",
! 									token->string),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return NULL;
  				}
! 				pfree(str);
  			}
  			else if (!parsedline->hostname)
  			{
  				/* Read the mask field. */
! 				pfree(str);
! 				field = lnext(field);
! 				if (!field)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  						  errmsg("end-of-line before netmask specification"),
+ 							 errhint("Specify an address range in CIDR notation, or provide a separate netmask."),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return NULL;
  				}
! 				tokens = lfirst(field);
! 				if (tokens->length > 1)
! 				{
! 					ereport(LOG,
! 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 						     errmsg("multiple values specified for netmask"),
! 							 errcontext("line %d of configuration file \"%s\"",
! 										line_num, HbaFileName)));
! 					return NULL;
! 				}
! 				token = linitial(tokens);
  
! 				ret = pg_getaddrinfo_all(token->string, NULL,
! 										 &hints, &gai_result);
  				if (ret || !gai_result)
  				{
  					ereport(LOG,
  							(errcode(ERRCODE_CONFIG_FILE_ERROR),
  							 errmsg("invalid IP mask \"%s\": %s",
! 									token->string, gai_strerror(ret)),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
  					if (gai_result)
  						pg_freeaddrinfo_all(hints.ai_family, gai_result);
! 					return NULL;
  				}
  
  				memcpy(&parsedline->mask, gai_result->ai_addr,
***************
*** 1061,1115 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  							 errmsg("IP address and mask do not match"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return false;
  				}
  			}
  		}
  	}							/* != ctLocal */
  
  	/* Get the authentication method */
! 	line_item = lnext(line_item);
! 	if (!line_item)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before authentication method"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
! 	token = lfirst(line_item);
  
  	unsupauth = NULL;
! 	if (strcmp(token, "trust") == 0)
  		parsedline->auth_method = uaTrust;
! 	else if (strcmp(token, "ident") == 0)
  		parsedline->auth_method = uaIdent;
! 	else if (strcmp(token, "peer") == 0)
  		parsedline->auth_method = uaPeer;
! 	else if (strcmp(token, "password") == 0)
  		parsedline->auth_method = uaPassword;
! 	else if (strcmp(token, "krb5") == 0)
  #ifdef KRB5
  		parsedline->auth_method = uaKrb5;
  #else
  		unsupauth = "krb5";
  #endif
! 	else if (strcmp(token, "gss") == 0)
  #ifdef ENABLE_GSS
  		parsedline->auth_method = uaGSS;
  #else
  		unsupauth = "gss";
  #endif
! 	else if (strcmp(token, "sspi") == 0)
  #ifdef ENABLE_SSPI
  		parsedline->auth_method = uaSSPI;
  #else
  		unsupauth = "sspi";
  #endif
! 	else if (strcmp(token, "reject") == 0)
  		parsedline->auth_method = uaReject;
! 	else if (strcmp(token, "md5") == 0)
  	{
  		if (Db_user_namespace)
  		{
--- 1102,1167 ----
  							 errmsg("IP address and mask do not match"),
  						   errcontext("line %d of configuration file \"%s\"",
  									  line_num, HbaFileName)));
! 					return NULL;
  				}
  			}
  		}
  	}							/* != ctLocal */
  
  	/* Get the authentication method */
! 	field = lnext(field);
! 	if (!field)
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("end-of-line before authentication method"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
! 	tokens = lfirst(field);
! 	if (tokens->length > 1)
! 	{
! 		ereport(LOG,
! 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 				 errmsg("multiple values specified for authentication type"),
! 				 errhint("Specify exactly one authentication type per line."),
! 				 errcontext("line %d of configuration file \"%s\"",
! 							line_num, HbaFileName)));
! 		return NULL;
! 	}
! 	token = linitial(tokens);
  
  	unsupauth = NULL;
! 	if (strcmp(token->string, "trust") == 0)
  		parsedline->auth_method = uaTrust;
! 	else if (strcmp(token->string, "ident") == 0)
  		parsedline->auth_method = uaIdent;
! 	else if (strcmp(token->string, "peer") == 0)
  		parsedline->auth_method = uaPeer;
! 	else if (strcmp(token->string, "password") == 0)
  		parsedline->auth_method = uaPassword;
! 	else if (strcmp(token->string, "krb5") == 0)
  #ifdef KRB5
  		parsedline->auth_method = uaKrb5;
  #else
  		unsupauth = "krb5";
  #endif
! 	else if (strcmp(token->string, "gss") == 0)
  #ifdef ENABLE_GSS
  		parsedline->auth_method = uaGSS;
  #else
  		unsupauth = "gss";
  #endif
! 	else if (strcmp(token->string, "sspi") == 0)
  #ifdef ENABLE_SSPI
  		parsedline->auth_method = uaSSPI;
  #else
  		unsupauth = "sspi";
  #endif
! 	else if (strcmp(token->string, "reject") == 0)
  		parsedline->auth_method = uaReject;
! 	else if (strcmp(token->string, "md5") == 0)
  	{
  		if (Db_user_namespace)
  		{
***************
*** 1118,1156 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  		}
  		parsedline->auth_method = uaMD5;
  	}
! 	else if (strcmp(token, "pam") == 0)
  #ifdef USE_PAM
  		parsedline->auth_method = uaPAM;
  #else
  		unsupauth = "pam";
  #endif
! 	else if (strcmp(token, "ldap") == 0)
  #ifdef USE_LDAP
  		parsedline->auth_method = uaLDAP;
  #else
  		unsupauth = "ldap";
  #endif
! 	else if (strcmp(token, "cert") == 0)
  #ifdef USE_SSL
  		parsedline->auth_method = uaCert;
  #else
  		unsupauth = "cert";
  #endif
! 	else if (strcmp(token, "radius") == 0)
  		parsedline->auth_method = uaRADIUS;
  	else
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\"",
! 						token),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	if (unsupauth)
--- 1170,1208 ----
  					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
  		}
  		parsedline->auth_method = uaMD5;
  	}
! 	else if (strcmp(token->string, "pam") == 0)
  #ifdef USE_PAM
  		parsedline->auth_method = uaPAM;
  #else
  		unsupauth = "pam";
  #endif
! 	else if (strcmp(token->string, "ldap") == 0)
  #ifdef USE_LDAP
  		parsedline->auth_method = uaLDAP;
  #else
  		unsupauth = "ldap";
  #endif
! 	else if (strcmp(token->string, "cert") == 0)
  #ifdef USE_SSL
  		parsedline->auth_method = uaCert;
  #else
  		unsupauth = "cert";
  #endif
! 	else if (strcmp(token->string, "radius") == 0)
  		parsedline->auth_method = uaRADIUS;
  	else
  	{
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\"",
! 						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	if (unsupauth)
***************
*** 1158,1167 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\": not supported by this build",
! 						token),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	/*
--- 1210,1219 ----
  		ereport(LOG,
  				(errcode(ERRCODE_CONFIG_FILE_ERROR),
  				 errmsg("invalid authentication method \"%s\": not supported by this build",
! 						token->string),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	/*
***************
*** 1181,1187 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			 errmsg("krb5 authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	if (parsedline->conntype == ctLocal &&
--- 1233,1239 ----
  			 errmsg("krb5 authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	if (parsedline->conntype == ctLocal &&
***************
*** 1192,1198 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		   errmsg("gssapi authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	if (parsedline->conntype != ctLocal &&
--- 1244,1250 ----
  		   errmsg("gssapi authentication is not supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	if (parsedline->conntype != ctLocal &&
***************
*** 1203,1217 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  			errmsg("peer authentication is only supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	/*
  	 * SSPI authentication can never be enabled on ctLocal connections,
  	 * because it's only supported on Windows, where ctLocal isn't supported.
  	 */
- 
- 
  	if (parsedline->conntype != ctHostSSL &&
  		parsedline->auth_method == uaCert)
  	{
--- 1255,1267 ----
  			errmsg("peer authentication is only supported on local sockets"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	/*
  	 * SSPI authentication can never be enabled on ctLocal connections,
  	 * because it's only supported on Windows, where ctLocal isn't supported.
  	 */
  	if (parsedline->conntype != ctHostSSL &&
  		parsedline->auth_method == uaCert)
  	{
***************
*** 1220,1450 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  				 errmsg("cert authentication is only supported on hostssl connections"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return false;
  	}
  
  	/* Parse remaining arguments */
! 	while ((line_item = lnext(line_item)) != NULL)
  	{
! 		char	   *c;
! 
! 		token = lfirst(line_item);
! 
! 		c = strchr(token, '=');
! 		if (c == NULL)
  		{
! 			/*
! 			 * Got something that's not a name=value pair.
! 			 */
! 			ereport(LOG,
! 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 					 errmsg("authentication option not in name=value format: %s", token),
! 					 errcontext("line %d of configuration file \"%s\"",
! 								line_num, HbaFileName)));
! 			return false;
! 		}
! 		else
! 		{
! 			*c++ = '\0';		/* token now holds "name", c holds "value" */
! 			if (strcmp(token, "map") == 0)
! 			{
! 				if (parsedline->auth_method != uaIdent &&
! 					parsedline->auth_method != uaPeer &&
! 					parsedline->auth_method != uaKrb5 &&
! 					parsedline->auth_method != uaGSS &&
! 					parsedline->auth_method != uaSSPI &&
! 					parsedline->auth_method != uaCert)
! 					INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, krb5, gssapi, sspi and cert"));
! 				parsedline->usermap = pstrdup(c);
! 			}
! 			else if (strcmp(token, "clientcert") == 0)
  			{
  				/*
! 				 * Since we require ctHostSSL, this really can never happen on
! 				 * non-SSL-enabled builds, so don't bother checking for
! 				 * USE_SSL.
  				 */
- 				if (parsedline->conntype != ctHostSSL)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("clientcert can only be configured for \"hostssl\" rows"),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					return false;
- 				}
- 				if (strcmp(c, "1") == 0)
- 				{
- 					if (!secure_loaded_verify_locations())
- 					{
- 						ereport(LOG,
- 								(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 								 errmsg("client certificates can only be checked if a root certificate store is available"),
- 								 errhint("Make sure the root.crt file is present and readable."),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 						return false;
- 					}
- 					parsedline->clientcert = true;
- 				}
- 				else
- 				{
- 					if (parsedline->auth_method == uaCert)
- 					{
- 						ereport(LOG,
- 								(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 								 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 						return false;
- 					}
- 					parsedline->clientcert = false;
- 				}
- 			}
- 			else if (strcmp(token, "pamservice") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
- 				parsedline->pamservice = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldaptls") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
- 				if (strcmp(c, "1") == 0)
- 					parsedline->ldaptls = true;
- 				else
- 					parsedline->ldaptls = false;
- 			}
- 			else if (strcmp(token, "ldapserver") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
- 				parsedline->ldapserver = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapport") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
- 				parsedline->ldapport = atoi(c);
- 				if (parsedline->ldapport == 0)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("invalid LDAP port number: \"%s\"", c),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					return false;
- 				}
- 			}
- 			else if (strcmp(token, "ldapbinddn") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
- 				parsedline->ldapbinddn = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapbindpasswd") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
- 				parsedline->ldapbindpasswd = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapsearchattribute") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
- 				parsedline->ldapsearchattribute = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapbasedn") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
- 				parsedline->ldapbasedn = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapprefix") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
- 				parsedline->ldapprefix = pstrdup(c);
- 			}
- 			else if (strcmp(token, "ldapsuffix") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
- 				parsedline->ldapsuffix = pstrdup(c);
- 			}
- 			else if (strcmp(token, "krb_server_hostname") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaKrb5, "krb_server_hostname", "krb5");
- 				parsedline->krb_server_hostname = pstrdup(c);
- 			}
- 			else if (strcmp(token, "krb_realm") == 0)
- 			{
- 				if (parsedline->auth_method != uaKrb5 &&
- 					parsedline->auth_method != uaGSS &&
- 					parsedline->auth_method != uaSSPI)
- 					INVALID_AUTH_OPTION("krb_realm", gettext_noop("krb5, gssapi and sspi"));
- 				parsedline->krb_realm = pstrdup(c);
- 			}
- 			else if (strcmp(token, "include_realm") == 0)
- 			{
- 				if (parsedline->auth_method != uaKrb5 &&
- 					parsedline->auth_method != uaGSS &&
- 					parsedline->auth_method != uaSSPI)
- 					INVALID_AUTH_OPTION("include_realm", gettext_noop("krb5, gssapi and sspi"));
- 				if (strcmp(c, "1") == 0)
- 					parsedline->include_realm = true;
- 				else
- 					parsedline->include_realm = false;
- 			}
- 			else if (strcmp(token, "radiusserver") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
- 
- 				MemSet(&hints, 0, sizeof(hints));
- 				hints.ai_socktype = SOCK_DGRAM;
- 				hints.ai_family = AF_UNSPEC;
- 
- 				ret = pg_getaddrinfo_all(c, NULL, &hints, &gai_result);
- 				if (ret || !gai_result)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
- 									c, gai_strerror(ret)),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					if (gai_result)
- 						pg_freeaddrinfo_all(hints.ai_family, gai_result);
- 					return false;
- 				}
- 				pg_freeaddrinfo_all(hints.ai_family, gai_result);
- 				parsedline->radiusserver = pstrdup(c);
- 			}
- 			else if (strcmp(token, "radiusport") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
- 				parsedline->radiusport = atoi(c);
- 				if (parsedline->radiusport == 0)
- 				{
- 					ereport(LOG,
- 							(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 							 errmsg("invalid RADIUS port number: \"%s\"", c),
- 						   errcontext("line %d of configuration file \"%s\"",
- 									  line_num, HbaFileName)));
- 					return false;
- 				}
- 			}
- 			else if (strcmp(token, "radiussecret") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
- 				parsedline->radiussecret = pstrdup(c);
- 			}
- 			else if (strcmp(token, "radiusidentifier") == 0)
- 			{
- 				REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
- 				parsedline->radiusidentifier = pstrdup(c);
- 			}
- 			else
- 			{
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 					errmsg("unrecognized authentication option name: \"%s\"",
! 						   token),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return false;
  			}
  		}
  	}
  
--- 1270,1307 ----
  				 errmsg("cert authentication is only supported on hostssl connections"),
  				 errcontext("line %d of configuration file \"%s\"",
  							line_num, HbaFileName)));
! 		return NULL;
  	}
  
  	/* Parse remaining arguments */
! 	while ((field = lnext(field)) != NULL)
  	{
! 		tokens = lfirst(field);
! 		foreach(tokencell, tokens)
  		{
! 			char	   *val;
! 			token = lfirst(tokencell);
! 
! 			str = pstrdup(token->string);
! 			val = strchr(str, '=');
! 			if (val == NULL)
  			{
  				/*
! 				 * Got something that's not a name=value pair.
  				 */
  				ereport(LOG,
  						(errcode(ERRCODE_CONFIG_FILE_ERROR),
! 						 errmsg("authentication option not in name=value format: %s", token->string),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return NULL;
  			}
+ 
+ 			*val++ = '\0';	/* str now holds "name", val holds "value" */
+ 			if (!parse_hba_auth_opt(str, val, parsedline, line_num))
+ 				/* parse_hba_auth_opt already logged the error message */
+ 				return NULL;
+ 			pfree(str);
  		}
  	}
  
***************
*** 1474,1480 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, or ldapsearchattribute together with ldapprefix"),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return false;
  			}
  		}
  		else if (!parsedline->ldapbasedn)
--- 1331,1337 ----
  						 errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, or ldapsearchattribute together with ldapprefix"),
  						 errcontext("line %d of configuration file \"%s\"",
  									line_num, HbaFileName)));
! 				return NULL;
  			}
  		}
  		else if (!parsedline->ldapbasedn)
***************
*** 1484,1490 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return false;
  		}
  	}
  
--- 1341,1347 ----
  					 errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
  					 errcontext("line %d of configuration file \"%s\"",
  								line_num, HbaFileName)));
! 			return NULL;
  		}
  	}
  
***************
*** 1502,1516 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline)
  		parsedline->clientcert = true;
  	}
  
! 	return true;
  }
  
- 
  /*
!  *	Scan the (pre-parsed) hba file line by line, looking for a match
!  *	to the port's connection request.
   */
  static bool
  check_hba(hbaPort *port)
  {
  	Oid			roleid;
--- 1359,1586 ----
  		parsedline->clientcert = true;
  	}
  
! 	return parsedline;
  }
  
  /*
!  * Parse one name-value pair as an authentication option into the given
!  * HbaLine.  Return true if we successfully parse the option, false if we
!  * encounter an error.
   */
  static bool
+ parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline, int line_num)
+ {
+ 	if (strcmp(name, "map") == 0)
+ 	{
+ 		if (hbaline->auth_method != uaIdent &&
+ 			hbaline->auth_method != uaPeer &&
+ 			hbaline->auth_method != uaKrb5 &&
+ 			hbaline->auth_method != uaGSS &&
+ 			hbaline->auth_method != uaSSPI &&
+ 			hbaline->auth_method != uaCert)
+ 			INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, krb5, gssapi, sspi and cert"));
+ 		hbaline->usermap = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "clientcert") == 0)
+ 	{
+ 		/*
+ 		 * Since we require ctHostSSL, this really can never happen
+ 		 * on non-SSL-enabled builds, so don't bother checking for
+ 		 * USE_SSL.
+ 		 */
+ 		if (hbaline->conntype != ctHostSSL)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("clientcert can only be configured for \"hostssl\" rows"),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			return false;
+ 		}
+ 		if (strcmp(val, "1") == 0)
+ 		{
+ 			if (!secure_loaded_verify_locations())
+ 			{
+ 				ereport(LOG,
+ 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 						 errmsg("client certificates can only be checked if a root certificate store is available"),
+ 						 errhint("Make sure the root.crt file is present and readable."),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 				return false;
+ 			}
+ 			hbaline->clientcert = true;
+ 		}
+ 		else
+ 		{
+ 			if (hbaline->auth_method == uaCert)
+ 			{
+ 				ereport(LOG,
+ 						(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 						 errmsg("clientcert can not be set to 0 when using \"cert\" authentication"),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 				return false;
+ 			}
+ 			hbaline->clientcert = false;
+ 		}
+ 	}
+ 	else if (strcmp(name, "pamservice") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
+ 		hbaline->pamservice = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldaptls") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldaptls", "ldap");
+ 		if (strcmp(val, "1") == 0)
+ 			hbaline->ldaptls = true;
+ 		else
+ 			hbaline->ldaptls = false;
+ 	}
+ 	else if (strcmp(name, "ldapserver") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapserver", "ldap");
+ 		hbaline->ldapserver = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapport") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapport", "ldap");
+ 		hbaline->ldapport = atoi(val);
+ 		if (hbaline->ldapport == 0)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("invalid LDAP port number: \"%s\"", val),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			return false;
+ 		}
+ 	}
+ 	else if (strcmp(name, "ldapbinddn") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbinddn", "ldap");
+ 		hbaline->ldapbinddn = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapbindpasswd") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbindpasswd", "ldap");
+ 		hbaline->ldapbindpasswd = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapsearchattribute") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchattribute", "ldap");
+ 		hbaline->ldapsearchattribute = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapbasedn") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapbasedn", "ldap");
+ 		hbaline->ldapbasedn = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapprefix") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapprefix", "ldap");
+ 		hbaline->ldapprefix = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "ldapsuffix") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaLDAP, "ldapsuffix", "ldap");
+ 		hbaline->ldapsuffix = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "krb_server_hostname") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaKrb5, "krb_server_hostname", "krb5");
+ 		hbaline->krb_server_hostname = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "krb_realm") == 0)
+ 	{
+ 		if (hbaline->auth_method != uaKrb5 &&
+ 			hbaline->auth_method != uaGSS &&
+ 			hbaline->auth_method != uaSSPI)
+ 			INVALID_AUTH_OPTION("krb_realm", gettext_noop("krb5, gssapi and sspi"));
+ 		hbaline->krb_realm = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "include_realm") == 0)
+ 	{
+ 		if (hbaline->auth_method != uaKrb5 &&
+ 			hbaline->auth_method != uaGSS &&
+ 			hbaline->auth_method != uaSSPI)
+ 			INVALID_AUTH_OPTION("include_realm", gettext_noop("krb5, gssapi and sspi"));
+ 		if (strcmp(val, "1") == 0)
+ 			hbaline->include_realm = true;
+ 		else
+ 			hbaline->include_realm = false;
+ 	}
+ 	else if (strcmp(name, "radiusserver") == 0)
+ 	{
+ 		struct addrinfo *gai_result;
+ 		struct addrinfo hints;
+ 		int ret;
+ 
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusserver", "radius");
+ 
+ 		MemSet(&hints, 0, sizeof(hints));
+ 		hints.ai_socktype = SOCK_DGRAM;
+ 		hints.ai_family = AF_UNSPEC;
+ 
+ 		ret = pg_getaddrinfo_all(val, NULL, &hints, &gai_result);
+ 		if (ret || !gai_result)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("could not translate RADIUS server name \"%s\" to address: %s",
+ 							val, gai_strerror(ret)),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			if (gai_result)
+ 				pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ 			return false;
+ 		}
+ 		pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ 		hbaline->radiusserver = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "radiusport") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusport", "radius");
+ 		hbaline->radiusport = atoi(val);
+ 		if (hbaline->radiusport == 0)
+ 		{
+ 			ereport(LOG,
+ 					(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 					 errmsg("invalid RADIUS port number: \"%s\"", val),
+ 				   errcontext("line %d of configuration file \"%s\"",
+ 							  line_num, HbaFileName)));
+ 			return false;
+ 		}
+ 	}
+ 	else if (strcmp(name, "radiussecret") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecret", "radius");
+ 		hbaline->radiussecret = pstrdup(val);
+ 	}
+ 	else if (strcmp(name, "radiusidentifier") == 0)
+ 	{
+ 		REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius");
+ 		hbaline->radiusidentifier = pstrdup(val);
+ 	}
+ 	else
+ 	{
+ 		ereport(LOG,
+ 				(errcode(ERRCODE_CONFIG_FILE_ERROR),
+ 			errmsg("unrecognized authentication option name: \"%s\"",
+ 				   name),
+ 				 errcontext("line %d of configuration file \"%s\"",
+ 							line_num, HbaFileName)));
+ 		return false;
+ 	}
+ 	return true;
+ }
+ 
+ /*
+  *	Scan the pre-parsed hba file, looking for a match to the port's connection
+  *	request.
+  */
+ static void
  check_hba(hbaPort *port)
  {
  	Oid			roleid;
***************
*** 1589,1660 **** check_hba(hbaPort *port)
  
  		/* Check database and role */
  		if (!check_db(port->database_name, port->user_name, roleid,
! 					  hba->database))
  			continue;
  
! 		if (!check_role(port->user_name, roleid, hba->role))
  			continue;
  
  		/* Found a record that matched! */
  		port->hba = hba;
! 		return true;
  	}
  
  	/* If no matching entry was found, then implicitly reject. */
  	hba = palloc0(sizeof(HbaLine));
  	hba->auth_method = uaImplicitReject;
  	port->hba = hba;
- 	return true;
- 
- 	/*
- 	 * XXX: Return false only happens if we have a parsing error, which we can
- 	 * no longer have (parsing now in postmaster). Consider changing API.
- 	 */
- }
- 
- /*
-  * Free an HbaLine structure
-  */
- static void
- free_hba_record(HbaLine *record)
- {
- 	if (record->database)
- 		pfree(record->database);
- 	if (record->role)
- 		pfree(record->role);
- 	if (record->usermap)
- 		pfree(record->usermap);
- 	if (record->pamservice)
- 		pfree(record->pamservice);
- 	if (record->ldapserver)
- 		pfree(record->ldapserver);
- 	if (record->ldapprefix)
- 		pfree(record->ldapprefix);
- 	if (record->ldapsuffix)
- 		pfree(record->ldapsuffix);
- 	if (record->krb_server_hostname)
- 		pfree(record->krb_server_hostname);
- 	if (record->krb_realm)
- 		pfree(record->krb_realm);
- 	pfree(record);
- }
- 
- /*
-  * Free all records on the parsed HBA list
-  */
- static void
- clean_hba_list(List *lines)
- {
- 	ListCell   *line;
- 
- 	foreach(line, lines)
- 	{
- 		HbaLine    *parsed = (HbaLine *) lfirst(line);
- 
- 		if (parsed)
- 			free_hba_record(parsed);
- 	}
- 	list_free(lines);
  }
  
  /*
--- 1659,1679 ----
  
  		/* Check database and role */
  		if (!check_db(port->database_name, port->user_name, roleid,
! 					  hba->databases))
  			continue;
  
! 		if (!check_role(port->user_name, roleid, hba->roles))
  			continue;
  
  		/* Found a record that matched! */
  		port->hba = hba;
! 		return;
  	}
  
  	/* If no matching entry was found, then implicitly reject. */
  	hba = palloc0(sizeof(HbaLine));
  	hba->auth_method = uaImplicitReject;
  	port->hba = hba;
  }
  
  /*
***************
*** 1674,1679 **** load_hba(void)
--- 1693,1701 ----
  			   *line_num;
  	List	   *new_parsed_lines = NIL;
  	bool		ok = true;
+ 	MemoryContext	linecxt;
+ 	MemoryContext	oldcxt;
+ 	MemoryContext	hbacxt;
  
  	file = AllocateFile(HbaFileName, "r");
  	if (file == NULL)
***************
*** 1691,1710 **** load_hba(void)
  		return false;
  	}
  
! 	tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
  	FreeFile(file);
  
  	/* Now parse all the lines */
  	forboth(line, hba_lines, line_num, hba_line_nums)
  	{
  		HbaLine    *newline;
  
! 		newline = palloc0(sizeof(HbaLine));
! 
! 		if (!parse_hba_line(lfirst(line), lfirst_int(line_num), newline))
  		{
! 			/* Parse error in the file, so indicate there's a problem */
! 			free_hba_record(newline);
  			ok = false;
  
  			/*
--- 1713,1741 ----
  		return false;
  	}
  
! 	linecxt = tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
  	FreeFile(file);
  
  	/* Now parse all the lines */
+ 	hbacxt = AllocSetContextCreate(TopMemoryContext,
+ 								   "hba parser context",
+ 								   ALLOCSET_DEFAULT_MINSIZE,
+ 								   ALLOCSET_DEFAULT_MINSIZE,
+ 								   ALLOCSET_DEFAULT_MAXSIZE);
+ 	oldcxt = MemoryContextSwitchTo(hbacxt);
  	forboth(line, hba_lines, line_num, hba_line_nums)
  	{
  		HbaLine    *newline;
  
! 		if ((newline = parse_hba_line(lfirst(line), lfirst_int(line_num))) == NULL)
  		{
! 			/*
! 			 * Parse error in the file, so indicate there's a problem.  NB: a
! 			 * problem in a line will free the memory for all previous lines as
! 			 * well!
! 			 */
! 			MemoryContextReset(hbacxt);
! 			new_parsed_lines = NIL;
  			ok = false;
  
  			/*
***************
*** 1718,1735 **** load_hba(void)
  		new_parsed_lines = lappend(new_parsed_lines, newline);
  	}
  
! 	/* Free the temporary lists */
! 	free_lines(&hba_lines, &hba_line_nums);
  
  	if (!ok)
  	{
  		/* Parsing failed at one or more rows, so bail out */
! 		clean_hba_list(new_parsed_lines);
  		return false;
  	}
  
  	/* Loaded new file successfully, replace the one we use */
! 	clean_hba_list(parsed_hba_lines);
  	parsed_hba_lines = new_parsed_lines;
  
  	return true;
--- 1749,1769 ----
  		new_parsed_lines = lappend(new_parsed_lines, newline);
  	}
  
! 	/* Free tokenizer memory */
! 	MemoryContextDelete(linecxt);
! 	MemoryContextSwitchTo(oldcxt);
  
  	if (!ok)
  	{
  		/* Parsing failed at one or more rows, so bail out */
! 		MemoryContextDelete(hbacxt);
  		return false;
  	}
  
  	/* Loaded new file successfully, replace the one we use */
! 	if (parsed_hba_context != NULL)
! 		MemoryContextDelete(parsed_hba_context);
! 	parsed_hba_context = hbacxt;
  	parsed_hba_lines = new_parsed_lines;
  
  	return true;
***************
*** 1746,1753 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
  					const char *pg_role, const char *ident_user,
  					bool case_insensitive, bool *found_p, bool *error_p)
  {
! 	ListCell   *line_item;
! 	char	   *token;
  	char	   *file_map;
  	char	   *file_pgrole;
  	char	   *file_ident_user;
--- 1780,1788 ----
  					const char *pg_role, const char *ident_user,
  					bool case_insensitive, bool *found_p, bool *error_p)
  {
! 	ListCell   *field;
! 	List	   *tokens;
! 	HbaToken   *token;
  	char	   *file_map;
  	char	   *file_pgrole;
  	char	   *file_ident_user;
***************
*** 1756,1780 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
  	*error_p = false;
  
  	Assert(line != NIL);
! 	line_item = list_head(line);
  
  	/* Get the map token (must exist) */
! 	token = lfirst(line_item);
! 	file_map = token;
  
  	/* Get the ident user token */
! 	line_item = lnext(line_item);
! 	if (!line_item)
! 		goto ident_syntax;
! 	token = lfirst(line_item);
! 	file_ident_user = token;
  
  	/* Get the PG rolename token */
! 	line_item = lnext(line_item);
! 	if (!line_item)
! 		goto ident_syntax;
! 	token = lfirst(line_item);
! 	file_pgrole = token;
  
  	if (strcmp(file_map, usermap_name) != 0)
  		/* Line does not match the map name we're looking for, so just abort */
--- 1791,1819 ----
  	*error_p = false;
  
  	Assert(line != NIL);
! 	field = list_head(line);
  
  	/* Get the map token (must exist) */
! 	tokens = lfirst(field);
! 	IDENT_MULTI_VALUE(tokens);
! 	token = linitial(tokens);
! 	file_map = token->string;
  
  	/* Get the ident user token */
! 	field = lnext(field);
! 	IDENT_FIELD_ABSENT(field);
! 	tokens = lfirst(field);
! 	IDENT_MULTI_VALUE(tokens);
! 	token = linitial(tokens);
! 	file_ident_user = token->string;
  
  	/* Get the PG rolename token */
! 	field = lnext(field);
! 	IDENT_FIELD_ABSENT(field);
! 	tokens = lfirst(field);
! 	IDENT_MULTI_VALUE(tokens);
! 	token = linitial(tokens);
! 	file_pgrole = token->string;
  
  	if (strcmp(file_map, usermap_name) != 0)
  		/* Line does not match the map name we're looking for, so just abort */
***************
*** 1913,1927 **** parse_ident_usermap(List *line, int line_number, const char *usermap_name,
  				*found_p = true;
  		}
  	}
- 
  	return;
- 
- ident_syntax:
- 	ereport(LOG,
- 			(errcode(ERRCODE_CONFIG_FILE_ERROR),
- 			 errmsg("missing entry in file \"%s\" at end of line %d",
- 					IdentFileName, line_number)));
- 	*error_p = true;
  }
  
  
--- 1952,1958 ----
***************
*** 1989,2003 **** check_usermap(const char *usermap_name,
  
  
  /*
!  * Read the ident config file and create a List of Lists of tokens in the file.
   */
  void
  load_ident(void)
  {
  	FILE	   *file;
  
! 	if (ident_lines || ident_line_nums)
! 		free_lines(&ident_lines, &ident_line_nums);
  
  	file = AllocateFile(IdentFileName, "r");
  	if (file == NULL)
--- 2020,2040 ----
  
  
  /*
!  * Read the ident config file and populate ident_lines and ident_line_nums.
!  *
!  * Like parsed_hba_lines, ident_lines is a triple-nested List of lines, fields
!  * and tokens.
   */
  void
  load_ident(void)
  {
  	FILE	   *file;
  
! 	if (ident_context != NULL)
! 	{
! 		MemoryContextDelete(ident_context);
! 		ident_context = NULL;
! 	}
  
  	file = AllocateFile(IdentFileName, "r");
  	if (file == NULL)
***************
*** 2010,2016 **** load_ident(void)
  	}
  	else
  	{
! 		tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
  		FreeFile(file);
  	}
  }
--- 2047,2054 ----
  	}
  	else
  	{
! 		ident_context = tokenize_file(IdentFileName, file, &ident_lines,
! 									  &ident_line_nums);
  		FreeFile(file);
  	}
  }
***************
*** 2022,2036 **** load_ident(void)
   *	"database" from frontend "raddr", user "user".	Return the method and
   *	an optional argument (stored in fields of *port), and STATUS_OK.
   *
!  *	Note that STATUS_ERROR indicates a problem with the hba config file.
!  *	If the file is OK but does not contain any entry matching the request,
!  *	we return STATUS_OK and method = uaImplicitReject.
   */
! int
  hba_getauthmethod(hbaPort *port)
  {
! 	if (check_hba(port))
! 		return STATUS_OK;
! 	else
! 		return STATUS_ERROR;
  }
--- 2060,2070 ----
   *	"database" from frontend "raddr", user "user".	Return the method and
   *	an optional argument (stored in fields of *port), and STATUS_OK.
   *
!  *	If the file does not contain any entry matching the request, we return
!  *	method = uaImplicitReject.
   */
! void
  hba_getauthmethod(hbaPort *port)
  {
! 	check_hba(port);
  }
*** a/src/include/libpq/hba.h
--- b/src/include/libpq/hba.h
***************
*** 49,60 **** typedef enum ConnType
  	ctHostNoSSL
  } ConnType;
  
! typedef struct
  {
  	int			linenumber;
  	ConnType	conntype;
! 	char	   *database;
! 	char	   *role;
  	struct sockaddr_storage addr;
  	struct sockaddr_storage mask;
  	IPCompareMethod ip_cmp_method;
--- 49,60 ----
  	ctHostNoSSL
  } ConnType;
  
! typedef struct HbaLine
  {
  	int			linenumber;
  	ConnType	conntype;
! 	List	   *databases;
! 	List	   *roles;
  	struct sockaddr_storage addr;
  	struct sockaddr_storage mask;
  	IPCompareMethod ip_cmp_method;
***************
*** 87,93 **** typedef struct Port hbaPort;
  
  extern bool load_hba(void);
  extern void load_ident(void);
! extern int	hba_getauthmethod(hbaPort *port);
  extern int check_usermap(const char *usermap_name,
  			  const char *pg_role, const char *auth_user,
  			  bool case_sensitive);
--- 87,93 ----
  
  extern bool load_hba(void);
  extern void load_ident(void);
! 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);
#31Brendan Jurd
direvus@gmail.com
In reply to: Alvaro Herrera (#30)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

On 24 June 2011 03:48, Alvaro Herrera <alvherre@commandprompt.com> wrote:

I have touched next_token() and next_token_expand() a bit more, because
it seemed to me that they could be simplified further (the bit about
returning the comma in the token, instead of being a boolean return,
seemed strange).  Please let me know what you think.

Cool. I didn't like the way it returned the comma either, but I
thought it would be best if I kept the changes in my patch to a
minimum.

Cheers,
BJ

#32Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#30)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Hello

I have touched next_token() and next_token_expand() a bit more, because
it seemed to me that they could be simplified further (the bit about
returning the comma in the token, instead of being a boolean return,
seemed strange).  Please let me know what you think.

I am thinking, so it is ok.

I am not sure, if load_ident is correct. In load_hba you clean a
previous context after successful processing. In load_ident you clean
data on start. Is it ok?

Regards

Pavel

#33Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#32)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Pavel Stehule's message of dom jun 26 13:10:13 -0400 2011:

Hello

I have touched next_token() and next_token_expand() a bit more, because
it seemed to me that they could be simplified further (the bit about
returning the comma in the token, instead of being a boolean return,
seemed strange).  Please let me know what you think.

I am thinking, so it is ok.

Thanks, I have pushed it. Brendan: thanks for the patch.

I am not sure, if load_ident is correct. In load_hba you clean a
previous context after successful processing. In load_ident you clean
data on start. Is it ok?

Yeah, they are a bit inconsistent. I am uninclined to mess with it,
though. Right now it seems to me that the only way it could fail is if
you hit an out-of-memory or a similar problem. And if you hit that at
postmaster startup ... well ... I guess you have A Problem.

If somebody wants to submit a patch to fix that, be my guest.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#34Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#33)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/28 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of dom jun 26 13:10:13 -0400 2011:

Hello

I have touched next_token() and next_token_expand() a bit more, because
it seemed to me that they could be simplified further (the bit about
returning the comma in the token, instead of being a boolean return,
seemed strange).  Please let me know what you think.

I am thinking, so it is ok.

Thanks, I have pushed it.  Brendan: thanks for the patch.

I am not sure, if load_ident is correct. In load_hba you clean a
previous context after successful processing. In load_ident you clean
data on start. Is it ok?

Yeah, they are a bit inconsistent.  I am uninclined to mess with it,
though.  Right now it seems to me that the only way it could fail is if
you hit an out-of-memory or a similar problem.  And if you hit that at
postmaster startup ... well ... I guess you have A Problem.

there should be a format (syntax) error. If somebody breaks a pg_ident
and will do a reload, then all ident mapping is lost.

This is not related to topic or doesn't modify current behave, so
should not be repaired now

Pavel

Show quoted text

If somebody wants to submit a patch to fix that, be my guest.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#35Alvaro Herrera
alvherre@commandprompt.com
In reply to: Pavel Stehule (#34)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

Excerpts from Pavel Stehule's message of mar jun 28 16:19:02 -0400 2011:

there should be a format (syntax) error. If somebody breaks a pg_ident
and will do a reload, then all ident mapping is lost.

No, the file is not actually parsed until the auth verification runs.
The incorrect tokens are happily stored in memory by the tokeniser. In
my view that's the wrong way to do it, but changing it seems to be a
lot of work (I didn't try).

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#36Pavel Stehule
pavel.stehule@gmail.com
In reply to: Alvaro Herrera (#35)
Re: Fwd: Keywords in pg_hba.conf should be field-specific

2011/6/28 Alvaro Herrera <alvherre@commandprompt.com>:

Excerpts from Pavel Stehule's message of mar jun 28 16:19:02 -0400 2011:

there should be a format (syntax) error. If somebody breaks a pg_ident
and will do a reload, then all ident mapping is lost.

No, the file is not actually parsed until the auth verification runs.
The incorrect tokens are happily stored in memory by the tokeniser.  In
my view that's the wrong way to do it, but changing it seems to be a
lot of work (I didn't try).

ok

Show quoted text

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support