| /*------------------------------------------------------------------------- |
| * |
| * 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 |
| */ |
| 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; |
| } |
| |
| /* Can't set LDAPS and StartTLS at the same time. Set ldaptls to 1 to |
| * make the connection between database and the LDAP server use TLS |
| * encryption. The scheme 'ldaps' makes LDAP connections over SSL. |
| */ |
| if (parsedline->ldaptls && parsedline->ldapscheme && strcmp(parsedline->ldapscheme, "ldaps") == 0) |
| { |
| ereport(LOG, |
| (errcode(ERRCODE_CONFIG_FILE_ERROR), |
| errmsg("cannot use 'ldaptls' with 'ldaps' scheme or 'ldapurl' start with 'ldaps://'"), |
| errcontext("line %d of configuration file \"%s\"", |
| line_num, HbaFileName))); |
| 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]; |
| } |