From 4b5c97778283f07833b74efd52a32370fe9e0380 Mon Sep 17 00:00:00 2001 From: Bryan Henderson Date: Sat, 12 Oct 1996 07:47:12 +0000 Subject: [PATCH] New host-based authentication with ident --- src/backend/libpq/auth.c | 393 +++++-------- src/backend/libpq/hba.c | 768 +++++++++++++++++++++++++ src/backend/libpq/pg_hba.conf.sample | 100 ++++ src/backend/libpq/pg_hba.sample | 13 - src/backend/libpq/pg_ident.conf.sample | 25 + 5 files changed, 1030 insertions(+), 269 deletions(-) create mode 100644 src/backend/libpq/hba.c create mode 100644 src/backend/libpq/pg_hba.conf.sample delete mode 100644 src/backend/libpq/pg_hba.sample create mode 100644 src/backend/libpq/pg_ident.conf.sample diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index ae02dfb632..8c253f5c39 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -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 #include -#include "libpq/auth.h" -#include "libpq/libpq.h" -#include "libpq/pqcomm.h" -#include "libpq/libpq-be.h" +#include +#include +#include +#include /*---------------------------------------------------------------- * 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) -{ - u_long ip_addr; - static struct conf_line conf[MAX_LINES]; - static int nconf; - int i; - - 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) +pg_krb5_recvauth(int sock, + struct sockaddr_in *laddr, + struct sockaddr_in *raddr, + char *username) { - 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'; + (void) sprintf(PQerrormsg, + "pg_krb5_recvauth: Kerberos not implemented on this " + "server.\n"); + fputs(PQerrormsg, stderr); + pqdebug("%s", PQerrormsg); - /* 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); } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c new file mode 100644 index 0000000000..0d3b6b4f33 --- /dev/null +++ b/src/backend/libpq/hba.c @@ -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 +#include +#include +#include /* isspace() declaration */ +#include +#include +#include + +#include +#include +#include +#include + + +#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) + * + -----------------------------------------------------------------*/ + diff --git a/src/backend/libpq/pg_hba.conf.sample b/src/backend/libpq/pg_hba.conf.sample new file mode 100644 index 0000000000..d17dc5fbb2 --- /dev/null +++ b/src/backend/libpq/pg_hba.conf.sample @@ -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". diff --git a/src/backend/libpq/pg_hba.sample b/src/backend/libpq/pg_hba.sample deleted file mode 100644 index 22a83beb09..0000000000 --- a/src/backend/libpq/pg_hba.sample +++ /dev/null @@ -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. -# -#
-# -all 127.0.0.1 0.0.0.0 - diff --git a/src/backend/libpq/pg_ident.conf.sample b/src/backend/libpq/pg_ident.conf.sample new file mode 100644 index 0000000000..9baff6a191 --- /dev/null +++ b/src/backend/libpq/pg_ident.conf.sample @@ -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 + + -- 2.40.0