Re: Preliminary GSSAPI Patches - Mailing list pgsql-patches
From | Magnus Hagander |
---|---|
Subject | Re: Preliminary GSSAPI Patches |
Date | |
Msg-id | 467D17AF.5020500@hagander.net Whole thread Raw |
In response to | Re: Preliminary GSSAPI Patches (Magnus Hagander <magnus@hagander.net>) |
List | pgsql-patches |
Magnus Hagander wrote: > Stephen Frost wrote: >> * Henry B. Hotz (hbhotz@oxy.edu) wrote: >>> On Jun 22, 2007, at 9:56 AM, Magnus Hagander wrote: >>>> Most likely it's just checking the keytab to find a principal with the >>>> same name as the one presented from the client. Since one is >>>> present, it >>>> loads it up automatically, and verifies against it. >>> Bingo! >>> >>> The server uses the keytab to decrypt the token provided by the >>> client. By using the GSS_C_NO_CREDENTIAL arg on the server anything >>> put in the keytab is OK. (The server doesn't need to authenticate >>> itself to Kerberos, it just accepts authentication. Mutual >>> authentication is done using the same keys.) The documentation needs >>> to reflect that. >> I agree there's some disconnect there between the documentation and the >> apparent implementation but I'm not sure I'm in favor of changing the >> documentation on this one. Personally, I'd rather it return an error if >> someone tries to use GSS_C_NO_CREDENTIAL when accepting a context than >> to just be happy using anything in the keytab. > > How about doing both, then? Set the principal name if it's specified in > the config file. If it's explicitly set to an empty string, use > GSS_C_NO_CREDENTIAL. Seems straightforward enough to me, and shouldn't > be hard to implement. Here's an updated patch that does this. //Magnus diff -cr pgsql.orig/src/backend/libpq/auth.c pgsql/src/backend/libpq/auth.c *** pgsql.orig/src/backend/libpq/auth.c 2007-02-08 05:52:18.000000000 +0100 --- pgsql/src/backend/libpq/auth.c 2007-06-23 14:42:45.000000000 +0200 *************** *** 23,28 **** --- 23,29 ---- #endif #include <netinet/in.h> #include <arpa/inet.h> + #include <unistd.h> #include "libpq/auth.h" #include "libpq/crypt.h" *************** *** 295,300 **** --- 296,611 ---- } #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 n_eq; + StringInfoData buf; + gss_buffer_desc gbuf; + gss_name_t gnbuf; + + 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); + } + } + + if (pg_krb_srvnam && strlen(pg_krb_srvnam) > 0) + { + /* + * Load service principal credentials + */ + char *hostname; + int len; + + if (!pg_krb_server_hostname || !strlen(pg_krb_server_hostname)) + { + char localhost[NI_MAXHOST]; + + /* + * hostname not specified in config file, so get it from + * the system default. + */ + localhost[NI_MAXHOST-1] = '\0'; + if (gethostname(localhost, NI_MAXHOST-1)) + ereport(ERROR, + (errmsg_internal("gethostname for GSSAPI service principal failed"))); + hostname = localhost; + } + else + hostname = pg_krb_server_hostname; + + len = strlen(hostname) + strlen(pg_krb_srvnam) + 2; + gbuf.value = palloc(len); + snprintf(gbuf.value, len, "%s@%s", pg_krb_srvnam, hostname); + gbuf.length = strlen(gbuf.value); + + ereport(DEBUG4, + (errmsg_internal("Acquiring GSSAPI service credentials for %s", (char *)gbuf.value))); + + maj_stat = gss_import_name(&min_stat, &gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &gnbuf); + pfree(gbuf.value); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, gettext_noop("importing GSS service principal name failed"), maj_stat, min_stat); + + maj_stat = gss_acquire_cred(&min_stat, + gnbuf, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_ACCEPT, + &port->gss->cred, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, gettext_noop("acquiring GSS service principal credentials failed"), maj_stat, min_stat); + + /* + * Clean up the name now that we have the credentials + */ + gss_release_name(&min_stat, &gnbuf); + } + else + { + /* + * No service principal name specified, so accept anything + * the client uses (must still be present in the keytab). + */ + port->gss->cred = GSS_C_NO_CREDENTIAL; + } + + 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))); + gss_release_buffer(&lmin_s, &gbuf); + + /* Convert pg username to GSSAPI format */ + gbuf.value = port->user_name; + gbuf.length = strlen(buf.data) + 1; + maj_stat = gss_import_name(&min_stat, &gbuf, GSS_C_NT_USER_NAME, &gnbuf); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, "importing GSS username failed", + maj_stat, min_stat); + + /* Verify that usernames are identical */ + maj_stat = gss_compare_name(&min_stat, port->gss->name, gnbuf, &n_eq); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, "comparing GSS username failed", + maj_stat, min_stat); + + if (!n_eq) + { + /* GSS name and PGUSER are not equivalent */ + char *namecopy; + + maj_stat = gss_display_name(&min_stat, gnbuf, &gbuf, NULL); + if (maj_stat != GSS_S_COMPLETE) + pg_GSS_error(ERROR, + "displaying GSS form of PGUSER failed", + maj_stat, min_stat); + + namecopy = palloc(gbuf.length); + strlcpy(namecopy, gbuf.value, gbuf.length); + gss_release_buffer(&lmin_s, &gbuf); + gss_release_name(&lmin_s, &gnbuf); + + ereport(ERROR, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("provided username and GSSAPI username don't match"), + errdetail("provided: %s, GSSAPI: %s", + port->user_name, namecopy))); + } + gss_release_name(&lmin_s, &gnbuf); + + 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,339 **** --- 645,653 ---- 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,434 **** --- 743,753 ---- status = pg_krb5_recvauth(port); break; + case uaGSS: + sendAuthRequest(port, AUTH_REQ_GSS); + status = pg_GSS_recvauth(port); + break; + case uaIdent: /* *************** *** 518,523 **** --- 837,860 ---- 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); /* diff -cr pgsql.orig/src/backend/libpq/hba.c pgsql/src/backend/libpq/hba.c *** pgsql.orig/src/backend/libpq/hba.c 2007-02-10 15:58:54.000000000 +0100 --- pgsql/src/backend/libpq/hba.c 2007-06-17 18:01:31.000000000 +0200 *************** *** 602,607 **** --- 602,609 ---- *userauth_p = uaPassword; else if (strcmp(token, "krb5") == 0) *userauth_p = uaKrb5; + else if (strcmp(token, "gss") == 0) + *userauth_p = uaGSS; else if (strcmp(token, "reject") == 0) *userauth_p = uaReject; else if (strcmp(token, "md5") == 0) diff -cr pgsql.orig/src/backend/libpq/pg_hba.conf.sample pgsql/src/backend/libpq/pg_hba.conf.sample *** pgsql.orig/src/backend/libpq/pg_hba.conf.sample 2006-10-12 01:01:46.000000000 +0200 --- pgsql/src/backend/libpq/pg_hba.conf.sample 2007-06-17 18:16:27.000000000 +0200 *************** *** 34,40 **** # the number of significant bits in the mask. Alternatively, you can write # an IP address and netmask in separate columns to specify the set of hosts. # ! # METHOD can be "trust", "reject", "md5", "crypt", "password", # "krb5", "ident", "pam" or "ldap". Note that "password" sends passwords # in clear text; "md5" is preferred since it sends encrypted passwords. # --- 34,40 ---- # the number of significant bits in the mask. Alternatively, you can write # an IP address and netmask in separate columns to specify the set of hosts. # ! # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", # "krb5", "ident", "pam" or "ldap". Note that "password" sends passwords # in clear text; "md5" is preferred since it sends encrypted passwords. # diff -cr pgsql.orig/src/backend/libpq/pqcomm.c pgsql/src/backend/libpq/pqcomm.c *** pgsql.orig/src/backend/libpq/pqcomm.c 2007-06-04 13:59:20.000000000 +0200 --- pgsql/src/backend/libpq/pqcomm.c 2007-06-22 12:48:24.000000000 +0200 *************** *** 173,178 **** --- 173,188 ---- { if (MyProcPort != NULL) { + #ifdef ENABLE_GSS + OM_uint32 min_s; + /* Shutdown GSSAPI layer */ + if (MyProcPort->gss->ctx) + gss_delete_sec_context(&min_s, MyProcPort->gss->ctx, NULL); + + if (MyProcPort->gss->cred) + gss_release_cred(&min_s, MyProcPort->gss->cred); + #endif + /* Cleanly shut down SSL layer */ secure_close(MyProcPort); diff -cr pgsql.orig/src/backend/postmaster/postmaster.c pgsql/src/backend/postmaster/postmaster.c *** pgsql.orig/src/backend/postmaster/postmaster.c 2007-03-22 20:53:30.000000000 +0100 --- pgsql/src/backend/postmaster/postmaster.c 2007-06-17 17:36:49.000000000 +0200 *************** *** 1726,1731 **** --- 1726,1738 ---- RandomSalt(port->cryptSalt, port->md5Salt); } + /* + * Allocate GSSAPI specific state struct + */ + #ifdef ENABLE_GSS + port->gss = (pg_gssinfo *)calloc(1, sizeof(pg_gssinfo)); + #endif + return port; } *************** *** 1739,1744 **** --- 1746,1753 ---- #ifdef USE_SSL secure_close(conn); #endif + if (conn->gss) + free(conn->gss); free(conn); } diff -cr pgsql.orig/src/include/libpq/hba.h pgsql/src/include/libpq/hba.h *** pgsql.orig/src/include/libpq/hba.h 2006-11-05 23:42:10.000000000 +0100 --- pgsql/src/include/libpq/hba.h 2007-06-17 18:01:47.000000000 +0200 *************** *** 22,28 **** uaIdent, uaPassword, uaCrypt, ! uaMD5 #ifdef USE_PAM ,uaPAM #endif /* USE_PAM */ --- 22,29 ---- uaIdent, uaPassword, uaCrypt, ! uaMD5, ! uaGSS, #ifdef USE_PAM ,uaPAM #endif /* USE_PAM */ diff -cr pgsql.orig/src/include/libpq/libpq-be.h pgsql/src/include/libpq/libpq-be.h *** pgsql.orig/src/include/libpq/libpq-be.h 2007-01-05 23:19:55.000000000 +0100 --- pgsql/src/include/libpq/libpq-be.h 2007-06-17 18:03:12.000000000 +0200 *************** *** 29,34 **** --- 29,38 ---- #include <netinet/tcp.h> #endif + #ifdef ENABLE_GSS + #include <gssapi/gssapi.h> + #endif + #include "libpq/hba.h" #include "libpq/pqcomm.h" #include "utils/timestamp.h" *************** *** 39,44 **** --- 43,62 ---- CAC_OK, CAC_STARTUP, CAC_SHUTDOWN, CAC_RECOVERY, CAC_TOOMANY } CAC_state; + + /* + * GSSAPI specific state information + */ + #ifdef ENABLE_GSS + typedef struct + { + gss_cred_id_t cred; /* GSSAPI connection cred's */ + gss_ctx_id_t ctx; /* GSSAPI connection context */ + gss_name_t name; /* GSSAPI client name */ + gss_buffer_desc outbuf; /* GSSAPI output token buffer */ + } pg_gssinfo; + #endif + /* * This is used by the postmaster in its communication with frontends. It * contains all state information needed during this communication before the *************** *** 98,103 **** --- 116,132 ---- int keepalives_interval; int keepalives_count; + #ifdef ENABLE_GSS + /* + * If GSSAPI is supported, store GSSAPI information. + * Oterwise, store a NULL pointer to make sure offsets + * in the struct remain the same. + */ + pg_gssinfo *gss; + #else + void *gss; + #endif + /* * SSL structures (keep these last so that USE_SSL doesn't affect * locations of other fields) diff -cr pgsql.orig/src/include/libpq/pqcomm.h pgsql/src/include/libpq/pqcomm.h *** pgsql.orig/src/include/libpq/pqcomm.h 2007-01-05 23:19:55.000000000 +0100 --- pgsql/src/include/libpq/pqcomm.h 2007-06-19 22:01:08.000000000 +0200 *************** *** 156,161 **** --- 156,163 ---- #define AUTH_REQ_CRYPT 4 /* crypt password */ #define AUTH_REQ_MD5 5 /* md5 password */ #define AUTH_REQ_SCM_CREDS 6 /* transfer SCM credentials */ + #define AUTH_REQ_GSS 7 /* GSSAPI without wrap() */ + #define AUTH_REQ_GSS_CONT 8 /* Continue GSS exchanges */ typedef uint32 AuthRequest; diff -cr pgsql.orig/src/include/pg_config.h.in pgsql/src/include/pg_config.h.in *** pgsql.orig/src/include/pg_config.h.in 2007-05-04 17:20:52.000000000 +0200 --- pgsql/src/include/pg_config.h.in 2007-06-17 18:32:10.000000000 +0200 *************** *** 568,573 **** --- 568,576 ---- /* Define to the appropriate snprintf format for 64-bit ints, if any. */ #undef INT64_FORMAT + /* Define to build with GSSAPI support. (--with-gssapi) */ + #undef ENABLE_GSS + /* Define to build with Kerberos 5 support. (--with-krb5) */ #undef KRB5 diff -cr pgsql.orig/src/interfaces/libpq/Makefile pgsql/src/interfaces/libpq/Makefile *** pgsql.orig/src/interfaces/libpq/Makefile 2007-01-07 09:49:31.000000000 +0100 --- pgsql/src/interfaces/libpq/Makefile 2007-06-19 15:14:31.000000000 +0200 *************** *** 57,63 **** # shared library link. (The order in which you list them here doesn't # matter.) ifneq ($(PORTNAME), win32) ! SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl, $(LIBS))$(LDAP_LIBS_FE) $(PTHREAD_LIBS) else SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS),$(LIBS)) $(LDAP_LIBS_FE) endif --- 57,63 ---- # shared library link. (The order in which you list them here doesn't # matter.) ifneq ($(PORTNAME), win32) ! SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lgssapi_krb5 -lssl -lsocket -lnsl -lresolv -lintl,$(LIBS)) $(LDAP_LIBS_FE) $(PTHREAD_LIBS) else SHLIB_LINK += $(filter -lcrypt -ldes -lcom_err -lcrypto -lk5crypto -lkrb5 -lssl -lsocket -lnsl -lresolv -lintl $(PTHREAD_LIBS),$(LIBS)) $(LDAP_LIBS_FE) endif diff -cr pgsql.orig/src/interfaces/libpq/fe-auth.c pgsql/src/interfaces/libpq/fe-auth.c *** pgsql.orig/src/interfaces/libpq/fe-auth.c 2007-02-10 15:58:55.000000000 +0100 --- pgsql/src/interfaces/libpq/fe-auth.c 2007-06-22 12:56:47.000000000 +0200 *************** *** 313,318 **** --- 313,494 ---- } #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_HOSTBASED_SERVICE_desc = + {10, (void *)"\x2a\x86\x48\x86\xf7\x12\x01\x02\x01\x04"}; + static GSS_DLLIMP gss_OID GSS_C_NT_HOSTBASED_SERVICE = &GSS_C_NT_HOSTBASED_SERVICE_desc; + #endif + + /* + * Fetch all errors of a specific type that fit into a buffer + * and append them. + */ + static void + pg_GSS_error_int(char *mprefix, char *msg, int msglen, + OM_uint32 stat, int type) + { + int curlen = 0; + OM_uint32 lmaj_s, lmin_s; + gss_buffer_desc lmsg; + OM_uint32 msg_ctx = 0; + + do + { + lmaj_s = gss_display_status(&lmin_s, stat, type, + GSS_C_NO_OID, &msg_ctx, &lmsg); + + if (curlen < msglen) + { + snprintf(msg + curlen, msglen - curlen, "%s: %s\n", + mprefix, (char *)lmsg.value); + curlen += lmsg.length; + } + gss_release_buffer(&lmin_s, &lmsg); + } while (msg_ctx); + } + + /* + * GSSAPI errors contains two parts. Put as much as possible of + * both parts into the string. + */ + void + pg_GSS_error(char *mprefix, char *msg, int msglen, + OM_uint32 maj_stat, OM_uint32 min_stat) + { + int mlen; + + /* Fetch major error codes */ + pg_GSS_error_int(mprefix, msg, msglen, maj_stat, GSS_C_GSS_CODE); + mlen = strlen(msg); + + /* If there is room left, try to add the minor codes as well */ + if (mlen < msglen-1) + pg_GSS_error_int(mprefix, msg + mlen, msglen - mlen, + min_stat, GSS_C_MECH_CODE); + } + + /* + * Continue GSS authentication with next token as needed. + */ + static int + pg_GSS_continue(char *PQerrormsg, PGconn *conn) + { + OM_uint32 maj_stat, min_stat, lmin_s; + + maj_stat = gss_init_sec_context(&min_stat, + GSS_C_NO_CREDENTIAL, + &conn->gctx, + conn->gtarg_nam, + GSS_C_NO_OID, + conn->gflags, + 0, + GSS_C_NO_CHANNEL_BINDINGS, + (conn->gctx==GSS_C_NO_CONTEXT)?GSS_C_NO_BUFFER:&conn->ginbuf, + NULL, + &conn->goutbuf, + NULL, + NULL); + + if (conn->gctx != GSS_C_NO_CONTEXT) + { + free(conn->ginbuf.value); + conn->ginbuf.value = NULL; + conn->ginbuf.length = 0; + } + + if (conn->goutbuf.length != 0) + { + /* + * GSS generated data to send to the server. We don't care if it's + * the first or subsequent packet, just send the same kind of + * password packet. + */ + if (pqPacketSend(conn, 'p', + conn->goutbuf.value, conn->goutbuf.length) + != STATUS_OK) + { + gss_release_buffer(&lmin_s, &conn->goutbuf); + return STATUS_ERROR; + } + } + gss_release_buffer(&lmin_s, &conn->goutbuf); + + if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) + { + pg_GSS_error(libpq_gettext("GSSAPI continuation error"), + PQerrormsg, PQERRORMSG_LENGTH, + maj_stat, min_stat); + gss_release_name(&lmin_s, &conn->gtarg_nam); + if (conn->gctx) + gss_delete_sec_context(&lmin_s, &conn->gctx, GSS_C_NO_BUFFER); + return STATUS_ERROR; + } + + if (maj_stat == GSS_S_COMPLETE) + gss_release_name(&lmin_s, &conn->gtarg_nam); + + return STATUS_OK; + } + + /* + * Send initial GSS authentication token + */ + static int + pg_GSS_startup(char *PQerrormsg, PGconn *conn) + { + OM_uint32 maj_stat, min_stat; + int maxlen; + gss_buffer_desc temp_gbuf; + + if (conn->gctx) + { + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + libpq_gettext("duplicate GSS auth request\n")); + return STATUS_ERROR; + } + + /* + * Import service principal name so the proper ticket can be + * acquired by the GSSAPI system. + */ + maxlen = NI_MAXHOST + strlen(conn->krbsrvname) + 2; + temp_gbuf.value = (char*)malloc(maxlen); + snprintf(temp_gbuf.value, maxlen, "%s@%s", + conn->krbsrvname, conn->pghost); + temp_gbuf.length = strlen(temp_gbuf.value); + + maj_stat = gss_import_name(&min_stat, &temp_gbuf, + GSS_C_NT_HOSTBASED_SERVICE, &conn->gtarg_nam); + free(temp_gbuf.value); + + if (maj_stat != GSS_S_COMPLETE) + { + pg_GSS_error(libpq_gettext("GSSAPI name import error"), + PQerrormsg, PQERRORMSG_LENGTH, + maj_stat, min_stat); + return STATUS_ERROR; + } + + /* + * Initial packet is the same as a continuation packet with + * no initial context. + */ + conn->gctx = GSS_C_NO_CONTEXT; + + return pg_GSS_continue(PQerrormsg, conn); + } + #endif /* * Respond to AUTH_REQ_SCM_CREDS challenge. *************** *** 479,484 **** --- 655,691 ---- return STATUS_ERROR; #endif + #ifdef ENABLE_GSS + case AUTH_REQ_GSS: + pglock_thread(); + if (pg_GSS_startup(PQerrormsg, conn) != STATUS_OK) + { + /* PQerrormsg already filled in. */ + pgunlock_thread(); + return STATUS_ERROR; + } + pgunlock_thread(); + break; + + case AUTH_REQ_GSS_CONT: + pglock_thread(); + if (pg_GSS_continue(PQerrormsg, conn) != STATUS_OK) + { + /* PQerrormsg already filled in. */ + pgunlock_thread(); + return STATUS_ERROR; + } + pgunlock_thread(); + break; + + #else + case AUTH_REQ_GSS: + case AUTH_REQ_GSS_CONT: + snprintf(PQerrormsg, PQERRORMSG_LENGTH, + libpq_gettext("GSSAPI authentication not supported\n")); + return STATUS_ERROR; + #endif + case AUTH_REQ_MD5: case AUTH_REQ_CRYPT: case AUTH_REQ_PASSWORD: diff -cr pgsql.orig/src/interfaces/libpq/fe-connect.c pgsql/src/interfaces/libpq/fe-connect.c *** pgsql.orig/src/interfaces/libpq/fe-connect.c 2007-03-08 20:27:28.000000000 +0100 --- pgsql/src/interfaces/libpq/fe-connect.c 2007-06-19 21:24:35.000000000 +0200 *************** *** 181,188 **** {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL, "SSL-Mode", "", 8}, /* sizeof("disable") == 8 */ ! #ifdef KRB5 ! /* Kerberos authentication supports specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, "Kerberos-service-name", "", 20}, #endif --- 181,188 ---- {"sslmode", "PGSSLMODE", DefaultSSLMode, NULL, "SSL-Mode", "", 8}, /* sizeof("disable") == 8 */ ! #if defined(KRB5) || defined(ENABLE_GSS) ! /* Kerberos and GSSAPI authentication support specifying the service name */ {"krbsrvname", "PGKRBSRVNAME", PG_KRB_SRVNAM, NULL, "Kerberos-service-name", "", 20}, #endif *************** *** 412,418 **** conn->sslmode = strdup("require"); } #endif ! #ifdef KRB5 tmp = conninfo_getval(connOptions, "krbsrvname"); conn->krbsrvname = tmp ? strdup(tmp) : NULL; #endif --- 412,418 ---- conn->sslmode = strdup("require"); } #endif ! #if defined(KRB5) || defined(ENABLE_GSS) tmp = conninfo_getval(connOptions, "krbsrvname"); conn->krbsrvname = tmp ? strdup(tmp) : NULL; #endif *************** *** 1496,1507 **** /* * Try to validate message length before using it. ! * Authentication requests can't be very large. Errors can be * a little larger, but not huge. If we see a large apparent * length in an error, it means we're really talking to a * pre-3.0-protocol server; cope. */ ! if (beresp == 'R' && (msgLength < 8 || msgLength > 100)) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext( --- 1496,1508 ---- /* * Try to validate message length before using it. ! * Authentication requests can't be very large, although GSS ! * auth requests may not be that small. Errors can be * a little larger, but not huge. If we see a large apparent * length in an error, it means we're really talking to a * pre-3.0-protocol server; cope. */ ! if (beresp == 'R' && (msgLength < 8 || msgLength > 2000)) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext( *************** *** 1660,1665 **** --- 1661,1703 ---- return PGRES_POLLING_READING; } } + #ifdef ENABLE_GSS + /* + * AUTH_REQ_GSS provides no input data + * Just set the request flags + */ + if (areq == AUTH_REQ_GSS) + conn->gflags = GSS_C_MUTUAL_FLAG; + + /* + * Read GSSAPI data packets + */ + if (areq == AUTH_REQ_GSS_CONT) + { + /* Continue GSSAPI authentication */ + int llen = msgLength - 4; + + /* + * We can be called repeatedly for the same buffer. + * Avoid re-allocating the buffer in this case - + * just re-use the old buffer. + */ + if (llen != conn->ginbuf.length) + { + if (conn->ginbuf.value) + free(conn->ginbuf.value); + + conn->ginbuf.length = llen; + conn->ginbuf.value = malloc(llen); + } + + if (pqGetnchar(conn->ginbuf.value, llen, conn)) + { + /* We'll come back when there is more data. */ + return PGRES_POLLING_READING; + } + } + #endif /* * OK, we successfully read the message; mark data consumed *************** *** 1952,1958 **** free(conn->pgpass); if (conn->sslmode) free(conn->sslmode); ! #ifdef KRB5 if (conn->krbsrvname) free(conn->krbsrvname); #endif --- 1990,1996 ---- free(conn->pgpass); if (conn->sslmode) free(conn->sslmode); ! #if defined(KRB5) || defined(GSS) if (conn->krbsrvname) free(conn->krbsrvname); #endif *************** *** 1968,1973 **** --- 2006,2024 ---- notify = notify->next; free(prev); } + #ifdef ENABLE_GSS + { + OM_uint32 min_s; + if (conn->gctx) + gss_delete_sec_context(&min_s, &conn->gctx, GSS_C_NO_BUFFER); + if (conn->gtarg_nam) + gss_release_name(&min_s, &conn->gtarg_nam); + if (conn->ginbuf.length) + gss_release_buffer(&min_s, &conn->ginbuf); + if (conn->goutbuf.length) + gss_release_buffer(&min_s, &conn->goutbuf); + } + #endif pstatus = conn->pstatus; while (pstatus != NULL) { diff -cr pgsql.orig/src/interfaces/libpq/libpq-int.h pgsql/src/interfaces/libpq/libpq-int.h *** pgsql.orig/src/interfaces/libpq/libpq-int.h 2007-03-03 20:52:47.000000000 +0100 --- pgsql/src/interfaces/libpq/libpq-int.h 2007-06-17 17:40:38.000000000 +0200 *************** *** 44,49 **** --- 44,53 ---- /* include stuff found in fe only */ #include "pqexpbuffer.h" + #ifdef ENABLE_GSS + #include <gssapi/gssapi.h> + #endif + #ifdef USE_SSL #include <openssl/ssl.h> #include <openssl/err.h> *************** *** 268,274 **** char *pguser; /* Postgres username and password, if any */ char *pgpass; char *sslmode; /* SSL mode (require,prefer,allow,disable) */ ! #ifdef KRB5 char *krbsrvname; /* Kerberos service name */ #endif --- 272,278 ---- char *pguser; /* Postgres username and password, if any */ char *pgpass; char *sslmode; /* SSL mode (require,prefer,allow,disable) */ ! #if defined(KRB5) || defined(GSS) char *krbsrvname; /* Kerberos service name */ #endif *************** *** 349,354 **** --- 353,366 ---- char peer_cn[SM_USER + 1]; /* peer common name */ #endif + #ifdef ENABLE_GSS + gss_ctx_id_t gctx; /* GSS context */ + gss_name_t gtarg_nam; /* GSS target name */ + OM_uint32 gflags; /* GSS service request flags */ + gss_buffer_desc ginbuf; /* GSS input token */ + gss_buffer_desc goutbuf; /* GSS output token */ + #endif + /* Buffer for current error message */ PQExpBufferData errorMessage; /* expansible string */ *************** *** 398,403 **** --- 410,420 ---- #define pgunlock_thread() ((void) 0) #endif + /* === in fe-auth.c === */ + #ifdef ENABLE_GSS + extern void pg_GSS_error(char *mprefix, char *msg, int msglen, + OM_uint32 maj_stat, OM_uint32 min_stat); + #endif /* === in fe-exec.c === */
pgsql-patches by date: