New host-based authentication with ident

This commit is contained in:
Bryan Henderson 1996-10-12 07:47:12 +00:00
parent 57026d6009
commit 4b5c977782
5 changed files with 1030 additions and 269 deletions

View File

@ -7,7 +7,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.2 1996/08/14 04:51:02 scrappy Exp $
* $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.3 1996/10/12 07:47:08 bryanh Exp $
*
*-------------------------------------------------------------------------
*/
@ -58,10 +58,10 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include "libpq/auth.h"
#include "libpq/libpq.h"
#include "libpq/pqcomm.h"
#include "libpq/libpq-be.h"
#include <libpq/auth.h>
#include <libpq/libpq.h>
#include <libpq/libpq-be.h>
#include <libpq/hba.h>
/*----------------------------------------------------------------
* common definitions for generic fe/be routines
@ -86,27 +86,37 @@ struct authsvc {
* allowed. Unauthenticated connections are disallowed unless there
* isn't any authentication system.
*/
#if defined(HBA)
static int useHostBasedAuth = 1;
#else
static int useHostBasedAuth = 0;
#endif
#if defined(KRB4) || defined(KRB5) || defined(HBA)
#define UNAUTH_ALLOWED 0
#else
#define UNAUTH_ALLOWED 1
#endif
static struct authsvc authsvcs[] = {
#ifdef KRB4
{ "unauth", STARTUP_UNAUTH_MSG, UNAUTH_ALLOWED },
{ "hba", STARTUP_HBA_MSG, 1 },
{ "krb4", STARTUP_KRB4_MSG, 1 },
{ "kerberos", STARTUP_KRB4_MSG, 1 },
#endif /* KRB4 */
#ifdef KRB5
{ "krb5", STARTUP_KRB5_MSG, 1 },
{ "kerberos", STARTUP_KRB5_MSG, 1 },
#endif /* KRB5 */
{ UNAUTHNAME, STARTUP_MSG,
#if defined(KRB4) || defined(KRB5)
0
#else /* !(KRB4 || KRB5) */
1
#endif /* !(KRB4 || KRB5) */
}
#if defined(KRB5)
{ "kerberos", STARTUP_KRB5_MSG, 1 }
#else
{ "kerberos", STARTUP_KRB4_MSG, 1 }
#endif
};
static n_authsvcs = sizeof(authsvcs) / sizeof(struct authsvc);
#ifdef KRB4
/* This has to be ifdef'd out because krb.h does exist. This needs
to be fixed.
*/
/*----------------------------------------------------------------
* MIT Kerberos authentication system - protocol version 4
*----------------------------------------------------------------
@ -184,9 +194,28 @@ pg_krb4_recvauth(int sock,
#endif /* !FRONTEND */
#else
static int
pg_krb4_recvauth(int sock,
struct sockaddr_in *laddr,
struct sockaddr_in *raddr,
char *username)
{
(void) sprintf(PQerrormsg,
"pg_krb4_recvauth: Kerberos not implemented on this "
"server.\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
#endif /* KRB4 */
#ifdef KRB5
/* This needs to be ifdef'd out because krb5.h doesn't exist. This needs
to be fixed.
*/
/*----------------------------------------------------------------
* MIT Kerberos authentication system - protocol version 5
*----------------------------------------------------------------
@ -222,7 +251,7 @@ pg_an_to_ln(char *aname)
#else /* !FRONTEND */
/*
* pg_krb4_recvauth -- server routine to receive authentication information
* pg_krb5_recvauth -- server routine to receive authentication information
* from the client
*
* We still need to compare the username obtained from the client's setup
@ -348,266 +377,118 @@ pg_krb5_recvauth(int sock,
#endif /* !FRONTEND */
#endif /* KRB5 */
/*----------------------------------------------------------------
* host based authentication
*----------------------------------------------------------------
* based on the securelib package originally written by William
* LeFebvre, EECS Department, Northwestern University
* (phil@eecs.nwu.edu) - orginal configuration file code handling
* by Sam Horrocks (sam@ics.uci.edu)
*
* modified and adapted for use with Postgres95 by Paul Fisher
* (pnfisher@unity.ncsu.edu)
*/
#define CONF_FILE "pg_hba" /* Name of the config file */
#define MAX_LINES 255 /* Maximum number of config lines *
* that can apply to one database */
#define ALL_NAME "all" /* Name used in config file for *
* lines that apply to all databases */
#define MAX_TOKEN 80 /* Maximum size of one token in the *
* configuration file */
struct conf_line { /* Info about config file line */
u_long adr, mask;
};
static int next_token(FILE *, char *, int);
/* hba_recvauth */
/* check for host-based authentication */
/*
* hba_recvauth - check the sockaddr_in "addr" to see if it corresponds
* to an acceptable host for the database that's being
* connected to. Return STATUS_OK if acceptable,
* otherwise return STATUS_ERROR.
*/
#else
static int
hba_recvauth(struct sockaddr_in *addr, PacketBuf *pbuf, StartupInfo *sp)
pg_krb5_recvauth(int sock,
struct sockaddr_in *laddr,
struct sockaddr_in *raddr,
char *username)
{
u_long ip_addr;
static struct conf_line conf[MAX_LINES];
static int nconf;
int i;
(void) sprintf(PQerrormsg,
"pg_krb5_recvauth: Kerberos not implemented on this "
"server.\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
char buf[MAX_TOKEN];
FILE *file;
char *conf_file;
/* put together the full pathname to the config file */
conf_file = (char *) malloc((strlen(DataDir)+strlen(CONF_FILE)+2)*sizeof(char));
sprintf(conf_file, "%s/%s", DataDir, CONF_FILE);
/* Open the config file. */
file = fopen(conf_file, "r");
if (file)
{
free(conf_file);
nconf = 0;
/* Grab the "name" */
while ((i = next_token(file, buf, sizeof(buf))) != EOF)
{
/* If only token on the line, ignore */
if (i == '\n') continue;
/* Comment -- read until end of line then next line */
if (buf[0] == '#')
{
while (next_token(file, buf, sizeof(buf)) == 0) ;
continue;
}
/*
* Check to make sure this says "all" or that it matches
* the database name.
*/
if (strcmp(buf, ALL_NAME) == 0 || (strcmp(buf, sp->database) == 0))
{
/* Get next token, if last on line, ignore */
if (next_token(file, buf, sizeof(buf)) != 0)
continue;
/* Got address */
conf[nconf].adr = inet_addr(buf);
/* Get next token (mask) */
i = next_token(file, buf, sizeof(buf));
/* Only ignore if we got no text at all */
if (i != EOF)
{
/* Add to list, quit if array is full */
conf[nconf++].mask = inet_addr(buf);
if (nconf == MAX_LINES) break;
}
/* If not at end-of-line, keep reading til we are */
while (i == 0)
i = next_token(file, buf, sizeof(buf));
}
}
fclose(file);
}
else
{ (void) sprintf(PQerrormsg,
"hba_recvauth: Host-based authentication config file "
"does not exist or permissions are not setup correctly! "
"Unable to open file \"%s\".\n",
conf_file);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
free(conf_file);
return(STATUS_ERROR);
}
/* Config lines now in memory so start checking address */
/* grab just the address */
ip_addr = addr->sin_addr.s_addr;
/*
* Go through the conf array, turn off the bits given by the mask
* and then compare the result with the address. A match means
* that this address is ok.
*/
for (i = 0; i < nconf; ++i)
if ((ip_addr & ~conf[i].mask) == conf[i].adr) return(STATUS_OK);
/* no match, so we can't approve the address */
return(STATUS_ERROR);
}
/*
* Grab one token out of fp. Defined as the next string of non-whitespace
* in the file. After we get the token, continue reading until EOF, end of
* line or the next token. If it's the last token on the line, return '\n'
* for the value. If we get EOF before reading a token, return EOF. In all
* other cases return 0.
*/
static int
next_token(FILE *fp, char *buf, int bufsz)
{
int c;
char *eb = buf+(bufsz-1);
/* Discard inital whitespace */
while (isspace(c = getc(fp))) ;
/* EOF seen before any token so return EOF */
if (c == EOF) return -1;
/* Form a token in buf */
do {
if (buf < eb) *buf++ = c;
c = getc(fp);
} while (!isspace(c) && c != EOF);
*buf = '\0';
/* Discard trailing tabs and spaces */
while (c == ' ' || c == '\t') c = getc(fp);
/* Put back the char that was non-whitespace (putting back EOF is ok) */
(void) ungetc(c, fp);
/* If we ended with a newline, return that, otherwise return 0 */
return (c == '\n' ? '\n' : 0);
return(STATUS_ERROR);
}
#endif /* KRB5 */
/*
* be_recvauth -- server demux routine for incoming authentication information
*/
int
be_recvauth(MsgType msgtype, Port *port, char *username, StartupInfo* sp)
be_recvauth(MsgType msgtype_arg, Port *port, char *username, StartupInfo* sp)
{
MsgType msgtype;
/* A message type of STARTUP_MSG (which once upon a time was the only
startup message type) means user wants us to choose. "unauth" is
what used to be the only choice, but installation may choose "hba"
instead.
*/
if (msgtype_arg == STARTUP_MSG && useHostBasedAuth)
msgtype = STARTUP_HBA_MSG;
else
msgtype = STARTUP_UNAUTH_MSG;
if (!username) {
(void) sprintf(PQerrormsg,
"be_recvauth: no user name passed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
(void) sprintf(PQerrormsg,
"be_recvauth: no user name passed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
if (!port) {
(void) sprintf(PQerrormsg,
"be_recvauth: no port structure passed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
(void) sprintf(PQerrormsg,
"be_recvauth: no port structure passed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
switch (msgtype) {
#ifdef KRB4
case STARTUP_KRB4_MSG:
if (!be_getauthsvc(msgtype)) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb4 authentication disallowed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
if (!be_getauthsvc(msgtype)) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb4 authentication disallowed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
if (pg_krb4_recvauth(port->sock, &port->laddr, &port->raddr,
username) != STATUS_OK) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb4 authentication failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
username) != STATUS_OK) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb4 authentication failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
#endif
#ifdef KRB5
case STARTUP_KRB5_MSG:
if (!be_getauthsvc(msgtype)) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb5 authentication disallowed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
if (pg_krb5_recvauth(port->sock, &port->laddr, &port->raddr,
username) != STATUS_OK) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb5 authentication failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
#endif
case STARTUP_MSG:
if (!be_getauthsvc(msgtype)) {
(void) sprintf(PQerrormsg,
"be_recvauth: unauthenticated connections disallowed failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
if (!be_getauthsvc(msgtype)) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb5 authentication disallowed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
if (pg_krb5_recvauth(port->sock, &port->laddr, &port->raddr,
username) != STATUS_OK) {
(void) sprintf(PQerrormsg,
"be_recvauth: krb5 authentication failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
case STARTUP_UNAUTH_MSG:
if (!be_getauthsvc(msgtype)) {
(void) sprintf(PQerrormsg,
"be_recvauth: "
"unauthenticated connections disallowed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
case STARTUP_HBA_MSG:
if (hba_recvauth(&port->raddr, &port->buf, sp) != STATUS_OK) {
(void) sprintf(PQerrormsg,
"be_recvauth: host-based authentication failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
if (hba_recvauth(port, sp->database, sp->user, DataDir) != STATUS_OK) {
(void) sprintf(PQerrormsg,
"be_recvauth: host-based authentication failed\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
break;
default:
(void) sprintf(PQerrormsg,
"be_recvauth: unrecognized message type: %d\n",
msgtype);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
(void) sprintf(PQerrormsg,
"be_recvauth: unrecognized message type: %d\n",
msgtype);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
return(STATUS_ERROR);
}
return(STATUS_OK);
}

768
src/backend/libpq/hba.c Normal file
View File

@ -0,0 +1,768 @@
/*-------------------------------------------------------------------------
*
* 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).
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/libpq/hba.c,v 1.1 1996/10/12 07:47:09 bryanh Exp $
*
*-------------------------------------------------------------------------
*/
#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <ctype.h> /* isspace() declaration */
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <miscadmin.h>
#include <libpq/libpq.h>
#include <libpq/pqcomm.h>
#include <libpq/hba.h>
#define CONF_FILE "pg_hba.conf"
/* Name of the config file */
#define MAP_FILE "pg_ident.conf"
/* Name of the usermap file */
#define OLD_CONF_FILE "pg_hba"
/* Name of the config file in prior releases of Postgres. */
#define MAX_LINES 255
/* Maximum number of config lines that can apply to one database */
#define MAX_TOKEN 80
/* Maximum size of one token in the configuration file */
#define USERMAP_NAME_SIZE 16 /* Max size of a usermap name */
#define IDENT_PORT 113
/* Standard TCP port number for Ident service. Assigned by IANA */
#define IDENT_USERNAME_MAX 512
/* Max size of username ident server can return */
enum Userauth {Trust, Ident};
static void
next_token(FILE *fp, char *buf, const int bufsz) {
/*--------------------------------------------------------------------------
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 to character immediately after the token or
EOF, whichever comes first. If no more tokens on line, return null
string as *buf and position file to beginning of next line or EOF,
whichever comes first.
--------------------------------------------------------------------------*/
int c;
char *eb = buf+(bufsz-1);
/* Move over inital token-delimiting blanks */
while (isblank(c = getc(fp))) ;
if (c != '\n') {
/* build a token in buf of next characters up to EOF, eol, or blank. */
while (c != EOF && c != '\n' && !isblank(c)) {
if (buf < eb) *buf++ = c;
c = getc(fp);
/* Put back the char right after the token (putting back EOF is ok) */
}
(void) ungetc(c, fp);
}
*buf = '\0';
}
static void
read_through_eol(FILE *file) {
int c;
do
c = getc(file);
while (c != '\n' && c != EOF);
}
static void
read_hba_entry2(FILE *file, enum Userauth *userauth_p, char usermap_name[],
bool *error_p) {
/*--------------------------------------------------------------------------
Read from file FILE the rest of a host record, after the mask field,
and return the interpretation of it as *userauth_p, usermap_name, and
*error_p.
---------------------------------------------------------------------------*/
char buf[MAX_TOKEN];
bool userauth_valid;
/* Get authentication type token. */
next_token(file, buf, sizeof(buf));
if (buf[0] == '\0') {
*error_p = true;
read_through_eol(file);
} else {
if (strcmp(buf, "trust") == 0) {
userauth_valid = true;
*userauth_p = Trust;
} else if (strcmp(buf, "ident") == 0) {
userauth_valid = true;
*userauth_p = Ident;
} else userauth_valid = false;
if (!userauth_valid) {
*error_p = true;
read_through_eol(file);
} else {
/* Get the map name token, if any */
next_token(file, buf, sizeof(buf));
if (buf[0] == '\0') {
*error_p = false;
usermap_name[0] = '\0';
} else {
strncpy(usermap_name, buf, USERMAP_NAME_SIZE);
next_token(file, buf, sizeof(buf));
if (buf[0] != '\0') {
*error_p = true;
read_through_eol(file);
} else *error_p = false;
}
}
}
}
static void
process_hba_record(FILE *file,
const struct in_addr ip_addr, const char database[],
bool *matches_p, bool *error_p,
enum Userauth *userauth_p, char usermap_name[] ) {
/*---------------------------------------------------------------------------
Process the non-comment record in the config file that is next on the file.
See if it applies to a connection to a host with IP address "ip_addr"
to a database named "database[]". If so, return *matches_p true
and *userauth_p and usermap_name[] as the values from the entry.
If not, return matches_p false. If the record has a syntax error,
return *error_p true, after issuing a message to stderr. If no error,
leave *error_p as it was.
---------------------------------------------------------------------------*/
char buf[MAX_TOKEN]; /* A token from the record */
/* Read the record type field */
next_token(file, buf, sizeof(buf));
if (buf[0] == '\0') *matches_p = false;
else {
/* if this isn't a "host" record, it can't match. */
if (strcmp(buf, "host") != 0) {
*matches_p = false;
read_through_eol(file);
} else {
/* It's a "host" record. Read the database name field. */
next_token(file, buf, sizeof(buf));
if (buf[0] == '\0') *matches_p = false;
else {
/* If this record isn't for our database, ignore it. */
if (strcmp(buf, database) != 0 && strcmp(buf, "all") != 0) {
*matches_p = false;
read_through_eol(file);
} else {
/* Read the IP address field */
next_token(file, buf, sizeof(buf));
if (buf[0] == '\0') *matches_p = false;
else {
int valid; /* Field is valid dotted decimal */
/* Remember the IP address field and go get mask field */
struct in_addr file_ip_addr; /* IP address field value */
valid = inet_aton(buf, &file_ip_addr);
if (!valid) {
*matches_p = false;
read_through_eol(file);
} else {
/* Read the mask field */
next_token(file, buf, sizeof(buf));
if (buf[0] == '\0') *matches_p = false;
else {
struct in_addr mask;
/* Got mask. Now see if this record is for our host. */
valid = inet_aton(buf, &mask);
if (!valid) {
*matches_p = false;
read_through_eol(file);
} else {
if (((file_ip_addr.s_addr ^ ip_addr.s_addr) & mask.s_addr)
!= 0x0000) {
*matches_p = false;
read_through_eol(file);
} else {
/* This is the record we're looking for. Read
the rest of the info from it.
*/
read_hba_entry2(file, userauth_p, usermap_name,
error_p);
*matches_p = true;
if (*error_p) {
sprintf(PQerrormsg,
"process_hba_record: invalid syntax in "
"hba config file "
"for host record for IP address %s\n",
inet_ntoa(file_ip_addr));
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
}
}
}
}
}
}
}
}
}
}
}
static void
process_open_config_file(FILE *file,
const struct in_addr ip_addr, const char database[],
bool *host_ok_p, enum Userauth *userauth_p,
char usermap_name[] ) {
/*---------------------------------------------------------------------------
This function does the same thing as find_hba_entry, only with
the config file already open on stream descriptor "file".
----------------------------------------------------------------------------*/
bool found_entry;
/* We've processed a record that applies to our connection */
bool error;
/* Said record has invalid syntax. */
bool eof; /* We've reached the end of the file we're reading */
found_entry = false; /* initial value */
error = false; /* initial value */
eof = false; /* initial value */
while (!eof && !found_entry && !error) {
/* Process a line from the config file */
int c; /* a character read from the file */
c = getc(file); ungetc(c, file);
if (c == EOF) eof = true;
else {
if (c == '#') read_through_eol(file);
else {
process_hba_record(file, ip_addr, database,
&found_entry, &error, userauth_p, usermap_name);
}
}
}
if (found_entry) {
if (error) *host_ok_p = false;
else *host_ok_p = true;
} else *host_ok_p = false;
}
static void
find_hba_entry(const char DataDir[], const struct in_addr ip_addr,
const char database[],
bool *host_ok_p, enum Userauth *userauth_p,
char usermap_name[] ) {
/*--------------------------------------------------------------------------
Read the config file and find an entry that allows connection from
host "ip_addr" to database "database". If not found, return
*host_ok_p == false. If found, return *userauth_p and *usermap_name
representing the contents of that entry.
When a record has invalid syntax, we either ignore it or reject the
connection (depending on where it's invalid). No message or anything.
We need to fix that some day.
If we don't find or can't access the config file, we issue an error
message and deny the connection.
If we find a file by the old name of the config file (pg_hba), we issue
an error message because it probably needs to be converted. He didn't
follow directions and just installed his old hba file in the new database
system.
---------------------------------------------------------------------------*/
int rc;
struct stat statbuf;
FILE *file; /* The config file we have to read */
char *old_conf_file;
/* The name of old config file that better not exist. */
/* Fail if config file by old name exists. */
/* put together the full pathname to the old config file */
old_conf_file = (char *) malloc((strlen(DataDir) +
strlen(OLD_CONF_FILE)+2)*sizeof(char));
sprintf(old_conf_file, "%s/%s", DataDir, OLD_CONF_FILE);
rc = stat(old_conf_file, &statbuf);
if (rc == 0) {
/* Old config file exists. Tell this guy he needs to upgrade. */
sprintf(PQerrormsg,
"A file exists by the name used for host-based authentication "
"in prior releases of Postgres (%s). The name and format of "
"the configuration file have changed, so this file should be "
"converted.\n",
old_conf_file);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
} else {
char *conf_file; /* The name of the config file we have to read */
/* put together the full pathname to the config file */
conf_file = (char *) malloc((strlen(DataDir) +
strlen(CONF_FILE)+2)*sizeof(char));
sprintf(conf_file, "%s/%s", DataDir, CONF_FILE);
file = fopen(conf_file, "r");
if (file == 0) {
/* The open of the config file failed. */
const int open_errno = errno;
*host_ok_p = false;
sprintf(PQerrormsg,
"find_hba_entry: Host-based authentication config file "
"does not exist or permissions are not setup correctly! "
"Unable to open file \"%s\".\n",
conf_file);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
} else {
process_open_config_file(file, ip_addr, database, host_ok_p, userauth_p,
usermap_name);
fclose(file);
}
free(conf_file);
}
free(old_conf_file);
return;
}
static void
interpret_ident_response(char ident_response[],
bool *error_p, char ident_username[]) {
/*----------------------------------------------------------------------------
Parse the string "ident_response[]" as a response from a query to an Ident
server. If it's a normal response indicating a username, return
*error_p == false and the username as ident_username[]. If it's anything
else, return *error_p == true and ident_username[] undefined.
----------------------------------------------------------------------------*/
char *cursor; /* Cursor into ident_response[] */
cursor = &ident_response[0];
/* Ident's response, in the telnet tradition, should end in crlf (\r\n). */
if (strlen(ident_response) < 2) *error_p = true;
else if (ident_response[strlen(ident_response)-2] != '\r') *error_p = true;
else {
while (*cursor != ':' && *cursor != '\r') cursor++; /* skip port field */
if (*cursor != ':') *error_p = true;
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 (isblank(*cursor)) cursor++; /* skip blanks */
i = 0;
while (*cursor != ':' && *cursor != '\r' && !isblank(*cursor)
&& i < sizeof(response_type)-1)
response_type[i++] = *cursor++;
response_type[i] = '\0';
while (isblank(*cursor)) cursor++; /* skip blanks */
if (strcmp(response_type, "USERID") != 0)
*error_p = true;
else {
/* It's a USERID response. Good. "cursor" should be pointing to
the colon that precedes the operating system type.
*/
if (*cursor != ':') *error_p = true;
else {
cursor++; /* Go over colon */
/* Skip over operating system field. */
while (*cursor != ':' && *cursor != '\r') cursor++;
if (*cursor != ':') *error_p = true;
else {
int i; /* Index into ident_username[] */
cursor ++; /* Go over colon */
while (isblank(*cursor)) cursor++; /* skip blanks */
/* Rest of line is username. Copy it over. */
i = 0;
while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
ident_username[i++] = *cursor++;
ident_username[i] = '\0';
*error_p = false;
}
}
}
}
}
}
static void
ident(const struct in_addr remote_ip_addr, const struct in_addr local_ip_addr,
const ushort remote_port, const ushort local_port,
bool *ident_failed, char ident_username[]) {
/*--------------------------------------------------------------------------
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 username the
ident server gives as "ident_username[]".
IP addresses and port numbers are in network byte order.
But iff we're unable to get the information from ident, return
*ident_failed == true (and ident_username[] undefined).
----------------------------------------------------------------------------*/
int sock_fd;
/* File descriptor for socket on which we talk to Ident */
int rc; /* Return code from a locally called function */
sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (sock_fd == -1) {
sprintf(PQerrormsg,
"Failed to create socket on which to talk to Ident server. "
"socket() returned errno = %s (%d)\n",
strerror(errno), errno);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
} else {
struct sockaddr_in ident_server;
/* Socket address of Ident server on the system from which client
is attempting to connect to us.
*/
ident_server.sin_family = AF_INET;
ident_server.sin_port = htons(IDENT_PORT);
ident_server.sin_addr = remote_ip_addr;
rc = connect(sock_fd,
(struct sockaddr *) &ident_server, sizeof(ident_server));
if (rc != 0) {
sprintf(PQerrormsg,
"Unable to connect to Ident server on the host which is "
"trying to connect to Postgres "
"(IP address %s, Port %d). "
"errno = %s (%d)\n",
inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
*ident_failed = true;
} else {
char ident_query[80];
/* The query we send to the Ident server */
sprintf(ident_query, "%d,%d\n",
ntohs(remote_port), ntohs(local_port));
rc = send(sock_fd, ident_query, strlen(ident_query), 0);
if (rc < 0) {
sprintf(PQerrormsg,
"Unable to send query to Ident server on the host which is "
"trying to connect to Postgres (Host %s, Port %s),"
"even though we successfully connected to it. "
"errno = %s (%d)\n",
inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
*ident_failed = true;
} else {
char ident_response[80+IDENT_USERNAME_MAX];
rc = recv(sock_fd, ident_response, sizeof(ident_response), 0);
if (rc < 0) {
sprintf(PQerrormsg,
"Unable to receive response from Ident server "
"on the host which is "
"trying to connect to Postgres (Host %s, Port %s),"
"even though we successfully sent our query to it. "
"errno = %s (%d)\n",
inet_ntoa(remote_ip_addr), IDENT_PORT,
strerror(errno), errno);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
*ident_failed = true;
} else {
bool error; /* response from Ident is garbage. */
interpret_ident_response(ident_response, &error, ident_username);
*ident_failed = error;
}
}
close(sock_fd);
}
}
}
static void
parse_map_record(FILE *file,
char file_map[], char file_pguser[], char file_iuser[]) {
/*---------------------------------------------------------------------------
Take the noncomment line which is next on file "file" and interpret
it as a line in a usermap file. Specifically, return the first
3 tokens as file_map, file_iuser, and file_pguser, respectively. If
there are fewer than 3 tokens, return null strings for the missing
ones.
---------------------------------------------------------------------------*/
char buf[MAX_TOKEN];
/* A token read from the file */
/* Set defaults in case fields not in file */
file_map[0] = '\0';
file_pguser[0] = '\0';
file_iuser[0] = '\0';
next_token(file, buf, sizeof(buf));
if (buf != '\0') {
strcpy(file_map, buf);
next_token(file, buf, sizeof(buf));
if (buf != '\0') {
strcpy(file_iuser, buf);
next_token(file, buf, sizeof(buf));
if (buf != '\0') {
strcpy(file_pguser, buf);
read_through_eol(file);
}
}
}
}
static void
verify_against_open_usermap(FILE *file,
const char pguser[],
const char ident_username[],
const char usermap_name[],
bool *checks_out_p) {
/*--------------------------------------------------------------------------
This function does the same thing as verify_against_usermap,
only with the config file already open on stream descriptor "file".
---------------------------------------------------------------------------*/
bool match; /* We found a matching entry in the map file */
bool eof; /* We've reached the end of the file we're reading */
match = false; /* initial value */
eof = false; /* initial value */
while (!eof && !match) {
/* Process a line from the map file */
int c; /* a character read from the file */
c = getc(file); ungetc(c, file);
if (c == EOF) eof = true;
else {
if (c == '#') read_through_eol(file);
else {
/* The following are fields read from a record of the file */
char file_map[MAX_TOKEN+1];
char file_pguser[MAX_TOKEN+1];
char file_iuser[MAX_TOKEN+1];
parse_map_record(file, file_map, file_pguser, file_iuser);
if (strcmp(file_map, usermap_name) == 0 &&
strcmp(file_pguser, pguser) == 0 &&
strcmp(file_iuser, ident_username) == 0)
match = true;
}
}
}
*checks_out_p = match;
}
static void
verify_against_usermap(const char DataDir[],
const char pguser[],
const char ident_username[],
const char usermap_name[],
bool *checks_out_p) {
/*--------------------------------------------------------------------------
See if the user with ident username "ident_username" is allowed to act
as Postgres user "pguser" according to usermap "usermap_name". Look
it up in the usermap file.
Special case: For usermap "sameuser", don't look in the usermap
file. That's an implied map where "pguser" must be identical to
"ident_username" in order to be authorized.
Iff authorized, return *checks_out_p == true.
--------------------------------------------------------------------------*/
if (usermap_name[0] == '\0') {
*checks_out_p = false;
sprintf(PQerrormsg,
"verify_against_usermap: hba configuration file does not "
"have the usermap field filled in in the entry that pertains "
"to this connection. That field is essential for Ident-based "
"authentication.\n");
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
} else if (strcmp(usermap_name, "sameuser") == 0) {
if (strcmp(ident_username, pguser) == 0) *checks_out_p = true;
else *checks_out_p = false;
} else {
FILE *file; /* The map file we have to read */
char *map_file; /* The name of the map file we have to read */
/* put together the full pathname to the map file */
map_file = (char *) malloc((strlen(DataDir) +
strlen(MAP_FILE)+2)*sizeof(char));
sprintf(map_file, "%s/%s", DataDir, MAP_FILE);
file = fopen(map_file, "r");
if (file == 0) {
/* The open of the map file failed. */
const int open_errno = errno;
*checks_out_p = false;
sprintf(PQerrormsg,
"verify_against_usermap: usermap file for Ident-based "
"authentication "
"does not exist or permissions are not setup correctly! "
"Unable to open file \"%s\".\n",
map_file);
fputs(PQerrormsg, stderr);
pqdebug("%s", PQerrormsg);
} else {
verify_against_open_usermap(file,
pguser, ident_username, usermap_name,
checks_out_p);
fclose(file);
}
free(map_file);
}
}
static void
authident(const char DataDir[],
const Port port, const char postgres_username[],
const char usermap_name[],
bool *authentic_p) {
/*---------------------------------------------------------------------------
Talk to the ident server on the remote host and find out who owns the
connection described by "port". Then look in the usermap file under
the usermap usermap_name[] and see if that user is equivalent to
Postgres user user[].
Return *authentic_p true iff yes.
---------------------------------------------------------------------------*/
bool ident_failed;
/* We were unable to get ident to give us a username */
char ident_username[IDENT_USERNAME_MAX+1];
/* The username returned by ident */
ident(port.raddr.sin_addr, port.laddr.sin_addr,
port.raddr.sin_port, port.laddr.sin_port,
&ident_failed, ident_username);
if (ident_failed) *authentic_p = false;
else {
bool checks_out;
verify_against_usermap(DataDir,
postgres_username, ident_username, usermap_name,
&checks_out);
if (checks_out) *authentic_p = true;
else *authentic_p = false;
}
}
extern int
hba_recvauth(const Port *port, const char database[], const char user[],
const char DataDir[]) {
/*---------------------------------------------------------------------------
Determine if the TCP connection described by "port" is with someone
allowed to act as user "user" and access database "database". Return
STATUS_OK if yes; STATUS_ERROR if not.
----------------------------------------------------------------------------*/
bool host_ok;
/* There's an entry for this database and remote host in the pg_hba file */
char usermap_name[USERMAP_NAME_SIZE+1];
/* The name of the map pg_hba specifies for this connection (or special
value "SAMEUSER")
*/
enum Userauth userauth;
/* The type of user authentication pg_hba specifies for this connection */
int retvalue;
/* Our eventual return value */
find_hba_entry(DataDir, port->raddr.sin_addr, database,
&host_ok, &userauth, usermap_name);
if (!host_ok) retvalue = STATUS_ERROR;
else {
switch (userauth) {
case Trust:
retvalue = STATUS_OK;
break;
case Ident: {
/* Here's where we need to call up ident and authenticate the user */
bool authentic; /* He is who he says he is. */
authident(DataDir, *port, user, usermap_name, &authentic);
if (authentic) retvalue = STATUS_OK;
else retvalue = STATUS_ERROR;
}
break;
default:
Assert(false);
}
}
return(retvalue);
}
/*----------------------------------------------------------------
* This version of hba was written by Bryan Henderson
* in September 1996 for Release 6.0. It changed the format of the
* hba file and added ident function.
*
* Here are some notes about the original host based authentication
* the preceded this one.
*
* based on the securelib package originally written by William
* LeFebvre, EECS Department, Northwestern University
* (phil@eecs.nwu.edu) - orginal configuration file code handling
* by Sam Horrocks (sam@ics.uci.edu)
*
* modified and adapted for use with Postgres95 by Paul Fisher
* (pnfisher@unity.ncsu.edu)
*
-----------------------------------------------------------------*/

View File

@ -0,0 +1,100 @@
#
# Example Postgres95 host access control file.
#
#
# This file controls what hosts are allowed to connect to what databases
# and specifies some options on how users on a particular host are identified.
#
# Each line (terminated by a newline character) is a record. A record cannot
# be continued across two lines.
#
# There are 3 kinds of records:
#
# 1) comment: Starts with #.
#
# 2) empty: Contains nothing excepting spaces and tabs.
#
# 3) content: anything else.
#
# Unless specified otherwise, "record" from here on means a content
# record.
#
# A record consists of tokens separated by spaces or tabs. Spaces and
# tabs at the beginning and end of a record are ignored as are extra
# spaces and tabs between two tokens.
#
# The first token in a record is the record type. The interpretation of the
# rest of the record depends on the record type.
#
# Record type "host"
# ------------------
#
# This record identifies a set of hosts that are permitted to connect to
# databases. No hosts are permitted to connect except as specified by a
# "host" record.
#
# Format:
#
# host DBNAME IP_ADDRESS ADDRESS_MASK USERAUTH [MAP]
#
# DBNAME is the name of a Postgres database, or "all" to indicate all
# databases.
#
# IP_ADDRESS and ADDRESS_MASK are a standard dotted decimal IP address and
# mask to identify a set of hosts. These hosts are allowed to connect to
# Database DBNAME.
#
# USERAUTH is a keyword indicating the method used to authenticate the
# user, i.e. to determine that the principal is authorized to connect
# under the Postgres username he supplies in his connection parameters.
#
# ident: Authentication is done by the ident server on the remote
# host, via the ident (RFC 1413) protocol.
#
# trust: No authentication is done. Trust that the user has the
# authority to user whatever username he says he does.
# Before Postgres Version 6, all authentication was this way.
#
# MAP is the name of a map that matches an authenticated principal with
# a Postgres username. If USERNAME is "trust", this value is ignored and
# may be absent.
#
# In the case of USERAUTH=ident, this is a map name to be found in the
# pg_ident.conf file. That table maps from ident usernames to Postgres
# usernames. The special map name "sameuser" indicates an implied map
# (not found in pg_ident.conf) that maps every ident username to the identical
# Postgres username.
#
# For backwards compatibility, Postgres also accepts pre-Version 2 records,
# which look like:
#
# all 127.0.0.1 0.0.0.0
#
#
# TYPE DATABASE IP_ADDRESS MASK USERAUTH MAP
host all 127.0.0.1 255.255.255.255 trust
# The above allows any user on the local system to connect to any database
# under any username.
host template1 192.168.0.0 255.255.255.0 ident sameuser
# The above allows any user from any host with IP address 192.168.0.x to
# connect to database template1 as the same username that ident on that host
# identifies him as (typically his Unix username).
#host all 0.0.0.0 0.0.0.0 trust
# The above would allow anyone anywhere to connect to any database under
# any username.
#host all 192.168.0.0 255.255.255.0 ident omicron
#
# The above would allow users from 192.168.0.x hosts to connect to any
# database, but if e.g. Ident says the user is "bryanh" and he requests to
# connect as Postgres user "guest1", the connection is only allowed if
# there is an entry for map "omicron" in pg_ident.conf that says "bryanh" is
# allowed to connect as "guest1".

View File

@ -1,13 +0,0 @@
#
# Example config file for Postgres95 host based access
#
# Lines starting with "all" apply to all databases. Otherwise the first
# column has to match the name of the database being connected to. Up to
# ten config lines can apply to each database. Mask specifies bits that
# aren't counted. After those bits are taken out, the connection address
# must match the address in the middle column.
#
# <name> <address> <mask>
#
all 127.0.0.1 0.0.0.0

View File

@ -0,0 +1,25 @@
# This is the pg_ident.conf file, which is used with Postgres ident-based
# authentication (a subtype of host-based authentication).
# This is a table of ident usernames (typically Unix usernames) and
# their corresponding Postgres usernames. For example, user "bryanh" on
# some particular remote system may equate to Postgres user "guest1".
# This file contains multiple maps. Each has a name. The pg_hba.conf
# file determines what connections relate to this file and for those that
# do, which map to use.
# Each record consists of 3 tokens:
#
# 1) map name
# 2) ident username
# 3) Postgres username
# Note that it is possible for one user to map to multiple Postgres usernames.
# A user always has to specify when he connects what Postgres username he is
# using. This file is only used to validate that selection.
testmap robert bob
testmap lucy lucy