diff options
author | Magnus Hagander <magnus@hagander.net> | 2007-07-10 13:14:22 +0000 |
---|---|---|
committer | Magnus Hagander <magnus@hagander.net> | 2007-07-10 13:14:22 +0000 |
commit | 6160106c7495509310b709ee320b55cea8a60928 (patch) | |
tree | 3a1be553f436ebf563d6ec4aaae3eff42d615db9 /src/backend/libpq/auth.c | |
parent | ff481ca0d463d33246a331c92bdf3dfc018c24eb (diff) | |
download | postgresql-6160106c7495509310b709ee320b55cea8a60928.tar.gz postgresql-6160106c7495509310b709ee320b55cea8a60928.zip |
Add support for GSSAPI authentication.
Documentation still being written, will be committed later.
Henry B. Hotz and Magnus Hagander
Diffstat (limited to 'src/backend/libpq/auth.c')
-rw-r--r-- | src/backend/libpq/auth.c | 273 |
1 files changed, 272 insertions, 1 deletions
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index 37bfa81abdf..dafc965445e 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.148 2007/02/08 04:52:18 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/auth.c,v 1.149 2007/07/10 13:14:20 mha Exp $ * *------------------------------------------------------------------------- */ @@ -23,6 +23,7 @@ #endif #include <netinet/in.h> #include <arpa/inet.h> +#include <unistd.h> #include "libpq/auth.h" #include "libpq/crypt.h" @@ -295,6 +296,250 @@ pg_krb5_recvauth(Port *port) } #endif /* KRB5 */ +#ifdef ENABLE_GSS +/*---------------------------------------------------------------- + * GSSAPI authentication system + *---------------------------------------------------------------- + */ + +#include <gssapi/gssapi.h> + +#ifdef WIN32 +/* + * MIT Kerberos GSSAPI DLL doesn't properly export the symbols + * that contain the OIDs required. Redefine here, values copied + * from src/athena/auth/krb5/src/lib/gssapi/generic/gssapi_generic.c + */ +static const gss_OID_desc GSS_C_NT_USER_NAME_desc = + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x02"}; +static GSS_DLLIMP gss_OID GSS_C_NT_USER_NAME = &GSS_C_NT_USER_NAME_desc; +#endif + + +static void +pg_GSS_error(int severity, char *text, OM_uint32 maj_stat, OM_uint32 min_stat) +{ + gss_buffer_desc gmsg; + OM_uint32 lmaj_s, lmin_s, msg_ctx; + char localmsg1[128], + localmsg2[128]; + + /* Fetch major status message */ + msg_ctx = 0; + lmaj_s = gss_display_status(&lmin_s, maj_stat, GSS_C_GSS_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(localmsg1, gmsg.value, sizeof(localmsg1)); + gss_release_buffer(&lmin_s, &gmsg); + + if (msg_ctx) + /* More than one message available. + * XXX: Should we loop and read all messages? + * (same below) + */ + ereport(WARNING, + (errmsg_internal("incomplete GSS error report"))); + + /* Fetch mechanism minor status message */ + msg_ctx = 0; + lmaj_s = gss_display_status(&lmin_s, min_stat, GSS_C_MECH_CODE, + GSS_C_NO_OID, &msg_ctx, &gmsg); + strlcpy(localmsg2, gmsg.value, sizeof(localmsg2)); + gss_release_buffer(&lmin_s, &gmsg); + + if (msg_ctx) + ereport(WARNING, + (errmsg_internal("incomplete GSS minor error report"))); + + /* errmsg_internal, since translation of the first part must be + * done before calling this function anyway. */ + ereport(severity, + (errmsg_internal("%s:%s\n%s", text, localmsg1, localmsg2))); +} + +static int +pg_GSS_recvauth(Port *port) +{ + OM_uint32 maj_stat, min_stat, lmin_s, gflags; + char *kt_path; + int mtype; + int ret; + StringInfoData buf; + gss_buffer_desc gbuf; + + if (pg_krb_server_keyfile && strlen(pg_krb_server_keyfile) > 0) + { + /* + * Set default Kerberos keytab file for the Krb5 mechanism. + * + * setenv("KRB5_KTNAME", pg_krb_server_keyfile, 0); + * except setenv() not always available. + */ + if (!getenv("KRB5_KTNAME")) + { + kt_path = palloc(PATH_MAX + 13); + snprintf(kt_path, PATH_MAX + 13, + "KRB5_KTNAME=%s", pg_krb_server_keyfile); + putenv(kt_path); + } + } + + /* + * We accept any service principal that's present in our + * keytab. This increases interoperability between kerberos + * implementations that see for example case sensitivity + * differently, while not really opening up any vector + * of attack. + */ + port->gss->cred = GSS_C_NO_CREDENTIAL; + + /* + * Initialize sequence with an empty context + */ + port->gss->ctx = GSS_C_NO_CONTEXT; + + /* + * Loop through GSSAPI message exchange. This exchange can consist + * of multiple messags sent in both directions. First message is always + * from the client. All messages from client to server are password + * packets (type 'p'). + */ + do + { + mtype = pq_getbyte(); + if (mtype != 'p') + { + /* Only log error if client didn't disconnect. */ + if (mtype != EOF) + ereport(COMMERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("expected GSS response, got message type %d", + mtype))); + return STATUS_ERROR; + } + + /* Get the actual GSS token */ + initStringInfo(&buf); + if (pq_getmessage(&buf, 2000)) + { + /* EOF - pq_getmessage already logged error */ + pfree(buf.data); + return STATUS_ERROR; + } + + /* Map to GSSAPI style buffer */ + gbuf.length = buf.len; + gbuf.value = buf.data; + + ereport(DEBUG4, + (errmsg_internal("Processing received GSS token of length: %u", + gbuf.length))); + + maj_stat = gss_accept_sec_context( + &min_stat, + &port->gss->ctx, + port->gss->cred, + &gbuf, + GSS_C_NO_CHANNEL_BINDINGS, + &port->gss->name, + NULL, + &port->gss->outbuf, + &gflags, + NULL, + NULL); + + /* gbuf no longer used */ + pfree(buf.data); + + ereport(DEBUG5, + (errmsg_internal("gss_accept_sec_context major: %i, " + "minor: %i, outlen: %u, outflags: %x", + maj_stat, min_stat, + port->gss->outbuf.length, gflags))); + + if (port->gss->outbuf.length != 0) + { + /* + * Negotiation generated data to be sent to the client. + */ + ereport(DEBUG4, + (errmsg_internal("sending GSS response token of length %u", + port->gss->outbuf.length))); + sendAuthRequest(port, AUTH_REQ_GSS_CONT); + } + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + OM_uint32 lmin_s; + gss_delete_sec_context(&lmin_s, &port->gss->ctx, GSS_C_NO_BUFFER); + pg_GSS_error(ERROR, + gettext_noop("accepting GSS security context failed"), + maj_stat, min_stat); + } + + if (maj_stat == GSS_S_CONTINUE_NEEDED) + ereport(DEBUG4, + (errmsg_internal("GSS continue needed"))); + + } while (maj_stat == GSS_S_CONTINUE_NEEDED); + + if (port->gss->cred != GSS_C_NO_CREDENTIAL) + { + /* + * Release service principal credentials + */ + gss_release_cred(&min_stat, port->gss->cred); + } + + /* + * GSS_S_COMPLETE indicates that authentication is now complete. + * + * Get the name of the user that authenticated, and compare it to the + * pg username that was specified for the connection. + */ + maj_stat = gss_display_name(&min_stat, port->gss->name, &gbuf, NULL); + ereport(DEBUG1, + (errmsg("GSSAPI authenticated name: %s", (char *)gbuf.value))); + + /* + * Compare the part of the username that comes before the @ + * sign only (ignore realm). The GSSAPI libraries won't have + * authenticated the user if he's from an invalid realm. + */ + if (strchr(gbuf.value, '@')) + { + char *cp = strchr(gbuf.value, '@'); + *cp = '\0'; + } + + if (pg_krb_caseins_users) + ret = pg_strcasecmp(port->user_name, gbuf.value); + else + ret = strcmp(port->user_name, gbuf.value); + + if (ret) + /* GSS name and PGUSER are not equivalent */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("provided username and GSSAPI username don't match"), + errdetail("provided: %s, GSSAPI: %s", + port->user_name, (char *)gbuf.value))); + + gss_release_buffer(&lmin_s, &gbuf); + + return STATUS_OK; +} + +#else /* no ENABLE_GSS */ +static int +pg_GSS_recvauth(Port *port) +{ + ereport(LOG, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("GSSAPI not implemented on this server."))); + return STATUS_ERROR; +} +#endif /* ENABLE_GSS */ + /* * Tell the user the authentication failed, but not (much about) why. @@ -334,6 +579,9 @@ auth_failed(Port *port, int status) case uaKrb5: errstr = gettext_noop("Kerberos 5 authentication failed for user \"%s\""); break; + case uaGSS: + errstr = gettext_noop("GSSAPI authentication failed for user \"%s\""); + break; case uaTrust: errstr = gettext_noop("\"trust\" authentication failed for user \"%s\""); break; @@ -429,6 +677,11 @@ ClientAuthentication(Port *port) status = pg_krb5_recvauth(port); break; + case uaGSS: + sendAuthRequest(port, AUTH_REQ_GSS); + status = pg_GSS_recvauth(port); + break; + case uaIdent: /* @@ -518,6 +771,24 @@ sendAuthRequest(Port *port, AuthRequest areq) else if (areq == AUTH_REQ_CRYPT) pq_sendbytes(&buf, port->cryptSalt, 2); +#ifdef ENABLE_GSS + /* Add the authentication data for the next step of + * the GSSAPI negotiation. */ + else if (areq == AUTH_REQ_GSS_CONT) + { + if (port->gss->outbuf.length > 0) + { + OM_uint32 lmin_s; + + ereport(DEBUG4, + (errmsg_internal("sending GSS token of length %u", + port->gss->outbuf.length))); + pq_sendbytes(&buf, port->gss->outbuf.value, port->gss->outbuf.length); + gss_release_buffer(&lmin_s, &port->gss->outbuf); + } + } +#endif + pq_endmessage(&buf); /* |