aboutsummaryrefslogtreecommitdiff
path: root/src/backend/libpq/auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/libpq/auth.c')
-rw-r--r--src/backend/libpq/auth.c668
1 files changed, 668 insertions, 0 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
new file mode 100644
index 00000000000..7b4437736f4
--- /dev/null
+++ b/src/backend/libpq/auth.c
@@ -0,0 +1,668 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth.c--
+ * Routines to handle network authentication
+ *
+ * Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.1.1.1 1996/07/09 06:21:30 scrappy Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * INTERFACE ROUTINES
+ *
+ * backend (postmaster) routines:
+ * be_recvauth receive authentication information
+ * be_setauthsvc do/do not permit an authentication service
+ * be_getauthsvc is an authentication service permitted?
+ *
+ * NOTES
+ * To add a new authentication system:
+ * 0. If you can't do your authentication over an existing socket,
+ * you lose -- get ready to hack around this framework instead of
+ * using it. Otherwise, you can assume you have an initialized
+ * and empty connection to work with. (Please don't leave leftover
+ * gunk in the connection after the authentication transactions, or
+ * the POSTGRES routines that follow will be very unhappy.)
+ * 1. Write a set of routines that:
+ * let a client figure out what user/principal name to use
+ * send authentication information (client side)
+ * receive authentication information (server side)
+ * You can include both routines in this file, using #ifdef FRONTEND
+ * to separate them.
+ * 2. Edit libpq/pqcomm.h and assign a MsgType for your protocol.
+ * 3. Edit the static "struct authsvc" array and the generic
+ * {be,fe}_{get,set}auth{name,svc} routines in this file to reflect
+ * the new service. You may have to change the arguments of these
+ * routines; they basically just reflect what Kerberos v4 needs.
+ * 4. Hack on src/{,bin}/Makefile.global and src/{backend,libpq}/Makefile
+ * to add library and CFLAGS hooks -- basically, grep the Makefile
+ * hierarchy for KRBVERS to see where you need to add things.
+ *
+ * Send mail to post_hackers@postgres.Berkeley.EDU if you have to make
+ * any changes to arguments, etc. Context diffs would be nice, too.
+ *
+ * Someday, this cruft will go away and magically be replaced by a
+ * nice interface based on the GSS API or something. For now, though,
+ * there's no (stable) UNIX security API to work with...
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <sys/param.h> /* for MAX{HOSTNAME,PATH}LEN, NOFILE */
+#include <pwd.h>
+#include <ctype.h> /* isspace() declaration */
+
+#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"
+
+/*----------------------------------------------------------------
+ * common definitions for generic fe/be routines
+ *----------------------------------------------------------------
+ */
+
+struct authsvc {
+ char name[16]; /* service nickname (for command line) */
+ MsgType msgtype; /* startup packet header type */
+ int allowed; /* initially allowed (before command line
+ * option parsing)?
+ */
+};
+
+/*
+ * Command-line parsing routines use this structure to map nicknames
+ * onto service types (and the startup packets to use with them).
+ *
+ * Programs receiving an authentication request use this structure to
+ * decide which authentication service types are currently permitted.
+ * By default, all authentication systems compiled into the system are
+ * allowed. Unauthenticated connections are disallowed unless there
+ * isn't any authentication system.
+ */
+static struct authsvc authsvcs[] = {
+#ifdef KRB4
+ { "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) */
+ }
+};
+
+static n_authsvcs = sizeof(authsvcs) / sizeof(struct authsvc);
+
+#ifdef KRB4
+/*----------------------------------------------------------------
+ * MIT Kerberos authentication system - protocol version 4
+ *----------------------------------------------------------------
+ */
+
+#include "krb.h"
+
+#ifdef FRONTEND
+/* moves to src/libpq/fe-auth.c */
+#else /* !FRONTEND */
+
+/*
+ * pg_krb4_recvauth -- server routine to receive authentication information
+ * from the client
+ *
+ * Nothing unusual here, except that we compare the username obtained from
+ * the client's setup packet to the authenticated name. (We have to retain
+ * the name in the setup packet since we have to retain the ability to handle
+ * unauthenticated connections.)
+ */
+static int
+pg_krb4_recvauth(int sock,
+ struct sockaddr_in *laddr,
+ struct sockaddr_in *raddr,
+ char *username)
+{
+ long krbopts = 0; /* one-way authentication */
+ KTEXT_ST clttkt;
+ char instance[INST_SZ];
+ AUTH_DAT auth_data;
+ Key_schedule key_sched;
+ char version[KRB_SENDAUTH_VLEN];
+ int status;
+
+ strcpy(instance, "*"); /* don't care, but arg gets expanded anyway */
+ status = krb_recvauth(krbopts,
+ sock,
+ &clttkt,
+ PG_KRB_SRVNAM,
+ instance,
+ raddr,
+ laddr,
+ &auth_data,
+ PG_KRB_SRVTAB,
+ key_sched,
+ version);
+ if (status != KSUCCESS) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb4_recvauth: kerberos error: %s\n",
+ krb_err_txt[status]);
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ return(STATUS_ERROR);
+ }
+ if (strncmp(version, PG_KRB4_VERSION, KRB_SENDAUTH_VLEN)) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb4_recvauth: protocol version != \"%s\"\n",
+ PG_KRB4_VERSION);
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ return(STATUS_ERROR);
+ }
+ if (username && *username &&
+ strncmp(username, auth_data.pname, NAMEDATALEN)) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb4_recvauth: name \"%s\" != \"%s\"\n",
+ username,
+ auth_data.pname);
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ return(STATUS_ERROR);
+ }
+ return(STATUS_OK);
+}
+
+#endif /* !FRONTEND */
+
+#endif /* KRB4 */
+
+#ifdef KRB5
+/*----------------------------------------------------------------
+ * MIT Kerberos authentication system - protocol version 5
+ *----------------------------------------------------------------
+ */
+
+#include "krb5/krb5.h"
+
+/*
+ * pg_an_to_ln -- return the local name corresponding to an authentication
+ * name
+ *
+ * XXX Assumes that the first aname component is the user name. This is NOT
+ * necessarily so, since an aname can actually be something out of your
+ * worst X.400 nightmare, like
+ * ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
+ * Note that the MIT an_to_ln code does the same thing if you don't
+ * provide an aname mapping database...it may be a better idea to use
+ * krb5_an_to_ln, except that it punts if multiple components are found,
+ * and we can't afford to punt.
+ */
+static char *
+pg_an_to_ln(char *aname)
+{
+ char *p;
+
+ if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
+ *p = '\0';
+ return(aname);
+}
+
+#ifdef FRONTEND
+/* moves to src/libpq/fe-auth.c */
+#else /* !FRONTEND */
+
+/*
+ * pg_krb4_recvauth -- server routine to receive authentication information
+ * from the client
+ *
+ * We still need to compare the username obtained from the client's setup
+ * packet to the authenticated name, as described in pg_krb4_recvauth. This
+ * is a bit more problematic in v5, as described above in pg_an_to_ln.
+ *
+ * In addition, as described above in pg_krb5_sendauth, we still need to
+ * canonicalize the server name v4-style before constructing a principal
+ * from it. Again, this is kind of iffy.
+ *
+ * Finally, we need to tangle with the fact that v5 doesn't let you explicitly
+ * set server keytab file names -- you have to feed lower-level routines a
+ * function to retrieve the contents of a keytab, along with a single argument
+ * that allows them to open the keytab. We assume that a server keytab is
+ * always a real file so we can allow people to specify their own filenames.
+ * (This is important because the POSTGRES keytab needs to be readable by
+ * non-root users/groups; the v4 tools used to force you do dump a whole
+ * host's worth of keys into a file, effectively forcing you to use one file,
+ * but kdb5_edit allows you to select which principals to dump. Yay!)
+ */
+static int
+pg_krb5_recvauth(int sock,
+ struct sockaddr_in *laddr,
+ struct sockaddr_in *raddr,
+ char *username)
+{
+ char servbuf[MAXHOSTNAMELEN + 1 +
+ sizeof(PG_KRB_SRVNAM)];
+ char *hostp, *kusername = (char *) NULL;
+ krb5_error_code code;
+ krb5_principal client, server;
+ krb5_address sender_addr;
+ krb5_rdreq_key_proc keyproc = (krb5_rdreq_key_proc) NULL;
+ krb5_pointer keyprocarg = (krb5_pointer) NULL;
+
+ /*
+ * Set up server side -- since we have no ticket file to make this
+ * easy, we construct our own name and parse it. See note on
+ * canonicalization above.
+ */
+ (void) strcpy(servbuf, PG_KRB_SRVNAM);
+ *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
+ if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
+ (void) strcpy(hostp, "localhost");
+ if (hostp = strchr(hostp, '.'))
+ *hostp = '\0';
+ if (code = krb5_parse_name(servbuf, &server)) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb5_recvauth: Kerberos error %d in krb5_parse_name\n",
+ code);
+ com_err("pg_krb5_recvauth", code, "in krb5_parse_name");
+ return(STATUS_ERROR);
+ }
+
+ /*
+ * krb5_sendauth needs this to verify the address in the client
+ * authenticator.
+ */
+ sender_addr.addrtype = raddr->sin_family;
+ sender_addr.length = sizeof(raddr->sin_addr);
+ sender_addr.contents = (krb5_octet *) &(raddr->sin_addr);
+
+ if (strcmp(PG_KRB_SRVTAB, "")) {
+ keyproc = krb5_kt_read_service_key;
+ keyprocarg = PG_KRB_SRVTAB;
+ }
+
+ if (code = krb5_recvauth((krb5_pointer) &sock,
+ PG_KRB5_VERSION,
+ server,
+ &sender_addr,
+ (krb5_pointer) NULL,
+ keyproc,
+ keyprocarg,
+ (char *) NULL,
+ (krb5_int32 *) NULL,
+ &client,
+ (krb5_ticket **) NULL,
+ (krb5_authenticator **) NULL)) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb5_recvauth: Kerberos error %d in krb5_recvauth\n",
+ code);
+ com_err("pg_krb5_recvauth", code, "in krb5_recvauth");
+ krb5_free_principal(server);
+ return(STATUS_ERROR);
+ }
+ krb5_free_principal(server);
+
+ /*
+ * The "client" structure comes out of the ticket and is therefore
+ * authenticated. Use it to check the username obtained from the
+ * postmaster startup packet.
+ */
+ if ((code = krb5_unparse_name(client, &kusername))) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb5_recvauth: Kerberos error %d in krb5_unparse_name\n",
+ code);
+ com_err("pg_krb5_recvauth", code, "in krb5_unparse_name");
+ krb5_free_principal(client);
+ return(STATUS_ERROR);
+ }
+ krb5_free_principal(client);
+ if (!kusername) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb5_recvauth: could not decode username\n");
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ return(STATUS_ERROR);
+ }
+ kusername = pg_an_to_ln(kusername);
+ if (username && strncmp(username, kusername, NAMEDATALEN)) {
+ (void) sprintf(PQerrormsg,
+ "pg_krb5_recvauth: name \"%s\" != \"%s\"\n",
+ username, kusername);
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ free(kusername);
+ return(STATUS_ERROR);
+ }
+ free(kusername);
+ return(STATUS_OK);
+}
+
+#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.
+ */
+
+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(GetPGData())+strlen(CONF_FILE)+2)*sizeof(char));
+ strcpy(conf_file, GetPGData());
+ strcat(conf_file, "/");
+ strcat(conf_file, 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: config file does not exist or permissions are not setup correctly!\n");
+ 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);
+}
+
+/*
+ * be_recvauth -- server demux routine for incoming authentication information
+ */
+int
+be_recvauth(MsgType msgtype, Port *port, char *username, StartupInfo* sp)
+{
+ if (!username) {
+ (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);
+ }
+
+ 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 (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);
+ }
+ 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;
+ 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;
+ default:
+ (void) sprintf(PQerrormsg,
+ "be_recvauth: unrecognized message type: %d\n",
+ msgtype);
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ return(STATUS_ERROR);
+ }
+ return(STATUS_OK);
+}
+
+/*
+ * be_setauthsvc -- enable/disable the authentication services currently
+ * selected for use by the backend
+ * be_getauthsvc -- returns whether a particular authentication system
+ * (indicated by its message type) is permitted by the
+ * current selections
+ *
+ * be_setauthsvc encodes the command-line syntax that
+ * -a "<service-name>"
+ * enables a service, whereas
+ * -a "no<service-name>"
+ * disables it.
+ */
+void
+be_setauthsvc(char *name)
+{
+ int i, j;
+ int turnon = 1;
+
+ if (!name)
+ return;
+ if (!strncmp("no", name, 2)) {
+ turnon = 0;
+ name += 2;
+ }
+ if (name[0] == '\0')
+ return;
+ for (i = 0; i < n_authsvcs; ++i)
+ if (!strcmp(name, authsvcs[i].name)) {
+ for (j = 0; j < n_authsvcs; ++j)
+ if (authsvcs[j].msgtype == authsvcs[i].msgtype)
+ authsvcs[j].allowed = turnon;
+ break;
+ }
+ if (i == n_authsvcs) {
+ (void) sprintf(PQerrormsg,
+ "be_setauthsvc: invalid name %s, ignoring...\n",
+ name);
+ fputs(PQerrormsg, stderr);
+ pqdebug("%s", PQerrormsg);
+ }
+ return;
+}
+
+int
+be_getauthsvc(MsgType msgtype)
+{
+ int i;
+
+ for (i = 0; i < n_authsvcs; ++i)
+ if (msgtype == authsvcs[i].msgtype)
+ return(authsvcs[i].allowed);
+ return(0);
+}