/*-------------------------------------------------------------------------
 *
 * hba.c
 *	  Routines to handle host based authentication (that's the scheme
 *	  wherein you authenticate a user by seeing what IP address the system
 *	  says he comes from and choosing authentication method based on it).
 *
 * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.178 2009/01/02 11:34:03 mha Exp $
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <pwd.h>
#include <fcntl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include "libpq/ip.h"
#include "libpq/libpq.h"
#include "regex/regex.h"
#include "storage/fd.h"
#include "utils/flatfiles.h"
#include "utils/guc.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

/* pre-parsed content of HBA config file */
static List *parsed_hba_lines = NIL;

/*
 * These variables hold the pre-parsed contents of the ident
 * configuration files, as well as the flat auth file.
 * Each is a list of sublists, one sublist for
 * each (non-empty, non-comment) line of the file.	Each sublist's
 * first item is an integer line number (so we can give somewhat-useful
 * location info in error messages).  Remaining 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.
 */

/* pre-parsed content of ident usermap file and corresponding line #s */
static List *ident_lines = NIL;
static List *ident_line_nums = NIL;

/* pre-parsed content of flat auth file and corresponding line #s */
static List *role_lines = NIL;
static List *role_line_nums = NIL;

/* sorted entries so we can do binary search lookups */
static List **role_sorted = NULL;		/* sorted role list, for bsearch() */
static int	role_length;

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,
 * so provide our own version.
 */
bool
pg_isblank(const char c)
{
	return c == ' ' || c == '\t' || c == '\r';
}


/*
 * Grab one token out of fp. Tokens are strings of non-blank
 * characters bounded by blank characters, commas, beginning of line, and
 * end of line. Blank means space or tab. Tokens can be delimited by
 * double quotes (this allows the inclusion of blanks, but not newlines).
 *
 * The token, if any, is returned at *buf (a buffer of size bufsz).
 *
 * If successful: store null-terminated token at *buf and return TRUE.
 * If no more tokens on line: set *buf = '\0' and return FALSE.
 *
 * Leave file positioned at the character immediately after the token or EOF,
 * 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 names or
 * database 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)
{
	int			c;
	char	   *start_buf = buf;
	char	   *end_buf = buf + (bufsz - 2);
	bool		in_quote = false;
	bool		was_quote = false;
	bool		saw_quote = false;

	Assert(end_buf > start_buf);

	/* Move over initial whitespace and commas */
	while ((c = getc(fp)) != EOF && (pg_isblank(c) || c == ','))
		;

	if (c == EOF || c == '\n')
	{
		*buf = '\0';
		return false;
	}

	/*
	 * Build a token in buf of next characters up to EOF, EOL, unquoted comma,
	 * or unquoted whitespace.
	 */
	while (c != EOF && c != '\n' &&
		   (!pg_isblank(c) || in_quote))
	{
		/* skip comments to EOL */
		if (c == '#' && !in_quote)
		{
			while ((c = getc(fp)) != EOF && c != '\n')
				;
			/* If only comment, consume EOL too; return EOL */
			if (c != EOF && buf == start_buf)
				c = getc(fp);
			break;
		}

		if (buf >= end_buf)
		{
			*buf = '\0';
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
			   errmsg("authentication file token too long, skipping: \"%s\"",
					  start_buf)));
			/* Discard remainder of line */
			while ((c = getc(fp)) != EOF && c != '\n')
				;
			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 == '"')
			was_quote = !was_quote;
		else
			was_quote = false;

		if (c == '"')
		{
			in_quote = !in_quote;
			saw_quote = true;
		}

		c = getc(fp);
	}

	/*
	 * Put back the char right after the token (critical in case it is EOL,
	 * since we need to detect end-of-line at next call).
	 */
	if (c != EOF)
		ungetc(c, fp);

	*buf = '\0';

	if (!saw_quote &&
		(strcmp(start_buf, "all") == 0 ||
		 strcmp(start_buf, "sameuser") == 0 ||
		 strcmp(start_buf, "samegroup") == 0 ||
		 strcmp(start_buf, "samerole") == 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;
	char	   *incbuf;
	int			needed;

	do
	{
		if (!next_token(file, buf, sizeof(buf)))
			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 (buf[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))
	{
		/* absolute path is taken as-is */
		inc_fullname = pstrdup(inc_filename);
	}
	else
	{
		/* relative path is relative to dir of calling file */
		inc_fullname = (char *) palloc(strlen(outer_filename) + 1 +
									   strlen(inc_filename) + 1);
		strcpy(inc_fullname, outer_filename);
		get_parent_directory(inc_fullname);
		join_path_components(inc_fullname, inc_fullname, inc_filename);
		canonicalize_path(inc_fullname);
	}

	inc_file = AllocateFile(inc_fullname, "r");
	if (inc_file == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 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))
	{
		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++;
		}
	}
}

