+/*
+ auth_gss.c
+
+ RPCSEC_GSS client routines.
+
+ Copyright (c) 2000 The Regents of the University of Michigan.
+ All rights reserved.
+
+ Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+ All rights reserved, all wrongs reversed.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <rpc/types.h>
+#include <rpc/xdr.h>
+#include <rpc/auth.h>
+#include <rpc/auth_gss.h>
+#include <rpc/clnt.h>
+#include <netinet/in.h>
+#include <gssapi/gssapi.h>
+
+static void authgss_nextverf();
+static bool_t authgss_marshal();
+static bool_t authgss_refresh();
+static bool_t authgss_validate();
+static void authgss_destroy();
+static void authgss_destroy_context();
+static bool_t authgss_wrap();
+static bool_t authgss_unwrap();
+
+
+/*
+ * from mit-krb5-1.2.1 mechglue/mglueP.h:
+ * Array of context IDs typed by mechanism OID
+ */
+typedef struct gss_union_ctx_id_t {
+ gss_OID mech_type;
+ gss_ctx_id_t internal_ctx_id;
+} gss_union_ctx_id_desc, *gss_union_ctx_id_t;
+
+static struct auth_ops authgss_ops = {
+ authgss_nextverf,
+ authgss_marshal,
+ authgss_validate,
+ authgss_refresh,
+ authgss_destroy,
+ authgss_wrap,
+ authgss_unwrap
+};
+
+#ifdef DEBUG
+
+/* useful as i add more mechanisms */
+void
+print_rpc_gss_sec(struct rpc_gss_sec *ptr)
+{
+int i;
+char *p;
+
+ log_debug("rpc_gss_sec:");
+ if(ptr->mech == NULL)
+ log_debug("NULL gss_OID mech");
+ else {
+ fprintf(stderr, " mechanism_OID: {");
+ p = (char *)ptr->mech->elements;
+ for (i=0; i < ptr->mech->length; i++)
+ /* First byte of OIDs encoded to save a byte */
+ if (i == 0) {
+ int first, second;
+ if (*p < 40) {
+ first = 0;
+ second = *p;
+ }
+ else if (40 <= *p && *p < 80) {
+ first = 1;
+ second = *p - 40;
+ }
+ else if (80 <= *p && *p < 127) {
+ first = 2;
+ second = *p - 80;
+ }
+ else {
+ /* Invalid value! */
+ first = -1;
+ second = -1;
+ }
+ fprintf(stderr, " %u %u", first, second);
+ p++;
+ }
+ else {
+ fprintf(stderr, " %u", (unsigned char)*p++);
+ }
+ fprintf(stderr, " }\n");
+ }
+ fprintf(stderr, " qop: %d\n", ptr->qop);
+ fprintf(stderr, " service: %d\n", ptr->svc);
+ fprintf(stderr, " cred: %p\n", ptr->cred);
+}
+#endif /*DEBUG*/
+
+struct rpc_gss_data {
+ bool_t established; /* context established */
+ gss_buffer_desc gc_wire_verf; /* save GSS_S_COMPLETE NULL RPC verfier
+ * to process at end of context negotiation*/
+ CLIENT *clnt; /* client handle */
+ gss_name_t name; /* service name */
+ struct rpc_gss_sec sec; /* security tuple */
+ gss_ctx_id_t ctx; /* context id */
+ struct rpc_gss_cred gc; /* client credentials */
+ u_int win; /* sequence window */
+};
+
+#define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private)
+
+static struct timeval AUTH_TIMEOUT = { 25, 0 };
+
+AUTH *
+authgss_create(CLIENT *clnt, gss_name_t name, struct rpc_gss_sec *sec)
+{
+ AUTH *auth, *save_auth;
+ struct rpc_gss_data *gd;
+ OM_uint32 min_stat = 0;
+
+ log_debug("in authgss_create()");
+
+ memset(&rpc_createerr, 0, sizeof(rpc_createerr));
+
+ if ((auth = calloc(sizeof(*auth), 1)) == NULL) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ return (NULL);
+ }
+ if ((gd = calloc(sizeof(*gd), 1)) == NULL) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ free(auth);
+ return (NULL);
+ }
+#ifdef DEBUG
+ fprintf(stderr, "authgss_create: name is %p\n", name);
+#endif
+ if (name != GSS_C_NO_NAME) {
+ if (gss_duplicate_name(&min_stat, name, &gd->name)
+ != GSS_S_COMPLETE) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ free(auth);
+ return (NULL);
+ }
+ }
+ else
+ gd->name = name;
+
+#ifdef DEBUG
+ fprintf(stderr, "authgss_create: gd->name is %p\n", gd->name);
+#endif
+ gd->clnt = clnt;
+ gd->ctx = GSS_C_NO_CONTEXT;
+ gd->sec = *sec;
+
+ gd->gc.gc_v = RPCSEC_GSS_VERSION;
+ gd->gc.gc_proc = RPCSEC_GSS_INIT;
+ gd->gc.gc_svc = gd->sec.svc;
+
+ auth->ah_ops = &authgss_ops;
+ auth->ah_private = (caddr_t)gd;
+
+ save_auth = clnt->cl_auth;
+ clnt->cl_auth = auth;
+
+ if (!authgss_refresh(auth))
+ auth = NULL;
+
+ clnt->cl_auth = save_auth;
+
+ return (auth);
+}
+
+AUTH *
+authgss_create_default(CLIENT *clnt, char *service, struct rpc_gss_sec *sec)
+{
+ AUTH *auth;
+ OM_uint32 maj_stat = 0, min_stat = 0;
+ gss_buffer_desc sname;
+ gss_name_t name = GSS_C_NO_NAME;
+
+ log_debug("in authgss_create_default()");
+
+
+ sname.value = service;
+ sname.length = strlen(service);
+
+ maj_stat = gss_import_name(&min_stat, &sname,
+ GSS_C_NT_HOSTBASED_SERVICE,
+ &name);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_import_name", maj_stat, min_stat);
+ rpc_createerr.cf_stat = RPC_AUTHERROR;
+ return (NULL);
+ }
+
+ auth = authgss_create(clnt, name, sec);
+
+ if (name != GSS_C_NO_NAME) {
+#ifdef DEBUG
+ fprintf(stderr, "authgss_create_default: freeing name %p\n", name);
+#endif
+ gss_release_name(&min_stat, &name);
+ }
+
+ return (auth);
+}
+
+bool_t
+authgss_get_private_data(AUTH *auth, struct authgss_private_data *pd)
+{
+ struct rpc_gss_data *gd;
+
+ log_debug("in authgss_get_private_data()");
+
+ if (!auth || !pd)
+ return (FALSE);
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (!gd || !gd->established)
+ return (FALSE);
+
+ pd->pd_ctx = gd->ctx;
+ pd->pd_ctx_hndl = gd->gc.gc_ctx;
+ pd->pd_seq_win = gd->win;
+
+ return (TRUE);
+}
+
+static void
+authgss_nextverf(AUTH *auth)
+{
+ log_debug("in authgss_nextverf()");
+ /* no action necessary */
+}
+
+static bool_t
+authgss_marshal(AUTH *auth, XDR *xdrs)
+{
+ XDR tmpxdrs;
+ char tmp[MAX_AUTH_BYTES];
+ struct rpc_gss_data *gd;
+ gss_buffer_desc rpcbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+ bool_t xdr_stat;
+
+ log_debug("in authgss_marshal()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (gd->established)
+ gd->gc.gc_seq++;
+
+ xdrmem_create(&tmpxdrs, tmp, sizeof(tmp), XDR_ENCODE);
+
+ if (!xdr_rpc_gss_cred(&tmpxdrs, &gd->gc)) {
+ XDR_DESTROY(&tmpxdrs);
+ return (FALSE);
+ }
+ auth->ah_cred.oa_flavor = RPCSEC_GSS;
+ auth->ah_cred.oa_base = tmp;
+ auth->ah_cred.oa_length = XDR_GETPOS(&tmpxdrs);
+
+ XDR_DESTROY(&tmpxdrs);
+
+ if (!xdr_opaque_auth(xdrs, &auth->ah_cred))
+ return (FALSE);
+
+ if (gd->gc.gc_proc == RPCSEC_GSS_INIT ||
+ gd->gc.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
+ return (xdr_opaque_auth(xdrs, &_null_auth));
+ }
+ /* Checksum serialized RPC header, up to and including credential. */
+ rpcbuf.length = XDR_GETPOS(xdrs);
+ XDR_SETPOS(xdrs, 0);
+ rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length);
+
+ maj_stat = gss_get_mic(&min_stat, gd->ctx, gd->sec.qop,
+ &rpcbuf, &checksum);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_get_mic", maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ gd->established = FALSE;
+ authgss_destroy_context(auth);
+ }
+ return (FALSE);
+ }
+ auth->ah_verf.oa_flavor = RPCSEC_GSS;
+ auth->ah_verf.oa_base = checksum.value;
+ auth->ah_verf.oa_length = checksum.length;
+
+ xdr_stat = xdr_opaque_auth(xdrs, &auth->ah_verf);
+ gss_release_buffer(&min_stat, &checksum);
+
+ return (xdr_stat);
+}
+
+static bool_t
+authgss_validate(AUTH *auth, struct opaque_auth *verf)
+{
+ struct rpc_gss_data *gd;
+ u_int num, qop_state;
+ gss_buffer_desc signbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+
+ log_debug("in authgss_validate()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (gd->established == FALSE) {
+ /* would like to do this only on NULL rpc --
+ * gc->established is good enough.
+ * save the on the wire verifier to validate last
+ * INIT phase packet after decode if the major
+ * status is GSS_S_COMPLETE
+ */
+ if ((gd->gc_wire_verf.value =
+ mem_alloc(verf->oa_length)) == NULL) {
+ fprintf(stderr, "gss_validate: out of memory\n");
+ return (FALSE);
+ }
+ memcpy(gd->gc_wire_verf.value, verf->oa_base, verf->oa_length);
+ gd->gc_wire_verf.length = verf->oa_length;
+ return (TRUE);
+ }
+
+ if (gd->gc.gc_proc == RPCSEC_GSS_INIT ||
+ gd->gc.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
+ num = htonl(gd->win);
+ }
+ else num = htonl(gd->gc.gc_seq);
+
+ signbuf.value = #
+ signbuf.length = sizeof(num);
+
+ checksum.value = verf->oa_base;
+ checksum.length = verf->oa_length;
+
+ maj_stat = gss_verify_mic(&min_stat, gd->ctx, &signbuf,
+ &checksum, &qop_state);
+ if (maj_stat != GSS_S_COMPLETE || qop_state != gd->sec.qop) {
+ log_status("gss_verify_mic", maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ gd->established = FALSE;
+ authgss_destroy_context(auth);
+ }
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+static bool_t
+authgss_refresh(AUTH *auth)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_gss_init_res gr;
+ gss_buffer_desc *recv_tokenp, send_token;
+ OM_uint32 maj_stat, min_stat, call_stat, ret_flags;
+ OM_uint32 req_flags=0;
+
+ log_debug("in authgss_refresh()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (gd->established)
+ return (TRUE);
+
+ /* GSS context establishment loop. */
+ memset(&gr, 0, sizeof(gr));
+ recv_tokenp = GSS_C_NO_BUFFER;
+
+#ifdef DEBUG
+ print_rpc_gss_sec(&gd->sec);
+#endif /*DEBUG*/
+
+ for (;;) {
+#ifdef DEBUG
+ /* print the token we just received */
+ if (recv_tokenp != GSS_C_NO_BUFFER) {
+ log_debug("The token we just received (length %d):",
+ recv_tokenp->length);
+ log_hexdump(recv_tokenp->value, recv_tokenp->length, 0);
+ }
+#endif
+ maj_stat = gss_init_sec_context(&min_stat,
+ gd->sec.cred,
+ &gd->ctx,
+ gd->name,
+ gd->sec.mech,
+ gd->sec.req_flags,
+ 0, /* time req */
+ NULL, /* channel */
+ recv_tokenp,
+ NULL, /* used mech */
+ &send_token,
+ &ret_flags,
+ NULL); /* time rec */
+
+ if (recv_tokenp != GSS_C_NO_BUFFER) {
+ gss_release_buffer(&min_stat, &gr.gr_token);
+ recv_tokenp = GSS_C_NO_BUFFER;
+ }
+ if (maj_stat != GSS_S_COMPLETE &&
+ maj_stat != GSS_S_CONTINUE_NEEDED) {
+ log_status("gss_init_sec_context", maj_stat, min_stat);
+ break;
+ }
+ if (send_token.length != 0) {
+ memset(&gr, 0, sizeof(gr));
+
+#ifdef DEBUG
+ /* print the token we are about to send */
+ log_debug("The token being sent (length %d):",
+ send_token.length);
+ log_hexdump(send_token.value, send_token.length, 0);
+#endif
+
+ call_stat = clnt_call(gd->clnt, NULLPROC,
+ xdr_rpc_gss_init_args,
+ &send_token,
+ xdr_rpc_gss_init_res,
+ (caddr_t)&gr, AUTH_TIMEOUT);
+
+ gss_release_buffer(&min_stat, &send_token);
+
+ if (call_stat != RPC_SUCCESS ||
+ (gr.gr_major != GSS_S_COMPLETE &&
+ gr.gr_major != GSS_S_CONTINUE_NEEDED))
+ return FALSE;
+
+ if (gr.gr_ctx.length != 0) {
+ if (gd->gc.gc_ctx.value)
+ gss_release_buffer(&min_stat,
+ &gd->gc.gc_ctx);
+ gd->gc.gc_ctx = gr.gr_ctx;
+ }
+ if (gr.gr_token.length != 0) {
+ if (maj_stat != GSS_S_CONTINUE_NEEDED)
+ break;
+ recv_tokenp = &gr.gr_token;
+ }
+ gd->gc.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
+ }
+
+ /* GSS_S_COMPLETE => check gss header verifier,
+ * usually checked in gss_validate
+ */
+ if (maj_stat == GSS_S_COMPLETE) {
+ gss_buffer_desc bufin;
+ gss_buffer_desc bufout;
+ u_int seq, qop_state = 0;
+
+ seq = htonl(gr.gr_win);
+ bufin.value = (unsigned char *)&seq;
+ bufin.length = sizeof(seq);
+ bufout.value = (unsigned char *)gd->gc_wire_verf.value;
+ bufout.length = gd->gc_wire_verf.length;
+
+ maj_stat = gss_verify_mic(&min_stat, gd->ctx,
+ &bufin, &bufout, &qop_state);
+
+ if (maj_stat != GSS_S_COMPLETE
+ || qop_state != gd->sec.qop) {
+ log_status("gss_verify_mic", maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ gd->established = FALSE;
+ authgss_destroy_context(auth);
+ }
+ return (FALSE);
+ }
+ gd->established = TRUE;
+ gd->gc.gc_proc = RPCSEC_GSS_DATA;
+ gd->gc.gc_seq = 0;
+ gd->win = gr.gr_win;
+ break;
+ }
+ }
+ /* End context negotiation loop. */
+ if (gd->gc.gc_proc != RPCSEC_GSS_DATA) {
+ if (gr.gr_token.length != 0)
+ gss_release_buffer(&min_stat, &gr.gr_token);
+
+ authgss_destroy(auth);
+ auth = NULL;
+ rpc_createerr.cf_stat = RPC_AUTHERROR;
+
+ return (FALSE);
+ }
+ return (TRUE);
+}
+
+bool_t
+authgss_service(AUTH *auth, int svc)
+{
+ struct rpc_gss_data *gd;
+
+ log_debug("in authgss_service()");
+
+ if (!auth)
+ return(FALSE);
+ gd = AUTH_PRIVATE(auth);
+ if (!gd || !gd->established)
+ return (FALSE);
+ gd->sec.svc = svc;
+ gd->gc.gc_svc = svc;
+ return (TRUE);
+}
+
+static void
+authgss_destroy_context(AUTH *auth)
+{
+ struct rpc_gss_data *gd;
+ OM_uint32 min_stat;
+
+ log_debug("in authgss_destroy_context()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (gd->gc.gc_ctx.length != 0) {
+ if (gd->established) {
+ gd->gc.gc_proc = RPCSEC_GSS_DESTROY;
+ clnt_call(gd->clnt, NULLPROC, xdr_void, NULL,
+ xdr_void, NULL, AUTH_TIMEOUT);
+ }
+ gss_release_buffer(&min_stat, &gd->gc.gc_ctx);
+ /* XXX ANDROS check size of context - should be 8 */
+ memset(&gd->gc.gc_ctx, 0, sizeof(gd->gc.gc_ctx));
+ }
+ if (gd->ctx != GSS_C_NO_CONTEXT) {
+ gss_delete_sec_context(&min_stat, &gd->ctx, NULL);
+ gd->ctx = GSS_C_NO_CONTEXT;
+ }
+ gd->established = FALSE;
+}
+
+static void
+authgss_destroy(AUTH *auth)
+{
+ struct rpc_gss_data *gd;
+ OM_uint32 min_stat;
+
+ log_debug("in authgss_destroy()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ authgss_destroy_context(auth);
+
+#ifdef DEBUG
+ fprintf(stderr, "authgss_destroy: freeing name %p\n", gd->name);
+#endif
+ if (gd->name != GSS_C_NO_NAME)
+ gss_release_name(&min_stat, &gd->name);
+
+ free(gd);
+ free(auth);
+}
+
+bool_t
+authgss_wrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+ struct rpc_gss_data *gd;
+
+ log_debug("in authgss_wrap()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) {
+ return ((*xdr_func)(xdrs, xdr_ptr));
+ }
+ return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr,
+ gd->ctx, gd->sec.qop,
+ gd->sec.svc, gd->gc.gc_seq));
+}
+
+bool_t
+authgss_unwrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+ struct rpc_gss_data *gd;
+
+ log_debug("in authgss_unwrap()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) {
+ return ((*xdr_func)(xdrs, xdr_ptr));
+ }
+ return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr,
+ gd->ctx, gd->sec.qop,
+ gd->sec.svc, gd->gc.gc_seq));
+}