summaryrefslogtreecommitdiffstats
path: root/src/backend/libpq/hba.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
commit293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch)
treefc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /src/backend/libpq/hba.c
parentInitial commit. (diff)
downloadpostgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz
postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/backend/libpq/hba.c')
-rw-r--r--src/backend/libpq/hba.c3081
1 files changed, 3081 insertions, 0 deletions
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
new file mode 100644
index 0000000..75fd2a8
--- /dev/null
+++ b/src/backend/libpq/hba.c
@@ -0,0 +1,3081 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/libpq/hba.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <ctype.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#include "access/htup_details.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_type.h"
+#include "common/ip.h"
+#include "common/string.h"
+#include "funcapi.h"
+#include "libpq/ifaddr.h"
+#include "libpq/libpq.h"
+#include "miscadmin.h"
+#include "postmaster/postmaster.h"
+#include "regex/regex.h"
+#include "replication/walsender.h"
+#include "storage/fd.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/conffiles.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
+
+#ifdef USE_LDAP
+#ifdef WIN32
+#include <winldap.h>
+#else
+#include <ldap.h>
+#endif
+#endif
+
+
+/* callback data for check_network_callback */
+typedef struct check_network_data
+{
+ IPCompareMethod method; /* test method */
+ SockAddr *raddr; /* client's actual address */
+ bool result; /* set to true if match */
+} check_network_data;
+
+typedef struct
+{
+ const char *filename;
+ int linenum;
+} tokenize_error_callback_arg;
+
+#define token_has_regexp(t) (t->regex != NULL)
+#define token_is_member_check(t) (!t->quoted && t->string[0] == '+')
+#define token_is_keyword(t, k) (!t->quoted && strcmp(t->string, k) == 0)
+#define token_matches(t, k) (strcmp(t->string, k) == 0)
+#define token_matches_insensitive(t,k) (pg_strcasecmp(t->string, k) == 0)
+
+/*
+ * Memory context holding the list of TokenizedAuthLines when parsing
+ * HBA or ident configuration files. This is created when opening the first
+ * file (depth of CONF_FILE_START_DEPTH).
+ */
+static MemoryContext tokenize_context = NULL;
+
+/*
+ * 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;
+
+/*
+ * pre-parsed content of ident mapping file: list of IdentLine structs.
+ * parsed_ident_context is the memory context where it lives.
+ */
+static List *parsed_ident_lines = NIL;
+static MemoryContext parsed_ident_context = NULL;
+
+/*
+ * The following character array represents the names of the authentication
+ * methods that are supported by PostgreSQL.
+ *
+ * Note: keep this in sync with the UserAuth enum in hba.h.
+ */
+static const char *const UserAuthName[] =
+{
+ "reject",
+ "implicit reject", /* Not a user-visible option */
+ "trust",
+ "ident",
+ "password",
+ "md5",
+ "scram-sha-256",
+ "gss",
+ "sspi",
+ "pam",
+ "bsd",
+ "ldap",
+ "cert",
+ "radius",
+ "peer"
+};
+
+/*
+ * Make sure UserAuthName[] tracks additions to the UserAuth enum
+ */
+StaticAssertDecl(lengthof(UserAuthName) == USER_AUTH_LAST + 1,
+ "UserAuthName[] must match the UserAuth enum");
+
+
+static List *tokenize_expand_file(List *tokens, const char *outer_filename,
+ const char *inc_filename, int elevel,
+ int depth, char **err_msg);
+static bool parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
+ int elevel, char **err_msg);
+static int regcomp_auth_token(AuthToken *token, char *filename, int line_num,
+ char **err_msg, int elevel);
+static int regexec_auth_token(const char *match, AuthToken *token,
+ size_t nmatch, regmatch_t pmatch[]);
+static void tokenize_error_callback(void *arg);
+
+
+/*
+ * 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 the string pointed to by *lineptr.
+ *
+ * Tokens are strings of non-blank characters bounded by blank characters,
+ * commas, beginning of line, and end of line. Blank means space or tab.
+ *
+ * Tokens can be delimited by double quotes (this allows the inclusion of
+ * commas, blanks, and '#', but not newlines). As in SQL, write two
+ * double-quotes to represent a double quote.
+ *
+ * Comments (started by an unquoted '#') are skipped, i.e. the remainder
+ * of the line is ignored.
+ *
+ * (Note that line continuation processing happens before tokenization.
+ * Thus, if a continuation occurs within quoted text or a comment, the
+ * quoted text or comment is considered to continue to the next line.)
+ *
+ * The token, if any, is returned into buf (replacing any previous
+ * contents), and *lineptr is advanced past the token.
+ *
+ * Also, we set *initial_quote to indicate whether there was quoting before
+ * 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, nor advanced over).
+ *
+ * The only possible error condition is lack of terminating quote, but we
+ * currently do not detect that, but just return the rest of the line.
+ *
+ * If successful: store dequoted token in buf and return true.
+ * If no more tokens on line: set buf to empty and return false.
+ */
+static bool
+next_token(char **lineptr, StringInfo buf,
+ bool *initial_quote, bool *terminating_comma)
+{
+ int c;
+ bool in_quote = false;
+ bool was_quote = false;
+ bool saw_quote = false;
+
+ /* Initialize output parameters */
+ resetStringInfo(buf);
+ *initial_quote = false;
+ *terminating_comma = false;
+
+ /* Move over any whitespace and commas preceding the next token */
+ while ((c = (*(*lineptr)++)) != '\0' && (pg_isblank(c) || c == ','))
+ ;
+
+ /*
+ * Build a token in buf of next characters up to EOL, unquoted comma, or
+ * unquoted whitespace.
+ */
+ while (c != '\0' &&
+ (!pg_isblank(c) || in_quote))
+ {
+ /* skip comments to EOL */
+ if (c == '#' && !in_quote)
+ {
+ while ((c = (*(*lineptr)++)) != '\0')
+ ;
+ break;
+ }
+
+ /* we do not pass back a terminating comma in the token */
+ if (c == ',' && !in_quote)
+ {
+ *terminating_comma = true;
+ break;
+ }
+
+ if (c != '"' || was_quote)
+ appendStringInfoChar(buf, c);
+
+ /* 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;
+ if (buf->len == 0)
+ *initial_quote = true;
+ }
+
+ c = *(*lineptr)++;
+ }
+
+ /*
+ * Un-eat the char right after the token (critical in case it is '\0',
+ * else next call will read past end of string).
+ */
+ (*lineptr)--;
+
+ return (saw_quote || buf->len > 0);
+}
+
+/*
+ * Construct a palloc'd AuthToken struct, copying the given string.
+ */
+static AuthToken *
+make_auth_token(const char *token, bool quoted)
+{
+ AuthToken *authtoken;
+ int toklen;
+
+ toklen = strlen(token);
+ /* we copy string into same palloc block as the struct */
+ authtoken = (AuthToken *) palloc0(sizeof(AuthToken) + toklen + 1);
+ authtoken->string = (char *) authtoken + sizeof(AuthToken);
+ authtoken->quoted = quoted;
+ authtoken->regex = NULL;
+ memcpy(authtoken->string, token, toklen + 1);
+
+ return authtoken;
+}
+
+/*
+ * Free an AuthToken, that may include a regular expression that needs
+ * to be cleaned up explicitly.
+ */
+static void
+free_auth_token(AuthToken *token)
+{
+ if (token_has_regexp(token))
+ pg_regfree(token->regex);
+}
+
+/*
+ * Copy a AuthToken struct into freshly palloc'd memory.
+ */
+static AuthToken *
+copy_auth_token(AuthToken *in)
+{
+ AuthToken *out = make_auth_token(in->string, in->quoted);
+
+ return out;
+}
+
+/*
+ * Compile the regular expression and store it in the AuthToken given in
+ * input. Returns the result of pg_regcomp(). On error, the details are
+ * stored in "err_msg".
+ */
+static int
+regcomp_auth_token(AuthToken *token, char *filename, int line_num,
+ char **err_msg, int elevel)
+{
+ pg_wchar *wstr;
+ int wlen;
+ int rc;
+
+ Assert(token->regex == NULL);
+
+ if (token->string[0] != '/')
+ return 0; /* nothing to compile */
+
+ token->regex = (regex_t *) palloc0(sizeof(regex_t));
+ wstr = palloc((strlen(token->string + 1) + 1) * sizeof(pg_wchar));
+ wlen = pg_mb2wchar_with_len(token->string + 1,
+ wstr, strlen(token->string + 1));
+
+ rc = pg_regcomp(token->regex, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+
+ if (rc)
+ {
+ char errstr[100];
+
+ pg_regerror(rc, token->regex, errstr, sizeof(errstr));
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+ errmsg("invalid regular expression \"%s\": %s",
+ token->string + 1, errstr),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, filename)));
+
+ *err_msg = psprintf("invalid regular expression \"%s\": %s",
+ token->string + 1, errstr);
+ }
+
+ pfree(wstr);
+ return rc;
+}
+
+/*
+ * Execute a regular expression computed in an AuthToken, checking for a match
+ * with the string specified in "match". The caller may optionally give an
+ * array to store the matches. Returns the result of pg_regexec().
+ */
+static int
+regexec_auth_token(const char *match, AuthToken *token, size_t nmatch,
+ regmatch_t pmatch[])
+{
+ pg_wchar *wmatchstr;
+ int wmatchlen;
+ int r;
+
+ Assert(token->string[0] == '/' && token->regex);
+
+ wmatchstr = palloc((strlen(match) + 1) * sizeof(pg_wchar));
+ wmatchlen = pg_mb2wchar_with_len(match, wmatchstr, strlen(match));
+
+ r = pg_regexec(token->regex, wmatchstr, wmatchlen, 0, NULL, nmatch, pmatch, 0);
+
+ pfree(wmatchstr);
+ return r;
+}
+
+/*
+ * Tokenize one HBA field from a line, handling file inclusion and comma lists.
+ *
+ * filename: current file's pathname (needed to resolve relative pathnames)
+ * *lineptr: current line pointer, which will be advanced past field
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error. Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
+ *
+ * The result is a List of AuthToken structs, one for each token in the field,
+ * or NIL if we reached EOL.
+ */
+static List *
+next_field_expand(const char *filename, char **lineptr,
+ int elevel, int depth, char **err_msg)
+{
+ StringInfoData buf;
+ bool trailing_comma;
+ bool initial_quote;
+ List *tokens = NIL;
+
+ initStringInfo(&buf);
+
+ do
+ {
+ if (!next_token(lineptr, &buf,
+ &initial_quote, &trailing_comma))
+ break;
+
+ /* Is this referencing a file? */
+ if (!initial_quote && buf.len > 1 && buf.data[0] == '@')
+ tokens = tokenize_expand_file(tokens, filename, buf.data + 1,
+ elevel, depth + 1, err_msg);
+ else
+ {
+ MemoryContext oldcxt;
+
+ /*
+ * lappend() may do its own allocations, so move to the context
+ * for the list of tokens.
+ */
+ oldcxt = MemoryContextSwitchTo(tokenize_context);
+ tokens = lappend(tokens, make_auth_token(buf.data, initial_quote));
+ MemoryContextSwitchTo(oldcxt);
+ }
+ } while (trailing_comma && (*err_msg == NULL));
+
+ pfree(buf.data);
+
+ return tokens;
+}
+
+/*
+ * tokenize_include_file
+ * Include a file from another file into an hba "field".
+ *
+ * Opens and tokenises a file included from another authentication file
+ * with one of the include records ("include", "include_if_exists" or
+ * "include_dir"), and assign all values found to an existing list of
+ * list of AuthTokens.
+ *
+ * All new tokens are allocated in the memory context dedicated to the
+ * tokenization, aka tokenize_context.
+ *
+ * If missing_ok is true, ignore a missing file.
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error. Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
+ */
+static void
+tokenize_include_file(const char *outer_filename,
+ const char *inc_filename,
+ List **tok_lines,
+ int elevel,
+ int depth,
+ bool missing_ok,
+ char **err_msg)
+{
+ char *inc_fullname;
+ FILE *inc_file;
+
+ inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
+ inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
+
+ if (!inc_file)
+ {
+ if (errno == ENOENT && missing_ok)
+ {
+ ereport(elevel,
+ (errmsg("skipping missing authentication file \"%s\"",
+ inc_fullname)));
+ *err_msg = NULL;
+ pfree(inc_fullname);
+ return;
+ }
+
+ /* error in err_msg, so leave and report */
+ pfree(inc_fullname);
+ Assert(err_msg);
+ return;
+ }
+
+ tokenize_auth_file(inc_fullname, inc_file, tok_lines, elevel,
+ depth);
+ free_auth_file(inc_file, depth);
+ pfree(inc_fullname);
+}
+
+/*
+ * tokenize_expand_file
+ * Expand a file included from another file into an hba "field"
+ *
+ * Opens and tokenises a file included from another HBA config file with @,
+ * and returns all values found therein as a flat list of AuthTokens. If a
+ * @-token or include record is found, recursively expand it. The newly
+ * read tokens are appended to "tokens" (so that foo,bar,@baz does what you
+ * expect). All new tokens are allocated in the memory context dedicated
+ * to the list of TokenizedAuthLines, aka tokenize_context.
+ *
+ * In event of an error, log a message at ereport level elevel, and also
+ * set *err_msg to a string describing the error. Note that the result
+ * may be non-NIL anyway, so *err_msg must be tested to determine whether
+ * there was an error.
+ */
+static List *
+tokenize_expand_file(List *tokens,
+ const char *outer_filename,
+ const char *inc_filename,
+ int elevel,
+ int depth,
+ char **err_msg)
+{
+ char *inc_fullname;
+ FILE *inc_file;
+ List *inc_lines = NIL;
+ ListCell *inc_line;
+
+ inc_fullname = AbsoluteConfigLocation(inc_filename, outer_filename);
+ inc_file = open_auth_file(inc_fullname, elevel, depth, err_msg);
+
+ if (inc_file == NULL)
+ {
+ /* error already logged */
+ pfree(inc_fullname);
+ return tokens;
+ }
+
+ /*
+ * There is possible recursion here if the file contains @ or an include
+ * record.
+ */
+ tokenize_auth_file(inc_fullname, inc_file, &inc_lines, elevel,
+ depth);
+
+ pfree(inc_fullname);
+
+ /*
+ * Move all the tokens found in the file to the tokens list. These are
+ * already saved in tokenize_context.
+ */
+ foreach(inc_line, inc_lines)
+ {
+ TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(inc_line);
+ ListCell *inc_field;
+
+ /* If any line has an error, propagate that up to caller */
+ if (tok_line->err_msg)
+ {
+ *err_msg = pstrdup(tok_line->err_msg);
+ break;
+ }
+
+ foreach(inc_field, tok_line->fields)
+ {
+ List *inc_tokens = lfirst(inc_field);
+ ListCell *inc_token;
+
+ foreach(inc_token, inc_tokens)
+ {
+ AuthToken *token = lfirst(inc_token);
+ MemoryContext oldcxt;
+
+ /*
+ * lappend() may do its own allocations, so move to the
+ * context for the list of tokens.
+ */
+ oldcxt = MemoryContextSwitchTo(tokenize_context);
+ tokens = lappend(tokens, token);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ }
+ }
+
+ free_auth_file(inc_file, depth);
+ return tokens;
+}
+
+/*
+ * free_auth_file
+ * Free a file opened by open_auth_file().
+ */
+void
+free_auth_file(FILE *file, int depth)
+{
+ FreeFile(file);
+
+ /* If this is the last cleanup, remove the tokenization context */
+ if (depth == CONF_FILE_START_DEPTH)
+ {
+ MemoryContextDelete(tokenize_context);
+ tokenize_context = NULL;
+ }
+}
+
+/*
+ * open_auth_file
+ * Open the given file.
+ *
+ * filename: the absolute path to the target file
+ * elevel: message logging level
+ * depth: recursion level when opening the file
+ * err_msg: details about the error
+ *
+ * Return value is the opened file. On error, returns NULL with details
+ * about the error stored in "err_msg".
+ */
+FILE *
+open_auth_file(const char *filename, int elevel, int depth,
+ char **err_msg)
+{
+ FILE *file;
+
+ /*
+ * Reject too-deep include nesting depth. This is just a safety check to
+ * avoid dumping core due to stack overflow if an include file loops back
+ * to itself. The maximum nesting depth is pretty arbitrary.
+ */
+ if (depth > CONF_FILE_MAX_DEPTH)
+ {
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": maximum nesting depth exceeded",
+ filename)));
+ if (err_msg)
+ *err_msg = psprintf("could not open file \"%s\": maximum nesting depth exceeded",
+ filename);
+ return NULL;
+ }
+
+ file = AllocateFile(filename, "r");
+ if (file == NULL)
+ {
+ int save_errno = errno;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\": %m",
+ filename)));
+ if (err_msg)
+ *err_msg = psprintf("could not open file \"%s\": %s",
+ filename, strerror(save_errno));
+ /* the caller may care about some specific errno */
+ errno = save_errno;
+ return NULL;
+ }
+
+ /*
+ * When opening the top-level file, create the memory context used for the
+ * tokenization. This will be closed with this file when coming back to
+ * this level of cleanup.
+ */
+ if (depth == CONF_FILE_START_DEPTH)
+ {
+ /*
+ * A context may be present, but assume that it has been eliminated
+ * already.
+ */
+ tokenize_context = AllocSetContextCreate(CurrentMemoryContext,
+ "tokenize_context",
+ ALLOCSET_START_SMALL_SIZES);
+ }
+
+ return file;
+}
+
+/*
+ * error context callback for tokenize_auth_file()
+ */
+static void
+tokenize_error_callback(void *arg)
+{
+ tokenize_error_callback_arg *callback_arg = (tokenize_error_callback_arg *) arg;
+
+ errcontext("line %d of configuration file \"%s\"",
+ callback_arg->linenum, callback_arg->filename);
+}
+
+/*
+ * tokenize_auth_file
+ * Tokenize the given file.
+ *
+ * The output is a list of TokenizedAuthLine structs; see the struct definition
+ * in libpq/hba.h. This is the central piece in charge of parsing the
+ * authentication files. All the operations of this function happen in its own
+ * local memory context, easing the cleanup of anything allocated here. This
+ * matters a lot when reloading authentication files in the postmaster.
+ *
+ * filename: the absolute path to the target file
+ * file: the already-opened target file
+ * tok_lines: receives output list, saved into tokenize_context
+ * elevel: message logging level
+ * depth: level of recursion when tokenizing the target file
+ *
+ * Errors are reported by logging messages at ereport level elevel and by
+ * adding TokenizedAuthLine structs containing non-null err_msg fields to the
+ * output list.
+ */
+void
+tokenize_auth_file(const char *filename, FILE *file, List **tok_lines,
+ int elevel, int depth)
+{
+ int line_number = 1;
+ StringInfoData buf;
+ MemoryContext linecxt;
+ MemoryContext funccxt; /* context of this function's caller */
+ ErrorContextCallback tokenerrcontext;
+ tokenize_error_callback_arg callback_arg;
+
+ Assert(tokenize_context);
+
+ callback_arg.filename = filename;
+ callback_arg.linenum = line_number;
+
+ tokenerrcontext.callback = tokenize_error_callback;
+ tokenerrcontext.arg = (void *) &callback_arg;
+ tokenerrcontext.previous = error_context_stack;
+ error_context_stack = &tokenerrcontext;
+
+ /*
+ * Do all the local tokenization in its own context, to ease the cleanup
+ * of any memory allocated while tokenizing.
+ */
+ linecxt = AllocSetContextCreate(CurrentMemoryContext,
+ "tokenize_auth_file",
+ ALLOCSET_SMALL_SIZES);
+ funccxt = MemoryContextSwitchTo(linecxt);
+
+ initStringInfo(&buf);
+
+ if (depth == CONF_FILE_START_DEPTH)
+ *tok_lines = NIL;
+
+ while (!feof(file) && !ferror(file))
+ {
+ TokenizedAuthLine *tok_line;
+ MemoryContext oldcxt;
+ char *lineptr;
+ List *current_line = NIL;
+ char *err_msg = NULL;
+ int last_backslash_buflen = 0;
+ int continuations = 0;
+
+ /* Collect the next input line, handling backslash continuations */
+ resetStringInfo(&buf);
+
+ while (pg_get_line_append(file, &buf, NULL))
+ {
+ /* Strip trailing newline, including \r in case we're on Windows */
+ buf.len = pg_strip_crlf(buf.data);
+
+ /*
+ * Check for backslash continuation. The backslash must be after
+ * the last place we found a continuation, else two backslashes
+ * followed by two \n's would behave surprisingly.
+ */
+ if (buf.len > last_backslash_buflen &&
+ buf.data[buf.len - 1] == '\\')
+ {
+ /* Continuation, so strip it and keep reading */
+ buf.data[--buf.len] = '\0';
+ last_backslash_buflen = buf.len;
+ continuations++;
+ continue;
+ }
+
+ /* Nope, so we have the whole line */
+ break;
+ }
+
+ if (ferror(file))
+ {
+ /* I/O error! */
+ int save_errno = errno;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m", filename)));
+ err_msg = psprintf("could not read file \"%s\": %s",
+ filename, strerror(save_errno));
+ break;
+ }
+
+ /* Parse fields */
+ lineptr = buf.data;
+ while (*lineptr && err_msg == NULL)
+ {
+ List *current_field;
+
+ current_field = next_field_expand(filename, &lineptr,
+ elevel, depth, &err_msg);
+ /* add field to line, unless we are at EOL or comment start */
+ if (current_field != NIL)
+ {
+ /*
+ * lappend() may do its own allocations, so move to the
+ * context for the list of tokens.
+ */
+ oldcxt = MemoryContextSwitchTo(tokenize_context);
+ current_line = lappend(current_line, current_field);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ }
+
+ /*
+ * Reached EOL; no need to emit line to TokenizedAuthLine list if it's
+ * boring.
+ */
+ if (current_line == NIL && err_msg == NULL)
+ goto next_line;
+
+ /* If the line is valid, check if that's an include directive */
+ if (err_msg == NULL && list_length(current_line) == 2)
+ {
+ AuthToken *first,
+ *second;
+
+ first = linitial(linitial_node(List, current_line));
+ second = linitial(lsecond_node(List, current_line));
+
+ if (strcmp(first->string, "include") == 0)
+ {
+ tokenize_include_file(filename, second->string, tok_lines,
+ elevel, depth + 1, false, &err_msg);
+
+ if (err_msg)
+ goto process_line;
+
+ /*
+ * tokenize_auth_file() has taken care of creating the
+ * TokenizedAuthLines.
+ */
+ goto next_line;
+ }
+ else if (strcmp(first->string, "include_dir") == 0)
+ {
+ char **filenames;
+ char *dir_name = second->string;
+ int num_filenames;
+ StringInfoData err_buf;
+
+ filenames = GetConfFilesInDir(dir_name, filename, elevel,
+ &num_filenames, &err_msg);
+
+ if (!filenames)
+ {
+ /* the error is in err_msg, so create an entry */
+ goto process_line;
+ }
+
+ initStringInfo(&err_buf);
+ for (int i = 0; i < num_filenames; i++)
+ {
+ tokenize_include_file(filename, filenames[i], tok_lines,
+ elevel, depth + 1, false, &err_msg);
+ /* cumulate errors if any */
+ if (err_msg)
+ {
+ if (err_buf.len > 0)
+ appendStringInfoChar(&err_buf, '\n');
+ appendStringInfoString(&err_buf, err_msg);
+ }
+ }
+
+ /* clean up things */
+ for (int i = 0; i < num_filenames; i++)
+ pfree(filenames[i]);
+ pfree(filenames);
+
+ /*
+ * If there were no errors, the line is fully processed,
+ * bypass the general TokenizedAuthLine processing.
+ */
+ if (err_buf.len == 0)
+ goto next_line;
+
+ /* Otherwise, process the cumulated errors, if any. */
+ err_msg = err_buf.data;
+ goto process_line;
+ }
+ else if (strcmp(first->string, "include_if_exists") == 0)
+ {
+
+ tokenize_include_file(filename, second->string, tok_lines,
+ elevel, depth + 1, true, &err_msg);
+ if (err_msg)
+ goto process_line;
+
+ /*
+ * tokenize_auth_file() has taken care of creating the
+ * TokenizedAuthLines.
+ */
+ goto next_line;
+ }
+ }
+
+process_line:
+
+ /*
+ * General processing: report the error if any and emit line to the
+ * TokenizedAuthLine. This is saved in the memory context dedicated
+ * to this list.
+ */
+ oldcxt = MemoryContextSwitchTo(tokenize_context);
+ tok_line = (TokenizedAuthLine *) palloc0(sizeof(TokenizedAuthLine));
+ tok_line->fields = current_line;
+ tok_line->file_name = pstrdup(filename);
+ tok_line->line_num = line_number;
+ tok_line->raw_line = pstrdup(buf.data);
+ tok_line->err_msg = err_msg ? pstrdup(err_msg) : NULL;
+ *tok_lines = lappend(*tok_lines, tok_line);
+ MemoryContextSwitchTo(oldcxt);
+
+next_line:
+ line_number += continuations + 1;
+ callback_arg.linenum = line_number;
+ }
+
+ MemoryContextSwitchTo(funccxt);
+ MemoryContextDelete(linecxt);
+
+ error_context_stack = tokenerrcontext.previous;
+}
+
+
+/*
+ * Does user belong to role?
+ *
+ * userid is the OID of the role given as the attempted login identifier.
+ * We check to see if it is a member of the specified role name.
+ */
+static bool
+is_member(Oid userid, const char *role)
+{
+ Oid roleid;
+
+ if (!OidIsValid(userid))
+ return false; /* if user not exist, say "no" */
+
+ roleid = get_role_oid(role, true);
+
+ if (!OidIsValid(roleid))
+ return false; /* if target role not exist, say "no" */
+
+ /*
+ * See if user is directly or indirectly a member of role. For this
+ * purpose, a superuser is not considered to be automatically a member of
+ * the role, so group auth only applies to explicit membership.
+ */
+ return is_member_of_role_nosuper(userid, roleid);
+}
+
+/*
+ * Check AuthToken list for a match to role, allowing group names.
+ *
+ * Each AuthToken listed is checked one-by-one. Keywords are processed
+ * first (these cannot have regular expressions), followed by regular
+ * expressions (if any), the case-insensitive match (if requested) and
+ * the exact match.
+ */
+static bool
+check_role(const char *role, Oid roleid, List *tokens, bool case_insensitive)
+{
+ ListCell *cell;
+ AuthToken *tok;
+
+ foreach(cell, tokens)
+ {
+ tok = lfirst(cell);
+ if (token_is_member_check(tok))
+ {
+ if (is_member(roleid, tok->string + 1))
+ return true;
+ }
+ else if (token_is_keyword(tok, "all"))
+ return true;
+ else if (token_has_regexp(tok))
+ {
+ if (regexec_auth_token(role, tok, 0, NULL) == REG_OKAY)
+ return true;
+ }
+ else if (case_insensitive)
+ {
+ if (token_matches_insensitive(tok, role))
+ return true;
+ }
+ else if (token_matches(tok, role))
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Check to see if db/role combination matches AuthToken list.
+ *
+ * Each AuthToken listed is checked one-by-one. Keywords are checked
+ * first (these cannot have regular expressions), followed by regular
+ * expressions (if any) and the exact match.
+ */
+static bool
+check_db(const char *dbname, const char *role, Oid roleid, List *tokens)
+{
+ ListCell *cell;
+ AuthToken *tok;
+
+ foreach(cell, tokens)
+ {
+ tok = lfirst(cell);
+ if (am_walsender && !am_db_walsender)
+ {
+ /*
+ * physical replication 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_has_regexp(tok))
+ {
+ if (regexec_auth_token(dbname, tok, 0, NULL) == REG_OKAY)
+ return true;
+ }
+ else if (token_matches(tok, dbname))
+ return true;
+ }
+ return false;
+}
+
+static bool
+ipv4eq(struct sockaddr_in *a, struct sockaddr_in *b)
+{
+ return (a->sin_addr.s_addr == b->sin_addr.s_addr);
+}
+
+static bool
+ipv6eq(struct sockaddr_in6 *a, struct sockaddr_in6 *b)
+{
+ int i;
+
+ for (i = 0; i < 16; i++)
+ if (a->sin6_addr.s6_addr[i] != b->sin6_addr.s6_addr[i])
+ return false;
+
+ return true;
+}
+
+/*
+ * Check whether host name matches pattern.
+ */
+static bool
+hostname_match(const char *pattern, const char *actual_hostname)
+{
+ if (pattern[0] == '.') /* suffix match */
+ {
+ size_t plen = strlen(pattern);
+ size_t hlen = strlen(actual_hostname);
+
+ if (hlen < plen)
+ return false;
+
+ return (pg_strcasecmp(pattern, actual_hostname + (hlen - plen)) == 0);
+ }
+ else
+ return (pg_strcasecmp(pattern, actual_hostname) == 0);
+}
+
+/*
+ * Check to see if a connecting IP matches a given host name.
+ */
+static bool
+check_hostname(hbaPort *port, const char *hostname)
+{
+ struct addrinfo *gai_result,
+ *gai;
+ int ret;
+ bool found;
+
+ /* Quick out if remote host name already known bad */
+ if (port->remote_hostname_resolv < 0)
+ return false;
+
+ /* Lookup remote host name if not already done */
+ if (!port->remote_hostname)
+ {
+ char remote_hostname[NI_MAXHOST];
+
+ ret = pg_getnameinfo_all(&port->raddr.addr, port->raddr.salen,
+ remote_hostname, sizeof(remote_hostname),
+ NULL, 0,
+ NI_NAMEREQD);
+ if (ret != 0)
+ {
+ /* remember failure; don't complain in the postmaster log yet */
+ port->remote_hostname_resolv = -2;
+ port->remote_hostname_errcode = ret;
+ return false;
+ }
+
+ port->remote_hostname = pstrdup(remote_hostname);
+ }
+
+ /* Now see if remote host name matches this pg_hba line */
+ if (!hostname_match(hostname, port->remote_hostname))
+ return false;
+
+ /* If we already verified the forward lookup, we're done */
+ if (port->remote_hostname_resolv == +1)
+ return true;
+
+ /* Lookup IP from host name and check against original IP */
+ ret = getaddrinfo(port->remote_hostname, NULL, NULL, &gai_result);
+ if (ret != 0)
+ {
+ /* remember failure; don't complain in the postmaster log yet */
+ port->remote_hostname_resolv = -2;
+ port->remote_hostname_errcode = ret;
+ return false;
+ }
+
+ found = false;
+ for (gai = gai_result; gai; gai = gai->ai_next)
+ {
+ if (gai->ai_addr->sa_family == port->raddr.addr.ss_family)
+ {
+ if (gai->ai_addr->sa_family == AF_INET)
+ {
+ if (ipv4eq((struct sockaddr_in *) gai->ai_addr,
+ (struct sockaddr_in *) &port->raddr.addr))
+ {
+ found = true;
+ break;
+ }
+ }
+ else if (gai->ai_addr->sa_family == AF_INET6)
+ {
+ if (ipv6eq((struct sockaddr_in6 *) gai->ai_addr,
+ (struct sockaddr_in6 *) &port->raddr.addr))
+ {
+ found = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (gai_result)
+ freeaddrinfo(gai_result);
+
+ if (!found)
+ elog(DEBUG2, "pg_hba.conf host name \"%s\" rejected because address resolution did not return a match with IP address of client",
+ hostname);
+
+ port->remote_hostname_resolv = found ? +1 : -1;
+
+ return found;
+}
+
+/*
+ * Check to see if a connecting IP matches the given address and netmask.
+ */
+static bool
+check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask)
+{
+ if (raddr->addr.ss_family == addr->sa_family &&
+ pg_range_sockaddr(&raddr->addr,
+ (struct sockaddr_storage *) addr,
+ (struct sockaddr_storage *) mask))
+ return true;
+ return false;
+}
+
+/*
+ * pg_foreach_ifaddr callback: does client addr match this machine interface?
+ */
+static void
+check_network_callback(struct sockaddr *addr, struct sockaddr *netmask,
+ void *cb_data)
+{
+ check_network_data *cn = (check_network_data *) cb_data;
+ struct sockaddr_storage mask;
+
+ /* Already found a match? */
+ if (cn->result)
+ return;
+
+ if (cn->method == ipCmpSameHost)
+ {
+ /* Make an all-ones netmask of appropriate length for family */
+ pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family);
+ cn->result = check_ip(cn->raddr, addr, (struct sockaddr *) &mask);
+ }
+ else
+ {
+ /* Use the netmask of the interface itself */
+ cn->result = check_ip(cn->raddr, addr, netmask);
+ }
+}
+
+/*
+ * Use pg_foreach_ifaddr to check a samehost or samenet match
+ */
+static bool
+check_same_host_or_net(SockAddr *raddr, IPCompareMethod method)
+{
+ check_network_data cn;
+
+ cn.method = method;
+ cn.raddr = raddr;
+ cn.result = false;
+
+ errno = 0;
+ if (pg_foreach_ifaddr(check_network_callback, &cn) < 0)
+ {
+ ereport(LOG,
+ (errmsg("error enumerating network interfaces: %m")));
+ return false;
+ }
+
+ return cn.result;
+}
+
+
+/*
+ * Macros used to check and report on invalid configuration options.
+ * On error: log a message at level elevel, set *err_msg, and exit the function.
+ * These macros are not as general-purpose as they look, because they know
+ * what the calling function's error-exit value is.
+ *
+ * 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(elevel, \
+ (errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ /* translator: the second %s is a list of auth methods */ \
+ errmsg("authentication option \"%s\" is only valid for authentication methods %s", \
+ optname, _(validmethods)), \
+ errcontext("line %d of configuration file \"%s\"", \
+ line_num, file_name))); \
+ *err_msg = psprintf("authentication option \"%s\" is only valid for authentication methods %s", \
+ optname, validmethods); \
+ return false; \
+} while (0)
+
+#define REQUIRE_AUTH_OPTION(methodval, optname, validmethods) \
+do { \
+ if (hbaline->auth_method != methodval) \
+ INVALID_AUTH_OPTION(optname, validmethods); \
+} while (0)
+
+#define MANDATORY_AUTH_ARG(argvar, argname, authname) \
+do { \
+ if (argvar == NULL) { \
+ ereport(elevel, \
+ (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, file_name))); \
+ *err_msg = psprintf("authentication method \"%s\" requires argument \"%s\" to be set", \
+ authname, argname); \
+ return NULL; \
+ } \
+} while (0)
+
+/*
+ * Macros for handling pg_ident problems, similar as above.
+ *
+ * IDENT_FIELD_ABSENT:
+ * Reports when the given ident field ListCell is not populated.
+ *
+ * IDENT_MULTI_VALUE:
+ * Reports when the given ident token List has more than one element.
+ */
+#define IDENT_FIELD_ABSENT(field) \
+do { \
+ if (!field) { \
+ ereport(elevel, \
+ (errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ errmsg("missing entry at end of line"), \
+ errcontext("line %d of configuration file \"%s\"", \
+ line_num, file_name))); \
+ *err_msg = pstrdup("missing entry at end of line"); \
+ return NULL; \
+ } \
+} while (0)
+
+#define IDENT_MULTI_VALUE(tokens) \
+do { \
+ if (tokens->length > 1) { \
+ ereport(elevel, \
+ (errcode(ERRCODE_CONFIG_FILE_ERROR), \
+ errmsg("multiple values in ident field"), \
+ errcontext("line %d of configuration file \"%s\"", \
+ line_num, file_name))); \
+ *err_msg = pstrdup("multiple values in ident field"); \
+ return NULL; \
+ } \
+} while (0)
+
+
+/*
+ * Parse one tokenised line from the hba config file and store the result in a
+ * HbaLine structure.
+ *
+ * If parsing fails, log a message at ereport level elevel, store an error
+ * string in tok_line->err_msg, and return NULL. (Some non-error conditions
+ * can also result in such messages.)
+ *
+ * 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.
+ */
+HbaLine *
+parse_hba_line(TokenizedAuthLine *tok_line, int elevel)
+{
+ int line_num = tok_line->line_num;
+ char *file_name = tok_line->file_name;
+ char **err_msg = &tok_line->err_msg;
+ char *str;
+ struct addrinfo *gai_result;
+ struct addrinfo hints;
+ int ret;
+ char *cidr_slash;
+ char *unsupauth;
+ ListCell *field;
+ List *tokens;
+ ListCell *tokencell;
+ AuthToken *token;
+ HbaLine *parsedline;
+
+ parsedline = palloc0(sizeof(HbaLine));
+ parsedline->sourcefile = pstrdup(file_name);
+ parsedline->linenumber = line_num;
+ parsedline->rawline = pstrdup(tok_line->raw_line);
+
+ /* Check the record type. */
+ Assert(tok_line->fields != NIL);
+ field = list_head(tok_line->fields);
+ tokens = lfirst(field);
+ if (tokens->length > 1)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = "multiple values specified for connection type";
+ return NULL;
+ }
+ token = linitial(tokens);
+ if (strcmp(token->string, "local") == 0)
+ {
+ parsedline->conntype = ctLocal;
+ }
+ else if (strcmp(token->string, "host") == 0 ||
+ strcmp(token->string, "hostssl") == 0 ||
+ strcmp(token->string, "hostnossl") == 0 ||
+ strcmp(token->string, "hostgssenc") == 0 ||
+ strcmp(token->string, "hostnogssenc") == 0)
+ {
+
+ if (token->string[4] == 's') /* "hostssl" */
+ {
+ parsedline->conntype = ctHostSSL;
+ /* Log a warning if SSL support is not active */
+#ifdef USE_SSL
+ if (!EnableSSL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostssl record cannot match because SSL is disabled"),
+ errhint("Set ssl = on in postgresql.conf."),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "hostssl record cannot match because SSL is disabled";
+ }
+#else
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostssl record cannot match because SSL is not supported by this build"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "hostssl record cannot match because SSL is not supported by this build";
+#endif
+ }
+ else if (token->string[4] == 'g') /* "hostgssenc" */
+ {
+ parsedline->conntype = ctHostGSS;
+#ifndef ENABLE_GSS
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("hostgssenc record cannot match because GSSAPI is not supported by this build"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "hostgssenc record cannot match because GSSAPI is not supported by this build";
+#endif
+ }
+ else if (token->string[4] == 'n' && token->string[6] == 's')
+ parsedline->conntype = ctHostNoSSL;
+ else if (token->string[4] == 'n' && token->string[6] == 'g')
+ parsedline->conntype = ctHostNoGSS;
+ else
+ {
+ /* "host" */
+ parsedline->conntype = ctHost;
+ }
+ } /* record type */
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid connection type \"%s\"",
+ token->string),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("invalid connection type \"%s\"", token->string);
+ return NULL;
+ }
+
+ /* Get the databases. */
+ field = lnext(tok_line->fields, field);
+ if (!field)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("end-of-line before database specification"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "end-of-line before database specification";
+ return NULL;
+ }
+ parsedline->databases = NIL;
+ tokens = lfirst(field);
+ foreach(tokencell, tokens)
+ {
+ AuthToken *tok = copy_auth_token(lfirst(tokencell));
+
+ /* Compile a regexp for the database token, if necessary */
+ if (regcomp_auth_token(tok, file_name, line_num, err_msg, elevel))
+ return NULL;
+
+ parsedline->databases = lappend(parsedline->databases, tok);
+ }
+
+ /* Get the roles. */
+ field = lnext(tok_line->fields, field);
+ if (!field)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("end-of-line before role specification"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "end-of-line before role specification";
+ return NULL;
+ }
+ parsedline->roles = NIL;
+ tokens = lfirst(field);
+ foreach(tokencell, tokens)
+ {
+ AuthToken *tok = copy_auth_token(lfirst(tokencell));
+
+ /* Compile a regexp from the role token, if necessary */
+ if (regcomp_auth_token(tok, file_name, line_num, err_msg, elevel))
+ return NULL;
+
+ parsedline->roles = lappend(parsedline->roles, tok);
+ }
+
+ if (parsedline->conntype != ctLocal)
+ {
+ /* Read the IP address field. (with or without CIDR netmask) */
+ field = lnext(tok_line->fields, field);
+ if (!field)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("end-of-line before IP address specification"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "end-of-line before IP address specification";
+ return NULL;
+ }
+ tokens = lfirst(field);
+ if (tokens->length > 1)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = "multiple values specified for host address";
+ 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;
+ }
+ else
+ {
+ /* IP and netmask are specified */
+ 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';
+
+ /* Get the IP address either way */
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_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(str, NULL, &hints, &gai_result);
+ if (ret == 0 && gai_result)
+ {
+ memcpy(&parsedline->addr, gai_result->ai_addr,
+ gai_result->ai_addrlen);
+ parsedline->addrlen = gai_result->ai_addrlen;
+ }
+ else if (ret == EAI_NONAME)
+ parsedline->hostname = str;
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid IP address \"%s\": %s",
+ str, gai_strerror(ret)),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("invalid IP address \"%s\": %s",
+ str, gai_strerror(ret));
+ if (gai_result)
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ return NULL;
+ }
+
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+
+ /* Get the netmask */
+ if (cidr_slash)
+ {
+ if (parsedline->hostname)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = psprintf("specifying both host name and CIDR mask is invalid: \"%s\"",
+ token->string);
+ return NULL;
+ }
+
+ if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1,
+ parsedline->addr.ss_family) < 0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid CIDR mask in address \"%s\"",
+ token->string),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("invalid CIDR mask in address \"%s\"",
+ token->string);
+ return NULL;
+ }
+ parsedline->masklen = parsedline->addrlen;
+ pfree(str);
+ }
+ else if (!parsedline->hostname)
+ {
+ /* Read the mask field. */
+ pfree(str);
+ field = lnext(tok_line->fields, field);
+ if (!field)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = "end-of-line before netmask specification";
+ return NULL;
+ }
+ tokens = lfirst(field);
+ if (tokens->length > 1)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("multiple values specified for netmask"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "multiple values specified for netmask";
+ return NULL;
+ }
+ token = linitial(tokens);
+
+ ret = pg_getaddrinfo_all(token->string, NULL,
+ &hints, &gai_result);
+ if (ret || !gai_result)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = psprintf("invalid IP mask \"%s\": %s",
+ token->string, gai_strerror(ret));
+ if (gai_result)
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ return NULL;
+ }
+
+ memcpy(&parsedline->mask, gai_result->ai_addr,
+ gai_result->ai_addrlen);
+ parsedline->masklen = gai_result->ai_addrlen;
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+
+ if (parsedline->addr.ss_family != parsedline->mask.ss_family)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("IP address and mask do not match"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "IP address and mask do not match";
+ return NULL;
+ }
+ }
+ }
+ } /* != ctLocal */
+
+ /* Get the authentication method */
+ field = lnext(tok_line->fields, field);
+ if (!field)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("end-of-line before authentication method"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "end-of-line before authentication method";
+ return NULL;
+ }
+ tokens = lfirst(field);
+ if (tokens->length > 1)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = "multiple values specified for authentication type";
+ 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, "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)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("MD5 authentication is not supported when \"db_user_namespace\" is enabled"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "MD5 authentication is not supported when \"db_user_namespace\" is enabled";
+ return NULL;
+ }
+ parsedline->auth_method = uaMD5;
+ }
+ else if (strcmp(token->string, "scram-sha-256") == 0)
+ parsedline->auth_method = uaSCRAM;
+ else if (strcmp(token->string, "pam") == 0)
+#ifdef USE_PAM
+ parsedline->auth_method = uaPAM;
+#else
+ unsupauth = "pam";
+#endif
+ else if (strcmp(token->string, "bsd") == 0)
+#ifdef USE_BSD_AUTH
+ parsedline->auth_method = uaBSD;
+#else
+ unsupauth = "bsd";
+#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(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid authentication method \"%s\"",
+ token->string),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("invalid authentication method \"%s\"",
+ token->string);
+ return NULL;
+ }
+
+ if (unsupauth)
+ {
+ ereport(elevel,
+ (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, file_name)));
+ *err_msg = psprintf("invalid authentication method \"%s\": not supported by this build",
+ token->string);
+ return NULL;
+ }
+
+ /*
+ * XXX: When using ident on local connections, change it to peer, for
+ * backwards compatibility.
+ */
+ if (parsedline->conntype == ctLocal &&
+ parsedline->auth_method == uaIdent)
+ parsedline->auth_method = uaPeer;
+
+ /* Invalid authentication combinations */
+ if (parsedline->conntype == ctLocal &&
+ parsedline->auth_method == uaGSS)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("gssapi authentication is not supported on local sockets"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "gssapi authentication is not supported on local sockets";
+ return NULL;
+ }
+
+ if (parsedline->conntype != ctLocal &&
+ parsedline->auth_method == uaPeer)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("peer authentication is only supported on local sockets"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "peer authentication is only supported on local sockets";
+ 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)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("cert authentication is only supported on hostssl connections"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "cert authentication is only supported on hostssl connections";
+ return NULL;
+ }
+
+ /*
+ * For GSS and SSPI, set the default value of include_realm to true.
+ * Having include_realm set to false is dangerous in multi-realm
+ * situations and is generally considered bad practice. We keep the
+ * capability around for backwards compatibility, but we might want to
+ * remove it at some point in the future. Users who still need to strip
+ * the realm off would be better served by using an appropriate regex in a
+ * pg_ident.conf mapping.
+ */
+ if (parsedline->auth_method == uaGSS ||
+ parsedline->auth_method == uaSSPI)
+ parsedline->include_realm = true;
+
+ /*
+ * For SSPI, include_realm defaults to the SAM-compatible domain (aka
+ * NetBIOS name) and user names instead of the Kerberos principal name for
+ * compatibility.
+ */
+ if (parsedline->auth_method == uaSSPI)
+ {
+ parsedline->compat_realm = true;
+ parsedline->upn_username = false;
+ }
+
+ /* Parse remaining arguments */
+ while ((field = lnext(tok_line->fields, 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(elevel,
+ (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, file_name)));
+ *err_msg = psprintf("authentication option not in name=value format: %s",
+ token->string);
+ return NULL;
+ }
+
+ *val++ = '\0'; /* str now holds "name", val holds "value" */
+ if (!parse_hba_auth_opt(str, val, parsedline, elevel, err_msg))
+ /* parse_hba_auth_opt already logged the error message */
+ return NULL;
+ pfree(str);
+ }
+ }
+
+ /*
+ * Check if the selected authentication method has any mandatory arguments
+ * that are not set.
+ */
+ if (parsedline->auth_method == uaLDAP)
+ {
+#ifndef HAVE_LDAP_INITIALIZE
+ /* Not mandatory for OpenLDAP, because it can use DNS SRV records */
+ MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap");
+#endif
+
+ /*
+ * LDAP can operate in two modes: either with a direct bind, using
+ * ldapprefix and ldapsuffix, or using a search+bind, using
+ * ldapbasedn, ldapbinddn, ldapbindpasswd and one of
+ * ldapsearchattribute or ldapsearchfilter. Disallow mixing these
+ * parameters.
+ */
+ if (parsedline->ldapprefix || parsedline->ldapsuffix)
+ {
+ if (parsedline->ldapbasedn ||
+ parsedline->ldapbinddn ||
+ parsedline->ldapbindpasswd ||
+ parsedline->ldapsearchattribute ||
+ parsedline->ldapsearchfilter)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "cannot use ldapbasedn, ldapbinddn, ldapbindpasswd, ldapsearchattribute, ldapsearchfilter, or ldapurl together with ldapprefix";
+ return NULL;
+ }
+ }
+ else if (!parsedline->ldapbasedn)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "authentication method \"ldap\" requires argument \"ldapbasedn\", \"ldapprefix\", or \"ldapsuffix\" to be set";
+ return NULL;
+ }
+
+ /*
+ * When using search+bind, you can either use a simple attribute
+ * (defaulting to "uid") or a fully custom search filter. You can't
+ * do both.
+ */
+ if (parsedline->ldapsearchattribute && parsedline->ldapsearchfilter)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("cannot use ldapsearchattribute together with ldapsearchfilter"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "cannot use ldapsearchattribute together with ldapsearchfilter";
+ return NULL;
+ }
+ }
+
+ if (parsedline->auth_method == uaRADIUS)
+ {
+ MANDATORY_AUTH_ARG(parsedline->radiusservers, "radiusservers", "radius");
+ MANDATORY_AUTH_ARG(parsedline->radiussecrets, "radiussecrets", "radius");
+
+ if (parsedline->radiusservers == NIL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("list of RADIUS servers cannot be empty"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "list of RADIUS servers cannot be empty";
+ return NULL;
+ }
+
+ if (parsedline->radiussecrets == NIL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("list of RADIUS secrets cannot be empty"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "list of RADIUS secrets cannot be empty";
+ return NULL;
+ }
+
+ /*
+ * Verify length of option lists - each can be 0 (except for secrets,
+ * but that's already checked above), 1 (use the same value
+ * everywhere) or the same as the number of servers.
+ */
+ if (!(list_length(parsedline->radiussecrets) == 1 ||
+ list_length(parsedline->radiussecrets) == list_length(parsedline->radiusservers)))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+ list_length(parsedline->radiussecrets),
+ list_length(parsedline->radiusservers)),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("the number of RADIUS secrets (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+ list_length(parsedline->radiussecrets),
+ list_length(parsedline->radiusservers));
+ return NULL;
+ }
+ if (!(list_length(parsedline->radiusports) == 0 ||
+ list_length(parsedline->radiusports) == 1 ||
+ list_length(parsedline->radiusports) == list_length(parsedline->radiusservers)))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+ list_length(parsedline->radiusports),
+ list_length(parsedline->radiusservers)),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("the number of RADIUS ports (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+ list_length(parsedline->radiusports),
+ list_length(parsedline->radiusservers));
+ return NULL;
+ }
+ if (!(list_length(parsedline->radiusidentifiers) == 0 ||
+ list_length(parsedline->radiusidentifiers) == 1 ||
+ list_length(parsedline->radiusidentifiers) == list_length(parsedline->radiusservers)))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+ list_length(parsedline->radiusidentifiers),
+ list_length(parsedline->radiusservers)),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("the number of RADIUS identifiers (%d) must be 1 or the same as the number of RADIUS servers (%d)",
+ list_length(parsedline->radiusidentifiers),
+ list_length(parsedline->radiusservers));
+ return NULL;
+ }
+ }
+
+ /*
+ * Enforce any parameters implied by other settings.
+ */
+ if (parsedline->auth_method == uaCert)
+ {
+ /*
+ * For auth method cert, client certificate validation is mandatory,
+ * and it implies the level of verify-full.
+ */
+ parsedline->clientcert = clientCertFull;
+ }
+
+ 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. In the event of an error, also log a message at
+ * ereport level elevel, and store a message string into *err_msg.
+ */
+static bool
+parse_hba_auth_opt(char *name, char *val, HbaLine *hbaline,
+ int elevel, char **err_msg)
+{
+ int line_num = hbaline->linenumber;
+ char *file_name = hbaline->sourcefile;
+
+#ifdef USE_LDAP
+ hbaline->ldapscope = LDAP_SCOPE_SUBTREE;
+#endif
+
+ if (strcmp(name, "map") == 0)
+ {
+ if (hbaline->auth_method != uaIdent &&
+ hbaline->auth_method != uaPeer &&
+ hbaline->auth_method != uaGSS &&
+ hbaline->auth_method != uaSSPI &&
+ hbaline->auth_method != uaCert)
+ INVALID_AUTH_OPTION("map", gettext_noop("ident, peer, gssapi, sspi, and cert"));
+ hbaline->usermap = pstrdup(val);
+ }
+ else if (strcmp(name, "clientcert") == 0)
+ {
+ if (hbaline->conntype != ctHostSSL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("clientcert can only be configured for \"hostssl\" rows"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "clientcert can only be configured for \"hostssl\" rows";
+ return false;
+ }
+
+ if (strcmp(val, "verify-full") == 0)
+ {
+ hbaline->clientcert = clientCertFull;
+ }
+ else if (strcmp(val, "verify-ca") == 0)
+ {
+ if (hbaline->auth_method == uaCert)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("clientcert only accepts \"verify-full\" when using \"cert\" authentication"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "clientcert can only be set to \"verify-full\" when using \"cert\" authentication";
+ return false;
+ }
+
+ hbaline->clientcert = clientCertCA;
+ }
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid value for clientcert: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ return false;
+ }
+ }
+ else if (strcmp(name, "clientname") == 0)
+ {
+ if (hbaline->conntype != ctHostSSL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("clientname can only be configured for \"hostssl\" rows"),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = "clientname can only be configured for \"hostssl\" rows";
+ return false;
+ }
+
+ if (strcmp(val, "CN") == 0)
+ {
+ hbaline->clientcertname = clientCertCN;
+ }
+ else if (strcmp(val, "DN") == 0)
+ {
+ hbaline->clientcertname = clientCertDN;
+ }
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid value for clientname: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ return false;
+ }
+ }
+ else if (strcmp(name, "pamservice") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaPAM, "pamservice", "pam");
+ hbaline->pamservice = pstrdup(val);
+ }
+ else if (strcmp(name, "pam_use_hostname") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaPAM, "pam_use_hostname", "pam");
+ if (strcmp(val, "1") == 0)
+ hbaline->pam_use_hostname = true;
+ else
+ hbaline->pam_use_hostname = false;
+ }
+ else if (strcmp(name, "ldapurl") == 0)
+ {
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+ LDAPURLDesc *urldata;
+ int rc;
+#endif
+
+ REQUIRE_AUTH_OPTION(uaLDAP, "ldapurl", "ldap");
+#ifdef LDAP_API_FEATURE_X_OPENLDAP
+ rc = ldap_url_parse(val, &urldata);
+ if (rc != LDAP_SUCCESS)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not parse LDAP URL \"%s\": %s", val, ldap_err2string(rc))));
+ *err_msg = psprintf("could not parse LDAP URL \"%s\": %s",
+ val, ldap_err2string(rc));
+ return false;
+ }
+
+ if (strcmp(urldata->lud_scheme, "ldap") != 0 &&
+ strcmp(urldata->lud_scheme, "ldaps") != 0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unsupported LDAP URL scheme: %s", urldata->lud_scheme)));
+ *err_msg = psprintf("unsupported LDAP URL scheme: %s",
+ urldata->lud_scheme);
+ ldap_free_urldesc(urldata);
+ return false;
+ }
+
+ if (urldata->lud_scheme)
+ hbaline->ldapscheme = pstrdup(urldata->lud_scheme);
+ if (urldata->lud_host)
+ hbaline->ldapserver = pstrdup(urldata->lud_host);
+ hbaline->ldapport = urldata->lud_port;
+ if (urldata->lud_dn)
+ hbaline->ldapbasedn = pstrdup(urldata->lud_dn);
+
+ if (urldata->lud_attrs)
+ hbaline->ldapsearchattribute = pstrdup(urldata->lud_attrs[0]); /* only use first one */
+ hbaline->ldapscope = urldata->lud_scope;
+ if (urldata->lud_filter)
+ hbaline->ldapsearchfilter = pstrdup(urldata->lud_filter);
+ ldap_free_urldesc(urldata);
+#else /* not OpenLDAP */
+ ereport(elevel,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("LDAP URLs not supported on this platform")));
+ *err_msg = "LDAP URLs not supported on this platform";
+#endif /* not OpenLDAP */
+ }
+ 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, "ldapscheme") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaLDAP, "ldapscheme", "ldap");
+ if (strcmp(val, "ldap") != 0 && strcmp(val, "ldaps") != 0)
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid ldapscheme value: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ hbaline->ldapscheme = pstrdup(val);
+ }
+ 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(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid LDAP port number: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("invalid LDAP port number: \"%s\"", val);
+ 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, "ldapsearchfilter") == 0)
+ {
+ REQUIRE_AUTH_OPTION(uaLDAP, "ldapsearchfilter", "ldap");
+ hbaline->ldapsearchfilter = 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_realm") == 0)
+ {
+ if (hbaline->auth_method != uaGSS &&
+ hbaline->auth_method != uaSSPI)
+ INVALID_AUTH_OPTION("krb_realm", gettext_noop("gssapi and sspi"));
+ hbaline->krb_realm = pstrdup(val);
+ }
+ else if (strcmp(name, "include_realm") == 0)
+ {
+ if (hbaline->auth_method != uaGSS &&
+ hbaline->auth_method != uaSSPI)
+ INVALID_AUTH_OPTION("include_realm", gettext_noop("gssapi and sspi"));
+ if (strcmp(val, "1") == 0)
+ hbaline->include_realm = true;
+ else
+ hbaline->include_realm = false;
+ }
+ else if (strcmp(name, "compat_realm") == 0)
+ {
+ if (hbaline->auth_method != uaSSPI)
+ INVALID_AUTH_OPTION("compat_realm", gettext_noop("sspi"));
+ if (strcmp(val, "1") == 0)
+ hbaline->compat_realm = true;
+ else
+ hbaline->compat_realm = false;
+ }
+ else if (strcmp(name, "upn_username") == 0)
+ {
+ if (hbaline->auth_method != uaSSPI)
+ INVALID_AUTH_OPTION("upn_username", gettext_noop("sspi"));
+ if (strcmp(val, "1") == 0)
+ hbaline->upn_username = true;
+ else
+ hbaline->upn_username = false;
+ }
+ else if (strcmp(name, "radiusservers") == 0)
+ {
+ struct addrinfo *gai_result;
+ struct addrinfo hints;
+ int ret;
+ List *parsed_servers;
+ ListCell *l;
+ char *dupval = pstrdup(val);
+
+ REQUIRE_AUTH_OPTION(uaRADIUS, "radiusservers", "radius");
+
+ if (!SplitGUCList(dupval, ',', &parsed_servers))
+ {
+ /* syntax error in list */
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not parse RADIUS server list \"%s\"",
+ val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ return false;
+ }
+
+ /* For each entry in the list, translate it */
+ foreach(l, parsed_servers)
+ {
+ MemSet(&hints, 0, sizeof(hints));
+ hints.ai_socktype = SOCK_DGRAM;
+ hints.ai_family = AF_UNSPEC;
+
+ ret = pg_getaddrinfo_all((char *) lfirst(l), NULL, &hints, &gai_result);
+ if (ret || !gai_result)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not translate RADIUS server name \"%s\" to address: %s",
+ (char *) lfirst(l), gai_strerror(ret)),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ if (gai_result)
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+
+ list_free(parsed_servers);
+ return false;
+ }
+ pg_freeaddrinfo_all(hints.ai_family, gai_result);
+ }
+
+ /* All entries are OK, so store them */
+ hbaline->radiusservers = parsed_servers;
+ hbaline->radiusservers_s = pstrdup(val);
+ }
+ else if (strcmp(name, "radiusports") == 0)
+ {
+ List *parsed_ports;
+ ListCell *l;
+ char *dupval = pstrdup(val);
+
+ REQUIRE_AUTH_OPTION(uaRADIUS, "radiusports", "radius");
+
+ if (!SplitGUCList(dupval, ',', &parsed_ports))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not parse RADIUS port list \"%s\"",
+ val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("invalid RADIUS port number: \"%s\"", val);
+ return false;
+ }
+
+ foreach(l, parsed_ports)
+ {
+ if (atoi(lfirst(l)) == 0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("invalid RADIUS port number: \"%s\"", val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+
+ return false;
+ }
+ }
+ hbaline->radiusports = parsed_ports;
+ hbaline->radiusports_s = pstrdup(val);
+ }
+ else if (strcmp(name, "radiussecrets") == 0)
+ {
+ List *parsed_secrets;
+ char *dupval = pstrdup(val);
+
+ REQUIRE_AUTH_OPTION(uaRADIUS, "radiussecrets", "radius");
+
+ if (!SplitGUCList(dupval, ',', &parsed_secrets))
+ {
+ /* syntax error in list */
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not parse RADIUS secret list \"%s\"",
+ val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ return false;
+ }
+
+ hbaline->radiussecrets = parsed_secrets;
+ hbaline->radiussecrets_s = pstrdup(val);
+ }
+ else if (strcmp(name, "radiusidentifiers") == 0)
+ {
+ List *parsed_identifiers;
+ char *dupval = pstrdup(val);
+
+ REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifiers", "radius");
+
+ if (!SplitGUCList(dupval, ',', &parsed_identifiers))
+ {
+ /* syntax error in list */
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("could not parse RADIUS identifiers list \"%s\"",
+ val),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ return false;
+ }
+
+ hbaline->radiusidentifiers = parsed_identifiers;
+ hbaline->radiusidentifiers_s = pstrdup(val);
+ }
+ else
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("unrecognized authentication option name: \"%s\"",
+ name),
+ errcontext("line %d of configuration file \"%s\"",
+ line_num, file_name)));
+ *err_msg = psprintf("unrecognized authentication option name: \"%s\"",
+ name);
+ 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;
+ ListCell *line;
+ HbaLine *hba;
+
+ /* Get the target role's OID. Note we do not error out for bad role. */
+ roleid = get_role_oid(port->user_name, true);
+
+ foreach(line, parsed_hba_lines)
+ {
+ hba = (HbaLine *) lfirst(line);
+
+ /* Check connection type */
+ if (hba->conntype == ctLocal)
+ {
+ if (port->raddr.addr.ss_family != AF_UNIX)
+ continue;
+ }
+ else
+ {
+ if (port->raddr.addr.ss_family == AF_UNIX)
+ continue;
+
+ /* Check SSL state */
+ if (port->ssl_in_use)
+ {
+ /* 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;
+ }
+
+ /* Check GSSAPI state */
+#ifdef ENABLE_GSS
+ if (port->gss && port->gss->enc &&
+ hba->conntype == ctHostNoGSS)
+ continue;
+ else if (!(port->gss && port->gss->enc) &&
+ hba->conntype == ctHostGSS)
+ continue;
+#else
+ if (hba->conntype == ctHostGSS)
+ continue;
+#endif
+
+ /* Check IP address */
+ switch (hba->ip_cmp_method)
+ {
+ case ipCmpMask:
+ if (hba->hostname)
+ {
+ if (!check_hostname(port,
+ hba->hostname))
+ continue;
+ }
+ else
+ {
+ if (!check_ip(&port->raddr,
+ (struct sockaddr *) &hba->addr,
+ (struct sockaddr *) &hba->mask))
+ continue;
+ }
+ break;
+ case ipCmpAll:
+ break;
+ case ipCmpSameHost:
+ case ipCmpSameNet:
+ if (!check_same_host_or_net(&port->raddr,
+ hba->ip_cmp_method))
+ continue;
+ break;
+ default:
+ /* shouldn't get here, but deem it no-match if so */
+ continue;
+ }
+ } /* != ctLocal */
+
+ /* 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, false))
+ 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;
+}
+
+/*
+ * 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.
+ *
+ * On a false result, caller will take care of reporting a FATAL error in case
+ * this is the initial startup. If it happens on reload, we just keep running
+ * with the old data.
+ */
+bool
+load_hba(void)
+{
+ FILE *file;
+ List *hba_lines = NIL;
+ ListCell *line;
+ List *new_parsed_lines = NIL;
+ bool ok = true;
+ MemoryContext oldcxt;
+ MemoryContext hbacxt;
+
+ file = open_auth_file(HbaFileName, LOG, 0, NULL);
+ if (file == NULL)
+ {
+ /* error already logged */
+ return false;
+ }
+
+ tokenize_auth_file(HbaFileName, file, &hba_lines, LOG, 0);
+
+ /* Now parse all the lines */
+ Assert(PostmasterContext);
+ hbacxt = AllocSetContextCreate(PostmasterContext,
+ "hba parser context",
+ ALLOCSET_SMALL_SIZES);
+ oldcxt = MemoryContextSwitchTo(hbacxt);
+ foreach(line, hba_lines)
+ {
+ TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line);
+ HbaLine *newline;
+
+ /* don't parse lines that already have errors */
+ if (tok_line->err_msg != NULL)
+ {
+ ok = false;
+ continue;
+ }
+
+ if ((newline = parse_hba_line(tok_line, LOG)) == NULL)
+ {
+ /* Parse error; remember there's trouble */
+ ok = false;
+
+ /*
+ * Keep parsing the rest of the file so we can report errors on
+ * more than the first line. Error has already been logged, no
+ * need for more chatter here.
+ */
+ continue;
+ }
+
+ new_parsed_lines = lappend(new_parsed_lines, newline);
+ }
+
+ /*
+ * A valid HBA file must have at least one entry; else there's no way to
+ * connect to the postmaster. But only complain about this if we didn't
+ * already have parsing errors.
+ */
+ if (ok && new_parsed_lines == NIL)
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("configuration file \"%s\" contains no entries",
+ HbaFileName)));
+ ok = false;
+ }
+
+ /* Free tokenizer memory */
+ free_auth_file(file, 0);
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!ok)
+ {
+ /*
+ * File contained one or more errors, so bail out. MemoryContextDelete
+ * is enough to clean up everything, including regexes.
+ */
+ 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;
+}
+
+
+/*
+ * Parse one tokenised line from the ident config file and store the result in
+ * an IdentLine structure.
+ *
+ * If parsing fails, log a message at ereport level elevel, store an error
+ * string in tok_line->err_msg and return NULL.
+ *
+ * If ident_user is a regular expression (ie. begins with a slash), it is
+ * compiled and stored in IdentLine structure.
+ *
+ * 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.
+ */
+IdentLine *
+parse_ident_line(TokenizedAuthLine *tok_line, int elevel)
+{
+ int line_num = tok_line->line_num;
+ char *file_name = tok_line->file_name;
+ char **err_msg = &tok_line->err_msg;
+ ListCell *field;
+ List *tokens;
+ AuthToken *token;
+ IdentLine *parsedline;
+
+ Assert(tok_line->fields != NIL);
+ field = list_head(tok_line->fields);
+
+ parsedline = palloc0(sizeof(IdentLine));
+ parsedline->linenumber = line_num;
+
+ /* Get the map token (must exist) */
+ tokens = lfirst(field);
+ IDENT_MULTI_VALUE(tokens);
+ token = linitial(tokens);
+ parsedline->usermap = pstrdup(token->string);
+
+ /* Get the ident user token */
+ field = lnext(tok_line->fields, field);
+ IDENT_FIELD_ABSENT(field);
+ tokens = lfirst(field);
+ IDENT_MULTI_VALUE(tokens);
+ token = linitial(tokens);
+
+ /* Copy the ident user token */
+ parsedline->system_user = copy_auth_token(token);
+
+ /* Get the PG rolename token */
+ field = lnext(tok_line->fields, field);
+ IDENT_FIELD_ABSENT(field);
+ tokens = lfirst(field);
+ IDENT_MULTI_VALUE(tokens);
+ token = linitial(tokens);
+ parsedline->pg_user = copy_auth_token(token);
+
+ /*
+ * Now that the field validation is done, compile a regex from the user
+ * tokens, if necessary.
+ */
+ if (regcomp_auth_token(parsedline->system_user, file_name, line_num,
+ err_msg, elevel))
+ {
+ /* err_msg includes the error to report */
+ return NULL;
+ }
+
+ if (regcomp_auth_token(parsedline->pg_user, file_name, line_num,
+ err_msg, elevel))
+ {
+ /* err_msg includes the error to report */
+ return NULL;
+ }
+
+ return parsedline;
+}
+
+/*
+ * Process one line from the parsed ident config lines.
+ *
+ * Compare input parsed ident line to the needed map, pg_user and system_user.
+ * *found_p and *error_p are set according to our results.
+ */
+static void
+check_ident_usermap(IdentLine *identLine, const char *usermap_name,
+ const char *pg_user, const char *system_user,
+ bool case_insensitive, bool *found_p, bool *error_p)
+{
+ Oid roleid;
+
+ *found_p = false;
+ *error_p = false;
+
+ if (strcmp(identLine->usermap, usermap_name) != 0)
+ /* Line does not match the map name we're looking for, so just abort */
+ return;
+
+ /* Get the target role's OID. Note we do not error out for bad role. */
+ roleid = get_role_oid(pg_user, true);
+
+ /* Match? */
+ if (token_has_regexp(identLine->system_user))
+ {
+ /*
+ * 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;
+ regmatch_t matches[2];
+ char *ofs;
+ AuthToken *expanded_pg_user_token;
+ bool created_temporary_token = false;
+
+ r = regexec_auth_token(system_user, identLine->system_user, 2, matches);
+ if (r)
+ {
+ char errstr[100];
+
+ if (r != REG_NOMATCH)
+ {
+ /* REG_NOMATCH is not an error, everything else is */
+ pg_regerror(r, identLine->system_user->regex, errstr, sizeof(errstr));
+ ereport(LOG,
+ (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+ errmsg("regular expression match for \"%s\" failed: %s",
+ identLine->system_user->string + 1, errstr)));
+ *error_p = true;
+ }
+ return;
+ }
+
+ /*
+ * Replace \1 with the first captured group unless the field already
+ * has some special meaning, like a group membership or a regexp-based
+ * check.
+ */
+ if (!token_is_member_check(identLine->pg_user) &&
+ !token_has_regexp(identLine->pg_user) &&
+ (ofs = strstr(identLine->pg_user->string, "\\1")) != NULL)
+ {
+ char *expanded_pg_user;
+ int offset;
+
+ /* substitution of the first argument requested */
+ if (matches[1].rm_so < 0)
+ {
+ ereport(LOG,
+ (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+ errmsg("regular expression \"%s\" has no subexpressions as requested by backreference in \"%s\"",
+ identLine->system_user->string + 1, identLine->pg_user->string)));
+ *error_p = true;
+ return;
+ }
+
+ /*
+ * length: original length minus length of \1 plus length of match
+ * plus null terminator
+ */
+ expanded_pg_user = palloc0(strlen(identLine->pg_user->string) - 2 + (matches[1].rm_eo - matches[1].rm_so) + 1);
+ offset = ofs - identLine->pg_user->string;
+ memcpy(expanded_pg_user, identLine->pg_user->string, offset);
+ memcpy(expanded_pg_user + offset,
+ system_user + matches[1].rm_so,
+ matches[1].rm_eo - matches[1].rm_so);
+ strcat(expanded_pg_user, ofs + 2);
+
+ /*
+ * Mark the token as quoted, so it will only be compared literally
+ * and not for some special meaning, such as "all" or a group
+ * membership check.
+ */
+ expanded_pg_user_token = make_auth_token(expanded_pg_user, true);
+ created_temporary_token = true;
+ pfree(expanded_pg_user);
+ }
+ else
+ {
+ expanded_pg_user_token = identLine->pg_user;
+ }
+
+ /* check the Postgres user */
+ *found_p = check_role(pg_user, roleid,
+ list_make1(expanded_pg_user_token),
+ case_insensitive);
+
+ if (created_temporary_token)
+ free_auth_token(expanded_pg_user_token);
+
+ return;
+ }
+ else
+ {
+ /*
+ * Not a regular expression, so make a complete match. If the system
+ * user does not match, just leave.
+ */
+ if (case_insensitive)
+ {
+ if (!token_matches_insensitive(identLine->system_user,
+ system_user))
+ return;
+ }
+ else
+ {
+ if (!token_matches(identLine->system_user, system_user))
+ return;
+ }
+
+ /* check the Postgres user */
+ *found_p = check_role(pg_user, roleid,
+ list_make1(identLine->pg_user),
+ case_insensitive);
+ }
+}
+
+
+/*
+ * Scan the (pre-parsed) ident usermap file line by line, looking for a match
+ *
+ * See if the system user with ident username "system_user" is allowed to act as
+ * Postgres user "pg_user" according to usermap "usermap_name".
+ *
+ * Special case: Usermap NULL, equivalent to what was previously called
+ * "sameuser" or "samerole", means don't look in the usermap file.
+ * That's an implied map wherein "pg_user" must be identical to
+ * "system_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_user,
+ const char *system_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_user, system_user) == 0)
+ return STATUS_OK;
+ }
+ else
+ {
+ if (strcmp(pg_user, system_user) == 0)
+ return STATUS_OK;
+ }
+ ereport(LOG,
+ (errmsg("provided user name (%s) and authenticated user name (%s) do not match",
+ pg_user, system_user)));
+ return STATUS_ERROR;
+ }
+ else
+ {
+ ListCell *line_cell;
+
+ foreach(line_cell, parsed_ident_lines)
+ {
+ check_ident_usermap(lfirst(line_cell), usermap_name,
+ pg_user, system_user, case_insensitive,
+ &found_entry, &error);
+ if (found_entry || error)
+ break;
+ }
+ }
+ if (!found_entry && !error)
+ {
+ ereport(LOG,
+ (errmsg("no match in usermap \"%s\" for user \"%s\" authenticated as \"%s\"",
+ usermap_name, pg_user, system_user)));
+ }
+ return found_entry ? STATUS_OK : STATUS_ERROR;
+}
+
+
+/*
+ * Read the ident config file and create a List of IdentLine records for
+ * the contents.
+ *
+ * This works the same as load_hba(), but for the user config file.
+ */
+bool
+load_ident(void)
+{
+ FILE *file;
+ List *ident_lines = NIL;
+ ListCell *line_cell;
+ List *new_parsed_lines = NIL;
+ bool ok = true;
+ MemoryContext oldcxt;
+ MemoryContext ident_context;
+ IdentLine *newline;
+
+ /* not FATAL ... we just won't do any special ident maps */
+ file = open_auth_file(IdentFileName, LOG, 0, NULL);
+ if (file == NULL)
+ {
+ /* error already logged */
+ return false;
+ }
+
+ tokenize_auth_file(IdentFileName, file, &ident_lines, LOG, 0);
+
+ /* Now parse all the lines */
+ Assert(PostmasterContext);
+ ident_context = AllocSetContextCreate(PostmasterContext,
+ "ident parser context",
+ ALLOCSET_SMALL_SIZES);
+ oldcxt = MemoryContextSwitchTo(ident_context);
+ foreach(line_cell, ident_lines)
+ {
+ TokenizedAuthLine *tok_line = (TokenizedAuthLine *) lfirst(line_cell);
+
+ /* don't parse lines that already have errors */
+ if (tok_line->err_msg != NULL)
+ {
+ ok = false;
+ continue;
+ }
+
+ if ((newline = parse_ident_line(tok_line, LOG)) == NULL)
+ {
+ /* Parse error; remember there's trouble */
+ ok = false;
+
+ /*
+ * Keep parsing the rest of the file so we can report errors on
+ * more than the first line. Error has already been logged, no
+ * need for more chatter here.
+ */
+ continue;
+ }
+
+ new_parsed_lines = lappend(new_parsed_lines, newline);
+ }
+
+ /* Free tokenizer memory */
+ free_auth_file(file, 0);
+ MemoryContextSwitchTo(oldcxt);
+
+ if (!ok)
+ {
+ /*
+ * File contained one or more errors, so bail out. MemoryContextDelete
+ * is enough to clean up everything, including regexes.
+ */
+ MemoryContextDelete(ident_context);
+ return false;
+ }
+
+ /* Loaded new file successfully, replace the one we use */
+ if (parsed_ident_context != NULL)
+ MemoryContextDelete(parsed_ident_context);
+
+ parsed_ident_context = ident_context;
+ parsed_ident_lines = new_parsed_lines;
+
+ return true;
+}
+
+
+
+/*
+ * 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.
+ *
+ * If the file does not contain any entry matching the request, we return
+ * method = uaImplicitReject.
+ */
+void
+hba_getauthmethod(hbaPort *port)
+{
+ check_hba(port);
+}
+
+
+/*
+ * Return the name of the auth method in use ("gss", "md5", "trust", etc.).
+ *
+ * The return value is statically allocated (see the UserAuthName array) and
+ * should not be freed.
+ */
+const char *
+hba_authname(UserAuth auth_method)
+{
+ return UserAuthName[auth_method];
+}