/*
 * Compare two lines based on their role/member names.
 *
 * Used for bsearch() lookup.
 */
static int
role_bsearch_cmp(const void *role, const void *list)
{
	char	   *role2 = linitial(*(List **) list);

	return strcmp(role, role2);
}


/*
 * Lookup a role name in the pg_auth file
 */
List	  **
get_role_line(const char *role)
{
	/* On some versions of Solaris, bsearch of zero items dumps core */
	if (role_length == 0)
		return NULL;

	return (List **) bsearch((void *) role,
							 (void *) role_sorted,
							 role_length,
							 sizeof(List *),
							 role_bsearch_cmp);
}


/*
 * Does user belong to role?
 *
 * user is always the name given as the attempted login identifier.
 * We check to see if it is a member of the specified role name.
 */
static bool
is_member(const char *user, const char *role)
{
	List	  **line;
	ListCell   *line_item;

	if ((line = get_role_line(user)) == NULL)
		return false;			/* if user not exist, say "no" */

	/* A user always belongs to its own role */
	if (strcmp(user, role) == 0)
		return true;

	/*
	 * skip over the role name, password, valuntil, examine all the membership
	 * entries
	 */
	if (list_length(*line) < 4)
		return false;
	for_each_cell(line_item, lnext(lnext(lnext(list_head(*line)))))
	{
		if (strcmp((char *) lfirst(line_item), role) == 0)
			return true;
	}

	return false;
}

/*
 * 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, 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(role, tok + 1))
				return true;
		}
		else if (strcmp(tok, role) == 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, char *param_str)
{
	char	   *tok;

	for (tok = strtok(param_str, MULTI_VALUE_SEP);
		 tok != NULL;
		 tok = strtok(NULL, MULTI_VALUE_SEP))
	{
		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(role, dbname))
				return true;
		}
		else if (strcmp(tok, dbname) == 0)
			return true;
	}
	return false;
}


/*
 * Macros used to check and report on invalid configuration options.
 * INVALID_AUTH_OPTION = reports when an option is specified for a method where it's
 *                       not supported.
 * REQUIRE_AUTH_OPTION = same as INVALID_AUTH_OPTION, except it also checks if the
 *                       method is actually the one specified. Used as a shortcut when
 *                       the option is only valid for one authentication method.
 * MANDATORY_AUTH_ARG  = check if a required option is set for an authentication method,
 *                       reporting error if it's not.
 */
#define INVALID_AUTH_OPTION(optname, validmethods) do {\
	ereport(LOG, \
			(errcode(ERRCODE_CONFIG_FILE_ERROR), \
			 errmsg("authentication option '%s' is only valid for authentication methods '%s'", \
					optname, validmethods), \
			 errcontext("line %d of configuration file \"%s\"", \
					line_num, HbaFileName))); \
	return false; \
} while (0);

#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) do {\
	if (parsedline->auth_method != methodval) \
		INVALID_AUTH_OPTION(optname, validmethods); \
} while (0);

