mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +03:00
1671 lines
39 KiB
C
1671 lines
39 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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 possibly using ident).
|
|
*
|
|
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.137 2005/02/12 23:53:42 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include <errno.h>
|
|
#include <pwd.h>
|
|
#include <fcntl.h>
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#if defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || defined(HAVE_STRUCT_SOCKCRED)
|
|
#include <sys/uio.h>
|
|
#include <sys/ucred.h>
|
|
#endif
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <unistd.h>
|
|
|
|
#include "commands/user.h"
|
|
#include "libpq/crypt.h"
|
|
#include "libpq/libpq.h"
|
|
#include "miscadmin.h"
|
|
#include "nodes/pg_list.h"
|
|
#include "storage/fd.h"
|
|
#include "utils/guc.h"
|
|
|
|
|
|
/* Max size of username ident server can return */
|
|
#define IDENT_USERNAME_MAX 512
|
|
|
|
/* Standard TCP port number for Ident service. Assigned by IANA */
|
|
#define IDENT_PORT 113
|
|
|
|
/* This is used to separate values in multi-valued column strings */
|
|
#define MULTI_VALUE_SEP "\001"
|
|
|
|
#define MAX_TOKEN 256
|
|
|
|
/*
|
|
* These variables hold the pre-parsed contents of the hba and ident
|
|
* configuration files. Each is a list of sublists, one sublist for
|
|
* each (non-empty, non-comment) line of the file. Each sublist's
|
|
* first item is an integer line number (so we can give somewhat-useful
|
|
* location info in error messages). Remaining items are palloc'd strings,
|
|
* one string per token on the line. Note there will always be at least
|
|
* one token, since blank lines are not entered in the data structure.
|
|
*/
|
|
|
|
/* pre-parsed content of HBA config file and corresponding line #s */
|
|
static List *hba_lines = NIL;
|
|
static List *hba_line_nums = NIL;
|
|
|
|
/* pre-parsed content of ident usermap file and corresponding line #s */
|
|
static List *ident_lines = NIL;
|
|
static List *ident_line_nums = NIL;
|
|
|
|
/* pre-parsed content of group file and corresponding line #s */
|
|
static List *group_lines = NIL;
|
|
static List *group_line_nums = NIL;
|
|
|
|
/* pre-parsed content of user passwd file and corresponding line #s */
|
|
static List *user_lines = NIL;
|
|
static List *user_line_nums = NIL;
|
|
|
|
/* sorted entries so we can do binary search lookups */
|
|
static List **user_sorted = NULL; /* sorted user list, for bsearch() */
|
|
static List **group_sorted = NULL; /* sorted group list, for
|
|
* bsearch() */
|
|
static int user_length;
|
|
static int group_length;
|
|
|
|
static void tokenize_file(const char *filename, FILE *file,
|
|
List **lines, List **line_nums);
|
|
static char *tokenize_inc_file(const char *outer_filename,
|
|
const char *inc_filename);
|
|
|
|
/*
|
|
* isblank() exists in the ISO C99 spec, but it's not very portable yet,
|
|
* so provide our own version.
|
|
*/
|
|
static bool
|
|
pg_isblank(const char c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '\r';
|
|
}
|
|
|
|
|
|
/*
|
|
* Grab one token out of fp. Tokens are strings of non-blank
|
|
* characters bounded by blank characters, beginning of line, and
|
|
* end of line. Blank means space or tab. Return the token as
|
|
* *buf. Leave file positioned at the character immediately after the
|
|
* token or EOF, whichever comes first. If no more tokens on line,
|
|
* return empty string as *buf and position the file to the beginning
|
|
* of the next line or EOF, whichever comes first. Allow spaces in
|
|
* quoted strings. Terminate on unquoted commas. Handle
|
|
* comments. Treat unquoted keywords that might be user names or
|
|
* database names specially, by appending a newline to them.
|
|
*/
|
|
static void
|
|
next_token(FILE *fp, char *buf, int bufsz)
|
|
{
|
|
int c;
|
|
char *start_buf = buf;
|
|
char *end_buf = buf + (bufsz - 2);
|
|
bool in_quote = false;
|
|
bool was_quote = false;
|
|
bool saw_quote = false;
|
|
|
|
Assert(end_buf > start_buf);
|
|
|
|
/* Move over initial whitespace and commas */
|
|
while ((c = getc(fp)) != EOF && (pg_isblank(c) || c == ','))
|
|
;
|
|
|
|
if (c == EOF || c == '\n')
|
|
{
|
|
*buf = '\0';
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Build a token in buf of next characters up to EOF, EOL, unquoted
|
|
* comma, or unquoted whitespace.
|
|
*/
|
|
while (c != EOF && c != '\n' &&
|
|
(!pg_isblank(c) || in_quote == true))
|
|
{
|
|
/* skip comments to EOL */
|
|
if (c == '#' && !in_quote)
|
|
{
|
|
while ((c = getc(fp)) != EOF && c != '\n')
|
|
;
|
|
/* If only comment, consume EOL too; return EOL */
|
|
if (c != EOF && buf == start_buf)
|
|
c = getc(fp);
|
|
break;
|
|
}
|
|
|
|
if (buf >= end_buf)
|
|
{
|
|
*buf = '\0';
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("authentication file token too long, skipping: \"%s\"",
|
|
start_buf)));
|
|
/* Discard remainder of line */
|
|
while ((c = getc(fp)) != EOF && c != '\n')
|
|
;
|
|
break;
|
|
}
|
|
|
|
if (c != '"' || (c == '"' && was_quote))
|
|
*buf++ = c;
|
|
|
|
/* We pass back the comma so the caller knows there is more */
|
|
if ((pg_isblank(c) || c == ',') && !in_quote)
|
|
break;
|
|
|
|
/* Literal double-quote is two double-quotes */
|
|
if (in_quote && c == '"')
|
|
was_quote = !was_quote;
|
|
else
|
|
was_quote = false;
|
|
|
|
if (c == '"')
|
|
{
|
|
in_quote = !in_quote;
|
|
saw_quote = true;
|
|
}
|
|
|
|
c = getc(fp);
|
|
}
|
|
|
|
/*
|
|
* Put back the char right after the token (critical in case it is
|
|
* EOL, since we need to detect end-of-line at next call).
|
|
*/
|
|
if (c != EOF)
|
|
ungetc(c, fp);
|
|
|
|
*buf = '\0';
|
|
|
|
if (!saw_quote &&
|
|
(strcmp(start_buf, "all") == 0 ||
|
|
strcmp(start_buf, "sameuser") == 0 ||
|
|
strcmp(start_buf, "samegroup") == 0))
|
|
{
|
|
/* append newline to a magical keyword */
|
|
*buf++ = '\n';
|
|
*buf = '\0';
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Tokenize file and handle file inclusion and comma lists. We have
|
|
* to break apart the commas to expand any file names then
|
|
* reconstruct with commas.
|
|
*
|
|
* The result is always a palloc'd string. If it's zero-length then
|
|
* we have reached EOL.
|
|
*/
|
|
static char *
|
|
next_token_expand(const char *filename, FILE *file)
|
|
{
|
|
char buf[MAX_TOKEN];
|
|
char *comma_str = pstrdup("");
|
|
bool trailing_comma;
|
|
char *incbuf;
|
|
int needed;
|
|
|
|
do
|
|
{
|
|
next_token(file, buf, sizeof(buf));
|
|
if (!buf[0])
|
|
break;
|
|
|
|
if (buf[strlen(buf) - 1] == ',')
|
|
{
|
|
trailing_comma = true;
|
|
buf[strlen(buf) - 1] = '\0';
|
|
}
|
|
else
|
|
trailing_comma = false;
|
|
|
|
/* Is this referencing a file? */
|
|
if (buf[0] == '@')
|
|
incbuf = tokenize_inc_file(filename, buf + 1);
|
|
else
|
|
incbuf = pstrdup(buf);
|
|
|
|
needed = strlen(comma_str) + strlen(incbuf) + 1;
|
|
if (trailing_comma)
|
|
needed++;
|
|
comma_str = repalloc(comma_str, needed);
|
|
strcat(comma_str, incbuf);
|
|
if (trailing_comma)
|
|
strcat(comma_str, MULTI_VALUE_SEP);
|
|
pfree(incbuf);
|
|
} while (trailing_comma);
|
|
|
|
return comma_str;
|
|
}
|
|
|
|
|
|
/*
|
|
* Free memory used by lines/tokens (i.e., structure built by tokenize_file)
|
|
*/
|
|
static void
|
|
free_lines(List **lines, List **line_nums)
|
|
{
|
|
/*
|
|
* Either both must be non-NULL, or both must be NULL
|
|
*/
|
|
Assert((*lines != NIL && *line_nums != NIL) ||
|
|
(*lines == NIL && *line_nums == NIL));
|
|
|
|
if (*lines)
|
|
{
|
|
/*
|
|
* "lines" is a list of lists; each of those sublists consists of
|
|
* palloc'ed tokens, so we want to free each pointed-to token in a
|
|
* sublist, followed by the sublist itself, and finally the whole
|
|
* list.
|
|
*/
|
|
ListCell *line;
|
|
|
|
foreach(line, *lines)
|
|
{
|
|
List *ln = lfirst(line);
|
|
ListCell *token;
|
|
|
|
foreach(token, ln)
|
|
pfree(lfirst(token));
|
|
/* free the sublist structure itself */
|
|
list_free(ln);
|
|
}
|
|
/* free the list structure itself */
|
|
list_free(*lines);
|
|
/* clear the static variable */
|
|
*lines = NIL;
|
|
}
|
|
|
|
if (*line_nums)
|
|
{
|
|
list_free(*line_nums);
|
|
*line_nums = NIL;
|
|
}
|
|
}
|
|
|
|
|
|
static char *
|
|
tokenize_inc_file(const char *outer_filename,
|
|
const char *inc_filename)
|
|
{
|
|
char *inc_fullname;
|
|
FILE *inc_file;
|
|
List *inc_lines;
|
|
List *inc_line_nums;
|
|
ListCell *line;
|
|
char *comma_str;
|
|
|
|
if (is_absolute_path(inc_filename))
|
|
{
|
|
/* absolute path is taken as-is */
|
|
inc_fullname = pstrdup(inc_filename);
|
|
}
|
|
else
|
|
{
|
|
/* relative path is relative to dir of calling file */
|
|
inc_fullname = (char *) palloc(strlen(outer_filename) + 1 +
|
|
strlen(inc_filename) + 1);
|
|
strcpy(inc_fullname, outer_filename);
|
|
get_parent_directory(inc_fullname);
|
|
join_path_components(inc_fullname, inc_fullname, inc_filename);
|
|
canonicalize_path(inc_fullname);
|
|
}
|
|
|
|
inc_file = AllocateFile(inc_fullname, "r");
|
|
if (inc_file == NULL)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open secondary authentication file \"@%s\" as \"%s\": %m",
|
|
inc_filename, inc_fullname)));
|
|
pfree(inc_fullname);
|
|
|
|
/* return single space, it matches nothing */
|
|
return pstrdup(" ");
|
|
}
|
|
|
|
/* There is possible recursion here if the file contains @ */
|
|
tokenize_file(inc_fullname, inc_file, &inc_lines, &inc_line_nums);
|
|
|
|
FreeFile(inc_file);
|
|
pfree(inc_fullname);
|
|
|
|
/* Create comma-separated string from List */
|
|
comma_str = pstrdup("");
|
|
foreach(line, inc_lines)
|
|
{
|
|
List *token_list = (List *) lfirst(line);
|
|
ListCell *token;
|
|
|
|
foreach(token, token_list)
|
|
{
|
|
int oldlen = strlen(comma_str);
|
|
int needed;
|
|
|
|
needed = oldlen + strlen(lfirst(token)) + 1;
|
|
if (oldlen > 0)
|
|
needed++;
|
|
comma_str = repalloc(comma_str, needed);
|
|
if (oldlen > 0)
|
|
strcat(comma_str, MULTI_VALUE_SEP);
|
|
strcat(comma_str, lfirst(token));
|
|
}
|
|
}
|
|
|
|
free_lines(&inc_lines, &inc_line_nums);
|
|
|
|
/* if file is empty, return single space rather than empty string */
|
|
if (strlen(comma_str) == 0)
|
|
{
|
|
pfree(comma_str);
|
|
return pstrdup(" ");
|
|
}
|
|
|
|
return comma_str;
|
|
}
|
|
|
|
|
|
/*
|
|
* Tokenize the given file, storing the resulting data into two lists:
|
|
* a list of sublists, each sublist containing the tokens in a line of
|
|
* the file, and a list of line numbers.
|
|
*
|
|
* filename must be the absolute path to the target file.
|
|
*/
|
|
static void
|
|
tokenize_file(const char *filename, FILE *file,
|
|
List **lines, List **line_nums)
|
|
{
|
|
List *current_line = NIL;
|
|
int line_number = 1;
|
|
char *buf;
|
|
|
|
*lines = *line_nums = NIL;
|
|
|
|
while (!feof(file))
|
|
{
|
|
buf = next_token_expand(filename, file);
|
|
|
|
/* add token to list, unless we are at EOL or comment start */
|
|
if (buf[0])
|
|
{
|
|
if (current_line == NIL)
|
|
{
|
|
/* make a new line List, record its line number */
|
|
current_line = lappend(current_line, buf);
|
|
*lines = lappend(*lines, current_line);
|
|
*line_nums = lappend_int(*line_nums, line_number);
|
|
}
|
|
else
|
|
{
|
|
/* append token to current line's list */
|
|
current_line = lappend(current_line, buf);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* we are at real or logical EOL, so force a new line List */
|
|
current_line = NIL;
|
|
/* Advance line number whenever we reach EOL */
|
|
line_number++;
|
|
/* Don't forget to pfree the next_token_expand result */
|
|
pfree(buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Compare two lines based on their user/group names.
|
|
*
|
|
* Used for qsort() sorting.
|
|
*/
|
|
static int
|
|
user_group_qsort_cmp(const void *list1, const void *list2)
|
|
{
|
|
char *user1 = linitial(*(List **) list1);
|
|
char *user2 = linitial(*(List **) list2);
|
|
|
|
return strcmp(user1, user2);
|
|
}
|
|
|
|
|
|
/*
|
|
* Compare two lines based on their user/group names.
|
|
*
|
|
* Used for bsearch() lookup.
|
|
*/
|
|
static int
|
|
user_group_bsearch_cmp(const void *user, const void *list)
|
|
{
|
|
char *user2 = linitial(*(List **) list);
|
|
|
|
return strcmp(user, user2);
|
|
}
|
|
|
|
|
|
/*
|
|
* Lookup a group name in the pg_group file
|
|
*/
|
|
static List **
|
|
get_group_line(const char *group)
|
|
{
|
|
/* On some versions of Solaris, bsearch of zero items dumps core */
|
|
if (group_length == 0)
|
|
return NULL;
|
|
|
|
return (List **) bsearch((void *) group,
|
|
(void *) group_sorted,
|
|
group_length,
|
|
sizeof(List *),
|
|
user_group_bsearch_cmp);
|
|
}
|
|
|
|
|
|
/*
|
|
* Lookup a user name in the pg_shadow file
|
|
*/
|
|
List **
|
|
get_user_line(const char *user)
|
|
{
|
|
/* On some versions of Solaris, bsearch of zero items dumps core */
|
|
if (user_length == 0)
|
|
return NULL;
|
|
|
|
return (List **) bsearch((void *) user,
|
|
(void *) user_sorted,
|
|
user_length,
|
|
sizeof(List *),
|
|
user_group_bsearch_cmp);
|
|
}
|
|
|
|
|
|
/*
|
|
* Check group for a specific user.
|
|
*/
|
|
static bool
|
|
check_group(char *group, char *user)
|
|
{
|
|
List **line;
|
|
|
|
if ((line = get_group_line(group)) != NULL)
|
|
{
|
|
ListCell *line_item;
|
|
|
|
/* skip over the group name */
|
|
for_each_cell(line_item, lnext(list_head(*line)))
|
|
{
|
|
if (strcmp(lfirst(line_item), user) == 0)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check comma user list for a specific user, handle group names.
|
|
*/
|
|
static bool
|
|
check_user(char *user, char *param_str)
|
|
{
|
|
char *tok;
|
|
|
|
for (tok = strtok(param_str, MULTI_VALUE_SEP); tok != NULL; tok = strtok(NULL, MULTI_VALUE_SEP))
|
|
{
|
|
if (tok[0] == '+')
|
|
{
|
|
if (check_group(tok + 1, user))
|
|
return true;
|
|
}
|
|
else if (strcmp(tok, user) == 0 ||
|
|
strcmp(tok, "all\n") == 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Check to see if db/user combination matches param string.
|
|
*/
|
|
static bool
|
|
check_db(char *dbname, char *user, char *param_str)
|
|
{
|
|
char *tok;
|
|
|
|
for (tok = strtok(param_str, MULTI_VALUE_SEP); tok != NULL; tok = strtok(NULL, MULTI_VALUE_SEP))
|
|
{
|
|
if (strcmp(tok, "all\n") == 0)
|
|
return true;
|
|
else if (strcmp(tok, "sameuser\n") == 0)
|
|
{
|
|
if (strcmp(dbname, user) == 0)
|
|
return true;
|
|
}
|
|
else if (strcmp(tok, "samegroup\n") == 0)
|
|
{
|
|
if (check_group(dbname, user))
|
|
return true;
|
|
}
|
|
else if (strcmp(tok, dbname) == 0)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan the rest of a host record (after the mask field)
|
|
* and return the interpretation of it as *userauth_p, *auth_arg_p, and
|
|
* *error_p. *line_item points to the next token of the line, and is
|
|
* advanced over successfully-read tokens.
|
|
*/
|
|
static void
|
|
parse_hba_auth(ListCell **line_item, UserAuth *userauth_p,
|
|
char **auth_arg_p, bool *error_p)
|
|
{
|
|
char *token;
|
|
|
|
*auth_arg_p = NULL;
|
|
|
|
if (!*line_item)
|
|
{
|
|
*error_p = true;
|
|
return;
|
|
}
|
|
|
|
token = lfirst(*line_item);
|
|
if (strcmp(token, "trust") == 0)
|
|
*userauth_p = uaTrust;
|
|
else if (strcmp(token, "ident") == 0)
|
|
*userauth_p = uaIdent;
|
|
else if (strcmp(token, "password") == 0)
|
|
*userauth_p = uaPassword;
|
|
else if (strcmp(token, "krb4") == 0)
|
|
*userauth_p = uaKrb4;
|
|
else if (strcmp(token, "krb5") == 0)
|
|
*userauth_p = uaKrb5;
|
|
else if (strcmp(token, "reject") == 0)
|
|
*userauth_p = uaReject;
|
|
else if (strcmp(token, "md5") == 0)
|
|
*userauth_p = uaMD5;
|
|
else if (strcmp(token, "crypt") == 0)
|
|
*userauth_p = uaCrypt;
|
|
#ifdef USE_PAM
|
|
else if (strcmp(token, "pam") == 0)
|
|
*userauth_p = uaPAM;
|
|
#endif
|
|
else
|
|
{
|
|
*error_p = true;
|
|
return;
|
|
}
|
|
*line_item = lnext(*line_item);
|
|
|
|
/* Get the authentication argument token, if any */
|
|
if (*line_item)
|
|
{
|
|
token = lfirst(*line_item);
|
|
*auth_arg_p = pstrdup(token);
|
|
*line_item = lnext(*line_item);
|
|
/* If there is more on the line, it is an error */
|
|
if (*line_item)
|
|
*error_p = true;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Process one line from the hba config file.
|
|
*
|
|
* See if it applies to a connection from a host with IP address port->raddr
|
|
* to a database named port->database. If so, return *found_p true
|
|
* and fill in the auth arguments into the appropriate port fields.
|
|
* If not, leave *found_p as it was. If the record has a syntax error,
|
|
* return *error_p true, after issuing a message to the log. If no error,
|
|
* leave *error_p as it was.
|
|
*/
|
|
static void
|
|
parse_hba(List *line, int line_num, hbaPort *port,
|
|
bool *found_p, bool *error_p)
|
|
{
|
|
char *token;
|
|
char *db;
|
|
char *user;
|
|
struct addrinfo *gai_result;
|
|
struct addrinfo hints;
|
|
int ret;
|
|
struct sockaddr_storage addr;
|
|
struct sockaddr_storage mask;
|
|
char *cidr_slash;
|
|
ListCell *line_item;
|
|
|
|
line_item = list_head(line);
|
|
/* Check the record type. */
|
|
token = lfirst(line_item);
|
|
if (strcmp(token, "local") == 0)
|
|
{
|
|
/* Get the database. */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
db = lfirst(line_item);
|
|
|
|
/* Get the user. */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
user = lfirst(line_item);
|
|
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
|
|
/* Read the rest of the line. */
|
|
parse_hba_auth(&line_item, &port->auth_method,
|
|
&port->auth_arg, error_p);
|
|
if (*error_p)
|
|
goto hba_syntax;
|
|
|
|
/* Disallow auth methods that always need TCP/IP sockets to work */
|
|
if (port->auth_method == uaKrb4 ||
|
|
port->auth_method == uaKrb5)
|
|
goto hba_syntax;
|
|
|
|
/* Does not match if connection isn't AF_UNIX */
|
|
if (!IS_AF_UNIX(port->raddr.addr.ss_family))
|
|
return;
|
|
}
|
|
else if (strcmp(token, "host") == 0
|
|
|| strcmp(token, "hostssl") == 0
|
|
|| strcmp(token, "hostnossl") == 0)
|
|
{
|
|
|
|
if (token[4] == 's') /* "hostssl" */
|
|
{
|
|
#ifdef USE_SSL
|
|
/* Record does not match if we are not on an SSL connection */
|
|
if (!port->ssl)
|
|
return;
|
|
|
|
/* Placeholder to require specific SSL level, perhaps? */
|
|
/* Or a client certificate */
|
|
|
|
/* Since we were on SSL, proceed as with normal 'host' mode */
|
|
#else
|
|
/* We don't accept this keyword at all if no SSL support */
|
|
goto hba_syntax;
|
|
#endif
|
|
}
|
|
#ifdef USE_SSL
|
|
else if (token[4] == 'n') /* "hostnossl" */
|
|
{
|
|
/* Record does not match if we are on an SSL connection */
|
|
if (port->ssl)
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/* Get the database. */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
db = lfirst(line_item);
|
|
|
|
/* Get the user. */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
user = lfirst(line_item);
|
|
|
|
/* Read the IP address field. (with or without CIDR netmask) */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
token = lfirst(line_item);
|
|
|
|
/* Check if it has a CIDR suffix and if so isolate it */
|
|
cidr_slash = strchr(token, '/');
|
|
if (cidr_slash)
|
|
*cidr_slash = '\0';
|
|
|
|
/* Get the IP address either way */
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
hints.ai_family = PF_UNSPEC;
|
|
hints.ai_socktype = 0;
|
|
hints.ai_protocol = 0;
|
|
hints.ai_addrlen = 0;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
|
|
ret = getaddrinfo_all(token, NULL, &hints, &gai_result);
|
|
if (ret || !gai_result)
|
|
{
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("invalid IP address \"%s\" in file \"%s\" line %d: %s",
|
|
token, HbaFileName, line_num,
|
|
gai_strerror(ret))));
|
|
if (cidr_slash)
|
|
*cidr_slash = '/';
|
|
if (gai_result)
|
|
freeaddrinfo_all(hints.ai_family, gai_result);
|
|
goto hba_other_error;
|
|
}
|
|
|
|
if (cidr_slash)
|
|
*cidr_slash = '/';
|
|
|
|
memcpy(&addr, gai_result->ai_addr, gai_result->ai_addrlen);
|
|
freeaddrinfo_all(hints.ai_family, gai_result);
|
|
|
|
/* Get the netmask */
|
|
if (cidr_slash)
|
|
{
|
|
if (SockAddr_cidr_mask(&mask, cidr_slash + 1, addr.ss_family) < 0)
|
|
goto hba_syntax;
|
|
}
|
|
else
|
|
{
|
|
/* Read the mask field. */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
token = lfirst(line_item);
|
|
|
|
ret = getaddrinfo_all(token, NULL, &hints, &gai_result);
|
|
if (ret || !gai_result)
|
|
{
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("invalid IP mask \"%s\" in file \"%s\" line %d: %s",
|
|
token, HbaFileName, line_num,
|
|
gai_strerror(ret))));
|
|
if (gai_result)
|
|
freeaddrinfo_all(hints.ai_family, gai_result);
|
|
goto hba_other_error;
|
|
}
|
|
|
|
memcpy(&mask, gai_result->ai_addr, gai_result->ai_addrlen);
|
|
freeaddrinfo_all(hints.ai_family, gai_result);
|
|
|
|
if (addr.ss_family != mask.ss_family)
|
|
{
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("IP address and mask do not match in file \"%s\" line %d",
|
|
HbaFileName, line_num)));
|
|
goto hba_other_error;
|
|
}
|
|
}
|
|
|
|
if (addr.ss_family != port->raddr.addr.ss_family)
|
|
{
|
|
/*
|
|
* Wrong address family. We allow only one case: if the file
|
|
* has IPv4 and the port is IPv6, promote the file address to
|
|
* IPv6 and try to match that way.
|
|
*/
|
|
#ifdef HAVE_IPV6
|
|
if (addr.ss_family == AF_INET &&
|
|
port->raddr.addr.ss_family == AF_INET6)
|
|
{
|
|
promote_v4_to_v6_addr(&addr);
|
|
promote_v4_to_v6_mask(&mask);
|
|
}
|
|
else
|
|
#endif /* HAVE_IPV6 */
|
|
{
|
|
/* Line doesn't match client port, so ignore it. */
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Ignore line if client port is not in the matching addr range. */
|
|
if (!rangeSockAddr(&port->raddr.addr, &addr, &mask))
|
|
return;
|
|
|
|
/* Read the rest of the line. */
|
|
line_item = lnext(line_item);
|
|
if (!line_item)
|
|
goto hba_syntax;
|
|
parse_hba_auth(&line_item, &port->auth_method,
|
|
&port->auth_arg, error_p);
|
|
if (*error_p)
|
|
goto hba_syntax;
|
|
}
|
|
else
|
|
goto hba_syntax;
|
|
|
|
/* Does the entry match database and user? */
|
|
if (!check_db(port->database_name, port->user_name, db))
|
|
return;
|
|
if (!check_user(port->user_name, user))
|
|
return;
|
|
|
|
/* Success */
|
|
*found_p = true;
|
|
return;
|
|
|
|
hba_syntax:
|
|
if (line_item)
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"",
|
|
HbaFileName, line_num,
|
|
(char *) lfirst(line_item))));
|
|
else
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("missing field in file \"%s\" at end of line %d",
|
|
HbaFileName, line_num)));
|
|
|
|
/* Come here if suitable message already logged */
|
|
hba_other_error:
|
|
*error_p = true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan the (pre-parsed) hba file line by line, looking for a match
|
|
* to the port's connection request.
|
|
*/
|
|
static bool
|
|
check_hba(hbaPort *port)
|
|
{
|
|
bool found_entry = false;
|
|
bool error = false;
|
|
ListCell *line;
|
|
ListCell *line_num;
|
|
|
|
forboth(line, hba_lines, line_num, hba_line_nums)
|
|
{
|
|
parse_hba(lfirst(line), lfirst_int(line_num),
|
|
port, &found_entry, &error);
|
|
if (found_entry || error)
|
|
break;
|
|
}
|
|
|
|
if (!error)
|
|
{
|
|
/* If no matching entry was found, synthesize 'reject' entry. */
|
|
if (!found_entry)
|
|
port->auth_method = uaReject;
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
/*
|
|
* Load group/user name mapping file
|
|
*/
|
|
void
|
|
load_group(void)
|
|
{
|
|
char *filename;
|
|
FILE *group_file;
|
|
|
|
/* Discard any old data */
|
|
if (group_lines || group_line_nums)
|
|
free_lines(&group_lines, &group_line_nums);
|
|
if (group_sorted)
|
|
pfree(group_sorted);
|
|
group_sorted = NULL;
|
|
group_length = 0;
|
|
|
|
/* Read in the file contents */
|
|
filename = group_getfilename();
|
|
group_file = AllocateFile(filename, "r");
|
|
|
|
if (group_file == NULL)
|
|
{
|
|
/* no complaint if not there */
|
|
if (errno != ENOENT)
|
|
ereport(LOG,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open file \"%s\": %m", filename)));
|
|
pfree(filename);
|
|
return;
|
|
}
|
|
|
|
tokenize_file(filename, group_file, &group_lines, &group_line_nums);
|
|
|
|
FreeFile(group_file);
|
|
pfree(filename);
|
|
|
|
/* create sorted lines for binary searching */
|
|
group_length = list_length(group_lines);
|
|
if (group_length)
|
|
{
|
|
int i = 0;
|
|
ListCell *line;
|
|
|
|
group_sorted = palloc(group_length * sizeof(List *));
|
|
|
|
foreach(line, group_lines)
|
|
group_sorted[i++] = lfirst(line);
|
|
|
|
qsort((void *) group_sorted,
|
|
group_length,
|
|
sizeof(List *),
|
|
user_group_qsort_cmp);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Load user/password mapping file
|
|
*/
|
|
void
|
|
load_user(void)
|
|
{
|
|
char *filename;
|
|
FILE *user_file;
|
|
|
|
/* Discard any old data */
|
|
if (user_lines || user_line_nums)
|
|
free_lines(&user_lines, &user_line_nums);
|
|
if (user_sorted)
|
|
pfree(user_sorted);
|
|
user_sorted = NULL;
|
|
user_length = 0;
|
|
|
|
/* Read in the file contents */
|
|
filename = user_getfilename();
|
|
user_file = AllocateFile(filename, "r");
|
|
|
|
if (user_file == NULL)
|
|
{
|
|
/* no complaint if not there */
|
|
if (errno != ENOENT)
|
|
ereport(LOG,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open file \"%s\": %m", filename)));
|
|
pfree(filename);
|
|
return;
|
|
}
|
|
|
|
tokenize_file(filename, user_file, &user_lines, &user_line_nums);
|
|
|
|
FreeFile(user_file);
|
|
pfree(filename);
|
|
|
|
/* create sorted lines for binary searching */
|
|
user_length = list_length(user_lines);
|
|
if (user_length)
|
|
{
|
|
int i = 0;
|
|
ListCell *line;
|
|
|
|
user_sorted = palloc(user_length * sizeof(List *));
|
|
|
|
foreach(line, user_lines)
|
|
user_sorted[i++] = lfirst(line);
|
|
|
|
qsort((void *) user_sorted,
|
|
user_length,
|
|
sizeof(List *),
|
|
user_group_qsort_cmp);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Read the config file and create a List of Lists of tokens in the file.
|
|
*/
|
|
void
|
|
load_hba(void)
|
|
{
|
|
FILE *file;
|
|
|
|
if (hba_lines || hba_line_nums)
|
|
free_lines(&hba_lines, &hba_line_nums);
|
|
|
|
file = AllocateFile(HbaFileName, "r");
|
|
/* Failure is fatal since with no HBA entries we can do nothing... */
|
|
if (file == NULL)
|
|
ereport(FATAL,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open configuration file \"%s\": %m",
|
|
HbaFileName)));
|
|
|
|
tokenize_file(HbaFileName, file, &hba_lines, &hba_line_nums);
|
|
FreeFile(file);
|
|
}
|
|
|
|
|
|
/*
|
|
* Process one line from the ident config file.
|
|
*
|
|
* Take the line and compare it to the needed map, pg_user and ident_user.
|
|
* *found_p and *error_p are set according to our results.
|
|
*/
|
|
static void
|
|
parse_ident_usermap(List *line, int line_number, const char *usermap_name,
|
|
const char *pg_user, const char *ident_user,
|
|
bool *found_p, bool *error_p)
|
|
{
|
|
ListCell *line_item;
|
|
char *token;
|
|
char *file_map;
|
|
char *file_pguser;
|
|
char *file_ident_user;
|
|
|
|
*found_p = false;
|
|
*error_p = false;
|
|
|
|
Assert(line != NIL);
|
|
line_item = list_head(line);
|
|
|
|
/* Get the map token (must exist) */
|
|
token = lfirst(line_item);
|
|
file_map = token;
|
|
|
|
/* Get the ident user token (must be provided) */
|
|
line_item = lnext(line_item);
|
|
if (!line)
|
|
goto ident_syntax;
|
|
token = lfirst(line_item);
|
|
file_ident_user = token;
|
|
|
|
/* Get the PG username token */
|
|
line_item = lnext(line_item);
|
|
if (!line)
|
|
goto ident_syntax;
|
|
token = lfirst(line_item);
|
|
file_pguser = token;
|
|
|
|
/* Match? */
|
|
if (strcmp(file_map, usermap_name) == 0 &&
|
|
strcmp(file_pguser, pg_user) == 0 &&
|
|
strcmp(file_ident_user, ident_user) == 0)
|
|
*found_p = true;
|
|
return;
|
|
|
|
ident_syntax:
|
|
if (line_item)
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("invalid entry in file \"%s\" at line %d, token \"%s\"",
|
|
IdentFileName, line_number,
|
|
(const char *) lfirst(line_item))));
|
|
else
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("missing entry in file \"%s\" at end of line %d",
|
|
IdentFileName, line_number)));
|
|
|
|
*error_p = true;
|
|
}
|
|
|
|
|
|
/*
|
|
* Scan the (pre-parsed) ident usermap file line by line, looking for a match
|
|
*
|
|
* See if the user with ident username "ident_user" is allowed to act
|
|
* as Postgres user "pguser" according to usermap "usermap_name".
|
|
*
|
|
* Special case: For usermap "sameuser", don't look in the usermap
|
|
* file. That's an implied map where "pguser" must be identical to
|
|
* "ident_user" in order to be authorized.
|
|
*
|
|
* Iff authorized, return true.
|
|
*/
|
|
static bool
|
|
check_ident_usermap(const char *usermap_name,
|
|
const char *pg_user,
|
|
const char *ident_user)
|
|
{
|
|
bool found_entry = false,
|
|
error = false;
|
|
|
|
if (usermap_name == NULL || usermap_name[0] == '\0')
|
|
{
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_CONFIG_FILE_ERROR),
|
|
errmsg("cannot use Ident authentication without usermap field")));
|
|
found_entry = false;
|
|
}
|
|
else if (strcmp(usermap_name, "sameuser\n") == 0)
|
|
{
|
|
if (strcmp(pg_user, ident_user) == 0)
|
|
found_entry = true;
|
|
else
|
|
found_entry = false;
|
|
}
|
|
else
|
|
{
|
|
ListCell *line_cell,
|
|
*num_cell;
|
|
|
|
forboth(line_cell, ident_lines, num_cell, ident_line_nums)
|
|
{
|
|
parse_ident_usermap(lfirst(line_cell), lfirst_int(num_cell),
|
|
usermap_name, pg_user, ident_user,
|
|
&found_entry, &error);
|
|
if (found_entry || error)
|
|
break;
|
|
}
|
|
}
|
|
return found_entry;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read the ident config file and create a List of Lists of tokens in the file.
|
|
*/
|
|
void
|
|
load_ident(void)
|
|
{
|
|
FILE *file;
|
|
|
|
if (ident_lines || ident_line_nums)
|
|
free_lines(&ident_lines, &ident_line_nums);
|
|
|
|
file = AllocateFile(IdentFileName, "r");
|
|
if (file == NULL)
|
|
{
|
|
/* not fatal ... we just won't do any special ident maps */
|
|
ereport(LOG,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not open Ident usermap file \"%s\": %m",
|
|
IdentFileName)));
|
|
}
|
|
else
|
|
{
|
|
tokenize_file(IdentFileName, file, &ident_lines, &ident_line_nums);
|
|
FreeFile(file);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Parse the string "*ident_response" as a response from a query to an Ident
|
|
* server. If it's a normal response indicating a user name, return true
|
|
* and store the user name at *ident_user. If it's anything else,
|
|
* return false.
|
|
*/
|
|
static bool
|
|
interpret_ident_response(const char *ident_response,
|
|
char *ident_user)
|
|
{
|
|
const char *cursor = ident_response; /* Cursor into
|
|
* *ident_response */
|
|
|
|
/*
|
|
* Ident's response, in the telnet tradition, should end in crlf
|
|
* (\r\n).
|
|
*/
|
|
if (strlen(ident_response) < 2)
|
|
return false;
|
|
else if (ident_response[strlen(ident_response) - 2] != '\r')
|
|
return false;
|
|
else
|
|
{
|
|
while (*cursor != ':' && *cursor != '\r')
|
|
cursor++; /* skip port field */
|
|
|
|
if (*cursor != ':')
|
|
return false;
|
|
else
|
|
{
|
|
/* We're positioned to colon before response type field */
|
|
char response_type[80];
|
|
int i; /* Index into *response_type */
|
|
|
|
cursor++; /* Go over colon */
|
|
while (pg_isblank(*cursor))
|
|
cursor++; /* skip blanks */
|
|
i = 0;
|
|
while (*cursor != ':' && *cursor != '\r' && !pg_isblank(*cursor) &&
|
|
i < (int) (sizeof(response_type) - 1))
|
|
response_type[i++] = *cursor++;
|
|
response_type[i] = '\0';
|
|
while (pg_isblank(*cursor))
|
|
cursor++; /* skip blanks */
|
|
if (strcmp(response_type, "USERID") != 0)
|
|
return false;
|
|
else
|
|
{
|
|
/*
|
|
* It's a USERID response. Good. "cursor" should be
|
|
* pointing to the colon that precedes the operating
|
|
* system type.
|
|
*/
|
|
if (*cursor != ':')
|
|
return false;
|
|
else
|
|
{
|
|
cursor++; /* Go over colon */
|
|
/* Skip over operating system field. */
|
|
while (*cursor != ':' && *cursor != '\r')
|
|
cursor++;
|
|
if (*cursor != ':')
|
|
return false;
|
|
else
|
|
{
|
|
int i; /* Index into *ident_user */
|
|
|
|
cursor++; /* Go over colon */
|
|
while (pg_isblank(*cursor))
|
|
cursor++; /* skip blanks */
|
|
/* Rest of line is user name. Copy it over. */
|
|
i = 0;
|
|
while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
|
|
ident_user[i++] = *cursor++;
|
|
ident_user[i] = '\0';
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Talk to the ident server on host "remote_ip_addr" and find out who
|
|
* owns the tcp connection from his port "remote_port" to port
|
|
* "local_port_addr" on host "local_ip_addr". Return the user name the
|
|
* ident server gives as "*ident_user".
|
|
*
|
|
* IP addresses and port numbers are in network byte order.
|
|
*
|
|
* But iff we're unable to get the information from ident, return false.
|
|
*/
|
|
static bool
|
|
ident_inet(const SockAddr remote_addr,
|
|
const SockAddr local_addr,
|
|
char *ident_user)
|
|
{
|
|
int sock_fd, /* File descriptor for socket on which we
|
|
* talk to Ident */
|
|
rc; /* Return code from a locally called
|
|
* function */
|
|
bool ident_return;
|
|
char remote_addr_s[NI_MAXHOST];
|
|
char remote_port[NI_MAXSERV];
|
|
char local_addr_s[NI_MAXHOST];
|
|
char local_port[NI_MAXSERV];
|
|
char ident_port[NI_MAXSERV];
|
|
char ident_query[80];
|
|
char ident_response[80 + IDENT_USERNAME_MAX];
|
|
struct addrinfo *ident_serv = NULL,
|
|
*la = NULL,
|
|
hints;
|
|
|
|
/*
|
|
* Might look a little weird to first convert it to text and then back
|
|
* to sockaddr, but it's protocol independent.
|
|
*/
|
|
getnameinfo_all(&remote_addr.addr, remote_addr.salen,
|
|
remote_addr_s, sizeof(remote_addr_s),
|
|
remote_port, sizeof(remote_port),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
getnameinfo_all(&local_addr.addr, local_addr.salen,
|
|
local_addr_s, sizeof(local_addr_s),
|
|
local_port, sizeof(local_port),
|
|
NI_NUMERICHOST | NI_NUMERICSERV);
|
|
|
|
snprintf(ident_port, sizeof(ident_port), "%d", IDENT_PORT);
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
hints.ai_family = remote_addr.addr.ss_family;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = 0;
|
|
hints.ai_addrlen = 0;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
rc = getaddrinfo_all(remote_addr_s, ident_port, &hints, &ident_serv);
|
|
if (rc || !ident_serv)
|
|
{
|
|
if (ident_serv)
|
|
freeaddrinfo_all(hints.ai_family, ident_serv);
|
|
return false; /* we don't expect this to happen */
|
|
}
|
|
|
|
hints.ai_flags = AI_NUMERICHOST;
|
|
hints.ai_family = local_addr.addr.ss_family;
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
hints.ai_protocol = 0;
|
|
hints.ai_addrlen = 0;
|
|
hints.ai_canonname = NULL;
|
|
hints.ai_addr = NULL;
|
|
hints.ai_next = NULL;
|
|
rc = getaddrinfo_all(local_addr_s, NULL, &hints, &la);
|
|
if (rc || !la)
|
|
{
|
|
if (la)
|
|
freeaddrinfo_all(hints.ai_family, la);
|
|
return false; /* we don't expect this to happen */
|
|
}
|
|
|
|
sock_fd = socket(ident_serv->ai_family, ident_serv->ai_socktype,
|
|
ident_serv->ai_protocol);
|
|
if (sock_fd < 0)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not create socket for Ident connection: %m")));
|
|
ident_return = false;
|
|
goto ident_inet_done;
|
|
}
|
|
|
|
/*
|
|
* Bind to the address which the client originally contacted,
|
|
* otherwise the ident server won't be able to match up the right
|
|
* connection. This is necessary if the PostgreSQL server is running
|
|
* on an IP alias.
|
|
*/
|
|
rc = bind(sock_fd, la->ai_addr, la->ai_addrlen);
|
|
if (rc != 0)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not bind to local address \"%s\": %m",
|
|
local_addr_s)));
|
|
ident_return = false;
|
|
goto ident_inet_done;
|
|
}
|
|
|
|
rc = connect(sock_fd, ident_serv->ai_addr,
|
|
ident_serv->ai_addrlen);
|
|
if (rc != 0)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not connect to Ident server at address \"%s\", port %s: %m",
|
|
remote_addr_s, ident_port)));
|
|
ident_return = false;
|
|
goto ident_inet_done;
|
|
}
|
|
|
|
/* The query we send to the Ident server */
|
|
snprintf(ident_query, sizeof(ident_query), "%s,%s\r\n",
|
|
remote_port, local_port);
|
|
|
|
/* loop in case send is interrupted */
|
|
do
|
|
{
|
|
rc = send(sock_fd, ident_query, strlen(ident_query), 0);
|
|
} while (rc < 0 && errno == EINTR);
|
|
|
|
if (rc < 0)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not send query to Ident server at address \"%s\", port %s: %m",
|
|
remote_addr_s, ident_port)));
|
|
ident_return = false;
|
|
goto ident_inet_done;
|
|
}
|
|
|
|
do
|
|
{
|
|
rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
|
|
} while (rc < 0 && errno == EINTR);
|
|
|
|
if (rc < 0)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not receive response from Ident server at address \"%s\", port %s: %m",
|
|
remote_addr_s, ident_port)));
|
|
ident_return = false;
|
|
goto ident_inet_done;
|
|
}
|
|
|
|
ident_response[rc] = '\0';
|
|
ident_return = interpret_ident_response(ident_response, ident_user);
|
|
if (!ident_return)
|
|
ereport(LOG,
|
|
(errmsg("invalidly formatted response from Ident server: \"%s\"",
|
|
ident_response)));
|
|
|
|
ident_inet_done:
|
|
if (sock_fd >= 0)
|
|
closesocket(sock_fd);
|
|
freeaddrinfo_all(remote_addr.addr.ss_family, ident_serv);
|
|
freeaddrinfo_all(local_addr.addr.ss_family, la);
|
|
return ident_return;
|
|
}
|
|
|
|
/*
|
|
* Ask kernel about the credentials of the connecting process and
|
|
* determine the symbolic name of the corresponding user.
|
|
*
|
|
* Returns either true and the username put into "ident_user",
|
|
* or false if we were unable to determine the username.
|
|
*/
|
|
#ifdef HAVE_UNIX_SOCKETS
|
|
|
|
static bool
|
|
ident_unix(int sock, char *ident_user)
|
|
{
|
|
#if defined(HAVE_GETPEEREID)
|
|
/* OpenBSD style: */
|
|
uid_t uid;
|
|
gid_t gid;
|
|
struct passwd *pass;
|
|
|
|
errno = 0;
|
|
if (getpeereid(sock, &uid, &gid) != 0)
|
|
{
|
|
/* We didn't get a valid credentials struct. */
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not get peer credentials: %m")));
|
|
return false;
|
|
}
|
|
|
|
pass = getpwuid(uid);
|
|
|
|
if (pass == NULL)
|
|
{
|
|
ereport(LOG,
|
|
(errmsg("local user with ID %d does not exist",
|
|
(int) uid)));
|
|
return false;
|
|
}
|
|
|
|
StrNCpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
|
|
|
|
return true;
|
|
|
|
#elif defined(SO_PEERCRED)
|
|
/* Linux style: use getsockopt(SO_PEERCRED) */
|
|
struct ucred peercred;
|
|
ACCEPT_TYPE_ARG3 so_len = sizeof(peercred);
|
|
struct passwd *pass;
|
|
|
|
errno = 0;
|
|
if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &peercred, &so_len) != 0 ||
|
|
so_len != sizeof(peercred))
|
|
{
|
|
/* We didn't get a valid credentials struct. */
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not get peer credentials: %m")));
|
|
return false;
|
|
}
|
|
|
|
pass = getpwuid(peercred.uid);
|
|
|
|
if (pass == NULL)
|
|
{
|
|
ereport(LOG,
|
|
(errmsg("local user with ID %d does not exist",
|
|
(int) peercred.uid)));
|
|
return false;
|
|
}
|
|
|
|
StrNCpy(ident_user, pass->pw_name, IDENT_USERNAME_MAX + 1);
|
|
|
|
return true;
|
|
|
|
#elif defined(HAVE_STRUCT_CMSGCRED) || defined(HAVE_STRUCT_FCRED) || (defined(HAVE_STRUCT_SOCKCRED) && defined(LOCAL_CREDS))
|
|
struct msghdr msg;
|
|
|
|
/* Credentials structure */
|
|
#if defined(HAVE_STRUCT_CMSGCRED)
|
|
typedef struct cmsgcred Cred;
|
|
|
|
#define cruid cmcred_uid
|
|
#elif defined(HAVE_STRUCT_FCRED)
|
|
typedef struct fcred Cred;
|
|
|
|
#define cruid fc_uid
|
|
#elif defined(HAVE_STRUCT_SOCKCRED)
|
|
typedef struct sockcred Cred;
|
|
|
|
#define cruid sc_uid
|
|
#endif
|
|
Cred *cred;
|
|
|
|
/* Compute size without padding */
|
|
char cmsgmem[ALIGN(sizeof(struct cmsghdr)) + ALIGN(sizeof(Cred))]; /* for NetBSD */
|
|
|
|
/* Point to start of first structure */
|
|
struct cmsghdr *cmsg = (struct cmsghdr *) cmsgmem;
|
|
|
|
struct iovec iov;
|
|
char buf;
|
|
struct passwd *pw;
|
|
|
|
memset(&msg, 0, sizeof(msg));
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = (char *) cmsg;
|
|
msg.msg_controllen = sizeof(cmsgmem);
|
|
memset(cmsg, 0, sizeof(cmsgmem));
|
|
|
|
/*
|
|
* The one character which is received here is not meaningful; its
|
|
* purposes is only to make sure that recvmsg() blocks long enough for
|
|
* the other side to send its credentials.
|
|
*/
|
|
iov.iov_base = &buf;
|
|
iov.iov_len = 1;
|
|
|
|
if (recvmsg(sock, &msg, 0) < 0 ||
|
|
cmsg->cmsg_len < sizeof(cmsgmem) ||
|
|
cmsg->cmsg_type != SCM_CREDS)
|
|
{
|
|
ereport(LOG,
|
|
(errcode_for_socket_access(),
|
|
errmsg("could not get peer credentials: %m")));
|
|
return false;
|
|
}
|
|
|
|
cred = (Cred *) CMSG_DATA(cmsg);
|
|
|
|
pw = getpwuid(cred->cruid);
|
|
|
|
if (pw == NULL)
|
|
{
|
|
ereport(LOG,
|
|
(errmsg("local user with ID %d does not exist",
|
|
(int) cred->cruid)));
|
|
return false;
|
|
}
|
|
|
|
StrNCpy(ident_user, pw->pw_name, IDENT_USERNAME_MAX + 1);
|
|
|
|
return true;
|
|
|
|
#else
|
|
ereport(LOG,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("Ident authentication is not supported on local connections on this platform")));
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
#endif /* HAVE_UNIX_SOCKETS */
|
|
|
|
|
|
/*
|
|
* Determine the username of the initiator of the connection described
|
|
* by "port". Then look in the usermap file under the usermap
|
|
* port->auth_arg and see if that user is equivalent to Postgres user
|
|
* port->user.
|
|
*
|
|
* Return STATUS_OK if yes, STATUS_ERROR if no match (or couldn't get info).
|
|
*/
|
|
int
|
|
authident(hbaPort *port)
|
|
{
|
|
char ident_user[IDENT_USERNAME_MAX + 1];
|
|
|
|
switch (port->raddr.addr.ss_family)
|
|
{
|
|
case AF_INET:
|
|
#ifdef HAVE_IPV6
|
|
case AF_INET6:
|
|
#endif
|
|
if (!ident_inet(port->raddr, port->laddr, ident_user))
|
|
return STATUS_ERROR;
|
|
break;
|
|
|
|
#ifdef HAVE_UNIX_SOCKETS
|
|
case AF_UNIX:
|
|
if (!ident_unix(port->sock, ident_user))
|
|
return STATUS_ERROR;
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
ereport(DEBUG2,
|
|
(errmsg("Ident protocol identifies remote user as \"%s\"",
|
|
ident_user)));
|
|
|
|
if (check_ident_usermap(port->auth_arg, port->user_name, ident_user))
|
|
return STATUS_OK;
|
|
else
|
|
return STATUS_ERROR;
|
|
}
|
|
|
|
|
|
/*
|
|
* Determine what authentication method should be used when accessing database
|
|
* "database" from frontend "raddr", user "user". Return the method and
|
|
* an optional argument (stored in fields of *port), and STATUS_OK.
|
|
*
|
|
* Note that STATUS_ERROR indicates a problem with the hba config file.
|
|
* If the file is OK but does not contain any entry matching the request,
|
|
* we return STATUS_OK and method = uaReject.
|
|
*/
|
|
int
|
|
hba_getauthmethod(hbaPort *port)
|
|
{
|
|
if (check_hba(port))
|
|
return STATUS_OK;
|
|
else
|
|
return STATUS_ERROR;
|
|
}
|