diff options
Diffstat (limited to 'src/backend/libpq/auth.c')
-rw-r--r-- | src/backend/libpq/auth.c | 668 |
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); +} |