#define MANDATORY_AUTH_ARG(argvar, argname, authname) do {\
	if (argvar == NULL) {\
		ereport(LOG, \
				(errcode(ERRCODE_CONFIG_FILE_ERROR), \
				 errmsg("authentication method '%s' requires argument '%s' to be set", \
						authname, argname), \
				 errcontext("line %d of configuration file \"%s\"", \
						line_num, HbaFileName))); \
		return false; \
	} \
} 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)
	{
		parsedline->conntype = ctLocal;
	}
	else if (strcmp(token, "host") == 0
			 || strcmp(token, "hostssl") == 0
			 || strcmp(token, "hostnossl") == 0)
	{

		if (token[4] == 's')	/* "hostssl" */
		{
#ifdef USE_SSL
			parsedline->conntype = ctHostSSL;
#else
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("hostssl not supported on this platform"),
					 errhint("compile with --enable-ssl 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;
		}
#endif
		else 
		{
			/* "host", or "hostnossl" and SSL support not built in */
			parsedline->conntype = ctHost;
		}
	} /* record type */
	else
	{
		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 = pstrdup(lfirst(line_item));

		/* Check if it has a CIDR suffix and if so isolate it */
		cidr_slash = strchr(token, '/');
		if (cidr_slash)
			*cidr_slash = '\0';

		/* Get the IP address either way */
		hints.ai_flags = AI_NUMERICHOST;
		hints.ai_family = PF_UNSPEC;
		hints.ai_socktype = 0;
		hints.ai_protocol = 0;
		hints.ai_addrlen = 0;
		hints.ai_canonname = NULL;
		hints.ai_addr = NULL;
		hints.ai_next = NULL;

		ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result);
		if (ret || !gai_result)
		{
			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 (cidr_slash)
				*cidr_slash = '/';
			if (gai_result)
				pg_freeaddrinfo_all(hints.ai_family, gai_result);
			return false;
		}

		if (cidr_slash)
			*cidr_slash = '/';

		memcpy(&parsedline->addr, gai_result->ai_addr, gai_result->ai_addrlen);
		pg_freeaddrinfo_all(hints.ai_family, gai_result);

		/* Get the netmask */
		if (cidr_slash)
		{
			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),
						 errcontext("line %d of configuration file \"%s\"",
							line_num, HbaFileName)));
				return false;
			}
		}
		else
		{
			/* Read the mask field. */
			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, gai_result->ai_addrlen);
			pg_freeaddrinfo_all(hints.ai_family, gai_result);

			if (parsedline->addr.ss_family != parsedline->mask.ss_family)
			{
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("IP address and mask do not match in file \"%s\" line %d",
								HbaFileName, line_num)));
				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, "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)
		{
			ereport(LOG,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled")));
			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
	{
		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)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid authentication method \"%s\": not supported on this platform",
						token),
				 errcontext("line %d of configuration file \"%s\"",
						line_num, HbaFileName)));
		return false;
	}

	/* Invalid authentication combinations */
	if (parsedline->conntype == ctLocal &&
		parsedline->auth_method == uaKrb5)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("krb5 authentication is not supported on local sockets"),
				 errcontext("line %d of configuration file \"%s\"",
						line_num, HbaFileName)));
		return false;
	}

	if (parsedline->conntype != ctHostSSL &&
		parsedline->auth_method == uaCert)
	{
		ereport(LOG,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 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.
			 *
			 * XXX: attempt to do some backwards compatible parsing here?
			 */
			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 != uaKrb5 &&
					parsedline->auth_method != uaGSS &&
					parsedline->auth_method != uaSSPI &&
					parsedline->auth_method != uaCert)
					INVALID_AUTH_OPTION("map", "ident, 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"),
								 errdetail("make sure the root certificate store 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 '%s'", c),
							 errcontext("line %d of configuration file \"%s\"",
										line_num, HbaFileName)));
					return false;
				}
			}
			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
			{
				ereport(LOG,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("unknown authentication option name '%s'", token),
						 errcontext("line %d of configuration file \"%s\"",
									line_num, HbaFileName)));
				return false;
			}
		}
	}

	/*
	 * Check if the selected authentication method has any mandatory arguments that
	 * are not set.
	 */
	if (parsedline->auth_method == uaLDAP)
	{
		MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
	}

	/*
	 * Enforce any parameters implied by other settings.
	 */
	if (parsedline->auth_method == uaCert)
	{
		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)
{
	ListCell   *line;
	HbaLine	   *hba;

	foreach(line, parsed_hba_lines)
	{
		hba = (HbaLine *) lfirst(line);

		/* Check connection type */
		if (hba->conntype == ctLocal)
		{
			if (!IS_AF_UNIX(port->raddr.addr.ss_family))
				continue;
		}
		else
		{
			if (IS_AF_UNIX(port->raddr.addr.ss_family))
				continue;

			/* Check SSL state */
#ifdef USE_SSL
			if (port->ssl)
			{
				/* Connection is SSL, match both "host" and "hostssl" */
				if (hba->conntype == ctHostNoSSL)
					continue;
			}
			else
			{
				/* Connection is not SSL, match both "host" and "hostnossl" */
				if (hba->conntype == ctHostSSL)
					continue;
			}
#else
			/* No SSL support, so reject "hostssl" lines */
			if (hba->conntype == ctHostSSL)
				continue;
#endif

			/* Check IP address */
			if (port->raddr.addr.ss_family == hba->addr.ss_family)
			{
				if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask))
					continue;
			}
#ifdef HAVE_IPV6
			else  if (hba->addr.ss_family == AF_INET &&
					  port->raddr.addr.ss_family == AF_INET6)
			{
				/*
				 * Wrong address family.  We allow only one case: if the file has
				 * IPv4 and the port is IPv6, promote the file address to IPv6 and
				 * try to match that way.
				 */
				struct sockaddr_storage addrcopy, maskcopy;
				memcpy(&addrcopy, &hba->addr, sizeof(addrcopy));
				memcpy(&maskcopy, &hba->mask, sizeof(maskcopy));
				pg_promote_v4_to_v6_addr(&addrcopy);
				pg_promote_v4_to_v6_mask(&maskcopy);
				
				if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy))
					continue;
			}
#endif /* HAVE_IPV6 */
			else
				/* Wrong address family, no IPV6 */
				continue;
		} /* != ctLocal */

		/* Check database and role */
		if (!check_db(port->database_name, port->user_name, hba->database))
			continue;

		if (!check_role(port->user_name, hba->role))
			continue;

		/* Found a record that matched! */
		port->hba = hba;
		return true;
	}

	/* If no matching entry was found, synthesize 'reject' entry. */
	hba = palloc0(sizeof(HbaLine));
	hba->auth_method = uaReject;
	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.
	 */
}


/*
 *	 Load role/password mapping file
 */
void
load_role(void)
{
	char	   *filename;
	FILE	   *role_file;

	/* Discard any old data */
	if (role_lines || role_line_nums)
		free_lines(&role_lines, &role_line_nums);
	if (role_sorted)
		pfree(role_sorted);
	role_sorted = NULL;
	role_length = 0;

	/* Read in the file contents */
	filename = auth_getflatfilename();
	role_file = AllocateFile(filename, "r");

	if (role_file == NULL)
	{
		/* no complaint if not there */
		if (errno != ENOENT)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\": %m", filename)));
		pfree(filename);
		return;
	}

	tokenize_file(filename, role_file, &role_lines, &role_line_nums);

	FreeFile(role_file);
	pfree(filename);

	/* create array for binary searching */
	role_length = list_length(role_lines);
	if (role_length)
	{
		int			i = 0;
		ListCell   *line;

		/* We assume the flat file was written already-sorted */
		role_sorted = palloc(role_length * sizeof(List *));
		foreach(line, role_lines)
			role_sorted[i++] = lfirst(line);
	}
}

/*
 * Free the contents of a hba record
 */
static void
free_hba_record(HbaLine *record)
{
	if (record->database)
		pfree(record->database);
	if (record->role)
		pfree(record->role);
	if (record->pamservice)
		pfree(record->pamservice);
	if (record->ldapserver)
		pfree(record->ldapserver);
	if (record->ldapprefix)
		pfree(record->ldapprefix);
	if (record->ldapsuffix)
		pfree(record->ldapsuffix);
}

/*
 * 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);
}

/*
 * Read the config file and create a List of HbaLine records for the contents.
 *
 * The configuration is read into a temporary list, and if any parse error occurs
 * the old list is kept in place and false is returned. Only if the whole file
 * parses Ok is the list replaced, and the function returns true.
 */
bool
load_hba(void)
{
	FILE	   *file;
	List *hba_lines = NIL;
	List *hba_line_nums = NIL;
	ListCell   *line, *line_num;
	List *new_parsed_lines = NIL;

	file = AllocateFile(HbaFileName, "r");
	/* Failure is fatal since with no HBA entries we can do nothing... */
	if (file == NULL)
		ereport(FATAL,
				(errcode_for_file_access(),
				 errmsg("could not open configuration file \"%s\": %m",
						HbaFileName)));

	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 bail out */
			free_hba_record(newline);
			pfree(newline);
			clean_hba_list(new_parsed_lines);
			/* Error has already been reported in the parsing function */
			return false;
		}

		new_parsed_lines = lappend(new_parsed_lines, newline);
	}

	/* Loaded new file successfully, replace the one we use */
	clean_hba_list(parsed_hba_lines);
	parsed_hba_lines = new_parsed_lines;

	/* Free the temporary lists */
	free_lines(&hba_lines, &hba_line_nums);

	return true;
}

/*
 * Read and parse one line from the flat pg_database file.
 *
 * Returns TRUE on success, FALSE if EOF; bad data causes elog(FATAL).
 *
 * Output parameters:
 *	dbname: gets database name (must be of size NAMEDATALEN bytes)
 *	dboid: gets database OID
 *	dbtablespace: gets database's default tablespace's OID
 *	dbfrozenxid: gets database's frozen XID
 *
 * This is not much related to the other functions in hba.c, but we put it
 * here because it uses the next_token() infrastructure.
 */
bool
read_pg_database_line(FILE *fp, char *dbname, Oid *dboid,
					  Oid *dbtablespace, TransactionId *dbfrozenxid)
{
	char		buf[MAX_TOKEN];

	if (feof(fp))
		return false;
	if (!next_token(fp, buf, sizeof(buf)))
		return false;
	if (strlen(buf) >= NAMEDATALEN)
		elog(FATAL, "bad data in flat pg_database file");
	strcpy(dbname, buf);
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
	*dboid = atooid(buf);
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
	*dbtablespace = atooid(buf);
	next_token(fp, buf, sizeof(buf));
	if (!isdigit((unsigned char) buf[0]))
		elog(FATAL, "bad data in flat pg_database file");
	*dbfrozenxid = atoxid(buf);
	/* expect EOL next */
	if (next_token(fp, buf, sizeof(buf)))
		elog(FATAL, "bad data in flat pg_database file");
	return true;
}

/*
 *	Process one line from the ident config file.
 *
 *	Take the line and compare it to the needed map, pg_role and ident_user.
 *	*found_p and *error_p are set according to our results.
 */
static void
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;

	*found_p = false;
	*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 */
		return;

	/* Match? */
	if (file_ident_user[0] == '/')
	{
		/*
		 * When system username starts with a slash, treat it as a regular expression.
		 * In this case, we process the system username as a regular expression that
		 * returns exactly one match. This is replaced for \1 in the database username
		 * string, if present.
		 */
		int			r;
		regex_t		re;
		regmatch_t	matches[2];
		pg_wchar   *wstr;
		int			wlen;
		char	   *ofs;
		char	   *regexp_pgrole;

		wstr = palloc((strlen(file_ident_user+1) + 1) * sizeof(pg_wchar));
		wlen = pg_mb2wchar_with_len(file_ident_user+1, wstr, strlen(file_ident_user+1));

		/*
		 * XXX: Major room for optimization: regexps could be compiled when the file is loaded
		 * and then re-used in every connection.
		 */
		r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED);
		if (r)
		{
			char errstr[100];

			pg_regerror(r, &re, errstr, sizeof(errstr));
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
					 errmsg("invalid regular expression '%s': %s", file_ident_user+1, errstr)));

			pfree(wstr);
			*error_p = true;
			return;
		}
		pfree(wstr);

		wstr = palloc((strlen(ident_user) + 1) * sizeof(pg_wchar));
		wlen = pg_mb2wchar_with_len(ident_user, wstr, strlen(ident_user));

		r = pg_regexec(&re, wstr, wlen, 0, NULL, 2, matches,0);
		if (r)
		{
			char errstr[100];

			if (r != REG_NOMATCH)
			{
				/* REG_NOMATCH is not an error, everything else is */
				pg_regerror(r, &re, errstr, sizeof(errstr));
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
						 errmsg("regular expression match for '%s' failed: %s", file_ident_user+1, errstr)));
				*error_p = true;
			}

			pfree(wstr);
			pg_regfree(&re);
			return;
		}
		pfree(wstr);

		if ((ofs = strstr(file_pgrole, "\\1")) != NULL)
		{
			/* substitution of the first argument requested */
			if (matches[1].rm_so < 0)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
						 errmsg("regular expression '%s' has no subexpressions as requested by backreference in '%s'",
								file_ident_user+1, file_pgrole)));
			/* length: original length minus length of \1 plus length of match plus null terminator */
			regexp_pgrole = palloc0(strlen(file_pgrole) - 2 + (matches[1].rm_eo-matches[1].rm_so) + 1);
			strncpy(regexp_pgrole, file_pgrole, (ofs-file_pgrole));
			memcpy(regexp_pgrole+strlen(regexp_pgrole),
				   ident_user+matches[1].rm_so,
				   matches[1].rm_eo-matches[1].rm_so);
			strcat(regexp_pgrole, ofs+2);
		}
		else
		{
			/* no substitution, so copy the match */
			regexp_pgrole = pstrdup(file_pgrole);
		}

		pg_regfree(&re);

		/* now check if the username actually matched what the user is trying to connect as */
		if (case_insensitive)
		{
			if (pg_strcasecmp(regexp_pgrole, pg_role) == 0)
				*found_p = true;
		}
		else
		{
			if (strcmp(regexp_pgrole, pg_role) == 0)
				*found_p = true;
		}
		pfree(regexp_pgrole);

		return;
	}
	else
	{
		/* Not regular expression, so make complete match */
		if (case_insensitive)
		{
			if (pg_strcasecmp(file_pgrole, pg_role) == 0 &&
				pg_strcasecmp(file_ident_user, ident_user) == 0)
				*found_p = true;
		}
		else
		{
			if (strcmp(file_pgrole, pg_role) == 0 &&
				strcmp(file_ident_user, ident_user) == 0)
				*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;
}


/*
 *	Scan the (pre-parsed) ident usermap file line by line, looking for a match
 *
 *	See if the user with ident username "ident_user" is allowed to act
 *	as Postgres user "pgrole" according to usermap "usermap_name".
 *
 *  Special case: Usermap NULL, equivalent to what was previously called
 *  "sameuser" or "samerole", don't look in the usermap
 *	file.  That's an implied map where "pgrole" must be identical to
 *	"ident_user" in order to be authorized.
 *
 *	Iff authorized, return STATUS_OK, otherwise return STATUS_ERROR.
 */
int
check_usermap(const char *usermap_name,
					const char *pg_role,
					const char *auth_user,
					bool case_insensitive)
{
	bool		found_entry = false,
				error = false;

	if (usermap_name == NULL || usermap_name[0] == '\0')
	{
		if (case_insensitive)
		{
			if (pg_strcasecmp(pg_role, auth_user) == 0)
				return STATUS_OK;
		}
		else {
			if (strcmp(pg_role, auth_user) == 0)
				return STATUS_OK;
		}
		ereport(LOG,
				(errmsg("provided username (%s) and authenticated username (%s) don't match",
						auth_user, pg_role)));
		return STATUS_ERROR;
	}
	else
	{
		ListCell   *line_cell,
				   *num_cell;

		forboth(line_cell, ident_lines, num_cell, ident_line_nums)
		{
			parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
								usermap_name, pg_role, auth_user, case_insensitive,
								&found_entry, &error);
			if (found_entry || error)
				break;
		}
	}
	if (!found_entry && !error)
	{
		ereport(LOG,
				(errmsg("no match in usermap for user '%s' authenticated as '%s'",
						pg_role, auth_user),
				 errcontext("usermap '%s'", usermap_name)));
	}
	return found_entry?STATUS_OK:STATUS_ERROR;
}


/*
 * 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)
	{
		/* not fatal ... we just won't do any special ident maps */
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open Ident usermap file \"%s\": %m",
						IdentFileName)));
	}
	else
	{
		tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
		FreeFile(file);
	}
}



/*
 *	Determine what authentication method should be used when accessing database
 *	"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 = uaReject.
 */
int
hba_getauthmethod(hbaPort *port)
{
	if (check_hba(port))
		return STATUS_OK;
	else
		return STATUS_ERROR;
}
