summaryrefslogtreecommitdiffstats
path: root/sys/rpc/rpcsec_gss/rpcsec_gss.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/rpc/rpcsec_gss/rpcsec_gss.c')
-rw-r--r--sys/rpc/rpcsec_gss/rpcsec_gss.c1064
1 files changed, 1064 insertions, 0 deletions
diff --git a/sys/rpc/rpcsec_gss/rpcsec_gss.c b/sys/rpc/rpcsec_gss/rpcsec_gss.c
new file mode 100644
index 0000000..790804d
--- /dev/null
+++ b/sys/rpc/rpcsec_gss/rpcsec_gss.c
@@ -0,0 +1,1064 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 AUTHOR 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.
+ */
+/*
+ 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.
+
+ $Id: auth_gss.c,v 1.32 2002/01/15 15:43:00 andros Exp $
+*/
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/hash.h>
+#include <sys/kernel.h>
+#include <sys/kobj.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/refcount.h>
+#include <sys/sx.h>
+#include <sys/ucred.h>
+
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+
+#include "rpcsec_gss_int.h"
+
+static void rpc_gss_nextverf(AUTH*);
+static bool_t rpc_gss_marshal(AUTH *, uint32_t, XDR *, struct mbuf *);
+static bool_t rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret);
+static bool_t rpc_gss_refresh(AUTH *, void *);
+static bool_t rpc_gss_validate(AUTH *, uint32_t, struct opaque_auth *,
+ struct mbuf **);
+static void rpc_gss_destroy(AUTH *);
+static void rpc_gss_destroy_context(AUTH *, bool_t);
+
+static struct auth_ops rpc_gss_ops = {
+ rpc_gss_nextverf,
+ rpc_gss_marshal,
+ rpc_gss_validate,
+ rpc_gss_refresh,
+ rpc_gss_destroy,
+};
+
+enum rpcsec_gss_state {
+ RPCSEC_GSS_START,
+ RPCSEC_GSS_CONTEXT,
+ RPCSEC_GSS_ESTABLISHED,
+ RPCSEC_GSS_DESTROYING
+};
+
+struct rpc_pending_request {
+ uint32_t pr_xid; /* XID of rpc */
+ uint32_t pr_seq; /* matching GSS seq */
+ LIST_ENTRY(rpc_pending_request) pr_link;
+};
+LIST_HEAD(rpc_pending_request_list, rpc_pending_request);
+
+struct rpc_gss_data {
+ volatile u_int gd_refs; /* number of current users */
+ struct mtx gd_lock;
+ uint32_t gd_hash;
+ AUTH *gd_auth; /* link back to AUTH */
+ struct ucred *gd_ucred; /* matching local cred */
+ char *gd_principal; /* server principal name */
+ rpc_gss_options_req_t gd_options; /* GSS context options */
+ enum rpcsec_gss_state gd_state; /* connection state */
+ gss_buffer_desc gd_verf; /* save GSS_S_COMPLETE
+ * NULL RPC verfier to
+ * process at end of
+ * context negotiation */
+ CLIENT *gd_clnt; /* client handle */
+ gss_OID gd_mech; /* mechanism to use */
+ gss_qop_t gd_qop; /* quality of protection */
+ gss_ctx_id_t gd_ctx; /* context id */
+ struct rpc_gss_cred gd_cred; /* client credentials */
+ uint32_t gd_seq; /* next sequence number */
+ u_int gd_win; /* sequence window */
+ struct rpc_pending_request_list gd_reqs;
+ TAILQ_ENTRY(rpc_gss_data) gd_link;
+ TAILQ_ENTRY(rpc_gss_data) gd_alllink;
+};
+TAILQ_HEAD(rpc_gss_data_list, rpc_gss_data);
+
+#define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private)
+
+static struct timeval AUTH_TIMEOUT = { 25, 0 };
+
+#define RPC_GSS_HASH_SIZE 11
+#define RPC_GSS_MAX 256
+static struct rpc_gss_data_list rpc_gss_cache[RPC_GSS_HASH_SIZE];
+static struct rpc_gss_data_list rpc_gss_all;
+static struct sx rpc_gss_lock;
+static int rpc_gss_count;
+
+static AUTH *rpc_gss_seccreate_int(CLIENT *, struct ucred *, const char *,
+ gss_OID, rpc_gss_service_t, u_int, rpc_gss_options_req_t *,
+ rpc_gss_options_ret_t *);
+
+static void
+rpc_gss_hashinit(void *dummy)
+{
+ int i;
+
+ for (i = 0; i < RPC_GSS_HASH_SIZE; i++)
+ TAILQ_INIT(&rpc_gss_cache[i]);
+ TAILQ_INIT(&rpc_gss_all);
+ sx_init(&rpc_gss_lock, "rpc_gss_lock");
+}
+SYSINIT(rpc_gss_hashinit, SI_SUB_KMEM, SI_ORDER_ANY, rpc_gss_hashinit, NULL);
+
+static uint32_t
+rpc_gss_hash(const char *principal, gss_OID mech,
+ struct ucred *cred, rpc_gss_service_t service)
+{
+ uint32_t h;
+
+ h = HASHSTEP(HASHINIT, cred->cr_uid);
+ h = hash32_str(principal, h);
+ h = hash32_buf(mech->elements, mech->length, h);
+ h = HASHSTEP(h, (int) service);
+
+ return (h % RPC_GSS_HASH_SIZE);
+}
+
+/*
+ * Simplified interface to create a security association for the
+ * current thread's * ucred.
+ */
+AUTH *
+rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
+ gss_OID mech_oid, rpc_gss_service_t service)
+{
+ uint32_t h, th;
+ AUTH *auth;
+ struct rpc_gss_data *gd, *tgd;
+
+ if (rpc_gss_count > RPC_GSS_MAX) {
+ while (rpc_gss_count > RPC_GSS_MAX) {
+ sx_xlock(&rpc_gss_lock);
+ tgd = TAILQ_FIRST(&rpc_gss_all);
+ th = tgd->gd_hash;
+ TAILQ_REMOVE(&rpc_gss_cache[th], tgd, gd_link);
+ TAILQ_REMOVE(&rpc_gss_all, tgd, gd_alllink);
+ rpc_gss_count--;
+ sx_xunlock(&rpc_gss_lock);
+ AUTH_DESTROY(tgd->gd_auth);
+ }
+ }
+
+ /*
+ * See if we already have an AUTH which matches.
+ */
+ h = rpc_gss_hash(principal, mech_oid, cred, service);
+
+again:
+ sx_slock(&rpc_gss_lock);
+ TAILQ_FOREACH(gd, &rpc_gss_cache[h], gd_link) {
+ if (gd->gd_ucred->cr_uid == cred->cr_uid
+ && !strcmp(gd->gd_principal, principal)
+ && gd->gd_mech == mech_oid
+ && gd->gd_cred.gc_svc == service) {
+ refcount_acquire(&gd->gd_refs);
+ if (sx_try_upgrade(&rpc_gss_lock)) {
+ /*
+ * Keep rpc_gss_all LRU sorted.
+ */
+ TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
+ TAILQ_INSERT_TAIL(&rpc_gss_all, gd,
+ gd_alllink);
+ sx_xunlock(&rpc_gss_lock);
+ } else {
+ sx_sunlock(&rpc_gss_lock);
+ }
+ return (gd->gd_auth);
+ }
+ }
+ sx_sunlock(&rpc_gss_lock);
+
+ /*
+ * We missed in the cache - create a new association.
+ */
+ auth = rpc_gss_seccreate_int(clnt, cred, principal, mech_oid, service,
+ GSS_C_QOP_DEFAULT, NULL, NULL);
+ if (!auth)
+ return (NULL);
+
+ gd = AUTH_PRIVATE(auth);
+ gd->gd_hash = h;
+
+ sx_xlock(&rpc_gss_lock);
+ TAILQ_FOREACH(tgd, &rpc_gss_cache[h], gd_link) {
+ if (tgd->gd_ucred->cr_uid == cred->cr_uid
+ && !strcmp(tgd->gd_principal, principal)
+ && tgd->gd_mech == mech_oid
+ && tgd->gd_cred.gc_svc == service) {
+ /*
+ * We lost a race to create the AUTH that
+ * matches this cred.
+ */
+ sx_xunlock(&rpc_gss_lock);
+ AUTH_DESTROY(auth);
+ goto again;
+ }
+ }
+
+ rpc_gss_count++;
+ TAILQ_INSERT_TAIL(&rpc_gss_cache[h], gd, gd_link);
+ TAILQ_INSERT_TAIL(&rpc_gss_all, gd, gd_alllink);
+ refcount_acquire(&gd->gd_refs); /* one for the cache, one for user */
+ sx_xunlock(&rpc_gss_lock);
+
+ return (auth);
+}
+
+void
+rpc_gss_secpurge(CLIENT *clnt)
+{
+ uint32_t h;
+ struct rpc_gss_data *gd, *tgd;
+
+ TAILQ_FOREACH_SAFE(gd, &rpc_gss_all, gd_alllink, tgd) {
+ if (gd->gd_clnt == clnt) {
+ sx_xlock(&rpc_gss_lock);
+ h = gd->gd_hash;
+ TAILQ_REMOVE(&rpc_gss_cache[h], gd, gd_link);
+ TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
+ rpc_gss_count--;
+ sx_xunlock(&rpc_gss_lock);
+ AUTH_DESTROY(gd->gd_auth);
+ }
+ }
+}
+
+AUTH *
+rpc_gss_seccreate(CLIENT *clnt, struct ucred *cred, const char *principal,
+ const char *mechanism, rpc_gss_service_t service, const char *qop,
+ rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
+{
+ gss_OID oid;
+ u_int qop_num;
+
+ /*
+ * Bail out now if we don't know this mechanism.
+ */
+ if (!rpc_gss_mech_to_oid(mechanism, &oid))
+ return (NULL);
+
+ if (qop) {
+ if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num))
+ return (NULL);
+ } else {
+ qop_num = GSS_C_QOP_DEFAULT;
+ }
+
+ return (rpc_gss_seccreate_int(clnt, cred, principal, oid, service,
+ qop_num, options_req, options_ret));
+}
+
+static AUTH *
+rpc_gss_seccreate_int(CLIENT *clnt, struct ucred *cred, const char *principal,
+ gss_OID mech_oid, rpc_gss_service_t service, u_int qop_num,
+ rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
+{
+ AUTH *auth;
+ rpc_gss_options_ret_t options;
+ struct rpc_gss_data *gd;
+
+ /*
+ * If the caller doesn't want the options, point at local
+ * storage to simplify the code below.
+ */
+ if (!options_ret)
+ options_ret = &options;
+
+ /*
+ * Default service is integrity.
+ */
+ if (service == rpc_gss_svc_default)
+ service = rpc_gss_svc_integrity;
+
+ memset(options_ret, 0, sizeof(*options_ret));
+
+ rpc_gss_log_debug("in rpc_gss_seccreate()");
+
+ memset(&rpc_createerr, 0, sizeof(rpc_createerr));
+
+ auth = mem_alloc(sizeof(*auth));
+ if (auth == NULL) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ return (NULL);
+ }
+ gd = mem_alloc(sizeof(*gd));
+ if (gd == NULL) {
+ rpc_createerr.cf_stat = RPC_SYSTEMERROR;
+ rpc_createerr.cf_error.re_errno = ENOMEM;
+ mem_free(auth, sizeof(*auth));
+ return (NULL);
+ }
+
+ auth->ah_ops = &rpc_gss_ops;
+ auth->ah_private = (caddr_t) gd;
+ auth->ah_cred.oa_flavor = RPCSEC_GSS;
+
+ refcount_init(&gd->gd_refs, 1);
+ mtx_init(&gd->gd_lock, "gd->gd_lock", NULL, MTX_DEF);
+ gd->gd_auth = auth;
+ gd->gd_ucred = crdup(cred);
+ gd->gd_principal = strdup(principal, M_RPC);
+
+
+ if (options_req) {
+ gd->gd_options = *options_req;
+ } else {
+ gd->gd_options.req_flags = GSS_C_MUTUAL_FLAG;
+ gd->gd_options.time_req = 0;
+ gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
+ gd->gd_options.input_channel_bindings = NULL;
+ }
+ CLNT_ACQUIRE(clnt);
+ gd->gd_clnt = clnt;
+ gd->gd_ctx = GSS_C_NO_CONTEXT;
+ gd->gd_mech = mech_oid;
+ gd->gd_qop = qop_num;
+
+ gd->gd_cred.gc_version = RPCSEC_GSS_VERSION;
+ gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
+ gd->gd_cred.gc_seq = 0;
+ gd->gd_cred.gc_svc = service;
+ LIST_INIT(&gd->gd_reqs);
+
+ if (!rpc_gss_init(auth, options_ret)) {
+ goto bad;
+ }
+
+ return (auth);
+
+ bad:
+ AUTH_DESTROY(auth);
+ return (NULL);
+}
+
+bool_t
+rpc_gss_set_defaults(AUTH *auth, rpc_gss_service_t service, const char *qop)
+{
+ struct rpc_gss_data *gd;
+ u_int qop_num;
+ const char *mechanism;
+
+ gd = AUTH_PRIVATE(auth);
+ if (!rpc_gss_oid_to_mech(gd->gd_mech, &mechanism)) {
+ return (FALSE);
+ }
+
+ if (qop) {
+ if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num)) {
+ return (FALSE);
+ }
+ } else {
+ qop_num = GSS_C_QOP_DEFAULT;
+ }
+
+ gd->gd_cred.gc_svc = service;
+ gd->gd_qop = qop_num;
+ return (TRUE);
+}
+
+static void
+rpc_gss_purge_xid(struct rpc_gss_data *gd, uint32_t xid)
+{
+ struct rpc_pending_request *pr, *npr;
+ struct rpc_pending_request_list reqs;
+
+ LIST_INIT(&reqs);
+ mtx_lock(&gd->gd_lock);
+ LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
+ if (pr->pr_xid == xid) {
+ LIST_REMOVE(pr, pr_link);
+ LIST_INSERT_HEAD(&reqs, pr, pr_link);
+ }
+ }
+
+ mtx_unlock(&gd->gd_lock);
+
+ LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
+ mem_free(pr, sizeof(*pr));
+ }
+}
+
+static uint32_t
+rpc_gss_alloc_seq(struct rpc_gss_data *gd)
+{
+ uint32_t seq;
+
+ mtx_lock(&gd->gd_lock);
+ seq = gd->gd_seq;
+ gd->gd_seq++;
+ mtx_unlock(&gd->gd_lock);
+
+ return (seq);
+}
+
+static void
+rpc_gss_nextverf(__unused AUTH *auth)
+{
+
+ /* not used */
+}
+
+static bool_t
+rpc_gss_marshal(AUTH *auth, uint32_t xid, XDR *xdrs, struct mbuf *args)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_pending_request *pr;
+ uint32_t seq;
+ XDR tmpxdrs;
+ struct rpc_gss_cred gsscred;
+ char credbuf[MAX_AUTH_BYTES];
+ struct opaque_auth creds, verf;
+ gss_buffer_desc rpcbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+ bool_t xdr_stat;
+
+ rpc_gss_log_debug("in rpc_gss_marshal()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ gsscred = gd->gd_cred;
+ seq = rpc_gss_alloc_seq(gd);
+ gsscred.gc_seq = seq;
+
+ xdrmem_create(&tmpxdrs, credbuf, sizeof(credbuf), XDR_ENCODE);
+ if (!xdr_rpc_gss_cred(&tmpxdrs, &gsscred)) {
+ XDR_DESTROY(&tmpxdrs);
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ creds.oa_flavor = RPCSEC_GSS;
+ creds.oa_base = credbuf;
+ creds.oa_length = XDR_GETPOS(&tmpxdrs);
+ XDR_DESTROY(&tmpxdrs);
+
+ xdr_opaque_auth(xdrs, &creds);
+
+ if (gd->gd_cred.gc_proc == RPCSEC_GSS_INIT ||
+ gd->gd_cred.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
+ if (!xdr_opaque_auth(xdrs, &_null_auth)) {
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ xdrmbuf_append(xdrs, args);
+ return (TRUE);
+ } else {
+ /*
+ * Keep track of this XID + seq pair so that we can do
+ * the matching gss_verify_mic in AUTH_VALIDATE.
+ */
+ pr = mem_alloc(sizeof(struct rpc_pending_request));
+ mtx_lock(&gd->gd_lock);
+ pr->pr_xid = xid;
+ pr->pr_seq = seq;
+ LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
+ mtx_unlock(&gd->gd_lock);
+
+ /*
+ * Checksum serialized RPC header, up to and including
+ * credential. For the in-kernel environment, we
+ * assume that our XDR stream is on a contiguous
+ * memory buffer (e.g. an mbuf).
+ */
+ rpcbuf.length = XDR_GETPOS(xdrs);
+ XDR_SETPOS(xdrs, 0);
+ rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length);
+
+ maj_stat = gss_get_mic(&min_stat, gd->gd_ctx, gd->gd_qop,
+ &rpcbuf, &checksum);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ rpc_gss_log_status("gss_get_mic", gd->gd_mech,
+ maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ rpc_gss_destroy_context(auth, TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
+ return (FALSE);
+ }
+
+ verf.oa_flavor = RPCSEC_GSS;
+ verf.oa_base = checksum.value;
+ verf.oa_length = checksum.length;
+
+ xdr_stat = xdr_opaque_auth(xdrs, &verf);
+ gss_release_buffer(&min_stat, &checksum);
+ if (!xdr_stat) {
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
+ gd->gd_cred.gc_svc == rpc_gss_svc_none) {
+ xdrmbuf_append(xdrs, args);
+ return (TRUE);
+ } else {
+ if (!xdr_rpc_gss_wrap_data(&args,
+ gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
+ seq))
+ return (FALSE);
+ xdrmbuf_append(xdrs, args);
+ return (TRUE);
+ }
+ }
+
+ return (TRUE);
+}
+
+static bool_t
+rpc_gss_validate(AUTH *auth, uint32_t xid, struct opaque_auth *verf,
+ struct mbuf **resultsp)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_pending_request *pr, *npr;
+ struct rpc_pending_request_list reqs;
+ gss_qop_t qop_state;
+ uint32_t num, seq;
+ gss_buffer_desc signbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+
+ rpc_gss_log_debug("in rpc_gss_validate()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ /*
+ * The client will call us with a NULL verf when it gives up
+ * on an XID.
+ */
+ if (!verf) {
+ rpc_gss_purge_xid(gd, xid);
+ return (TRUE);
+ }
+
+ if (gd->gd_state == RPCSEC_GSS_CONTEXT) {
+ /*
+ * Save the on the wire verifier to validate last INIT
+ * phase packet after decode if the major status is
+ * GSS_S_COMPLETE.
+ */
+ if (gd->gd_verf.value)
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_verf);
+ gd->gd_verf.value = mem_alloc(verf->oa_length);
+ if (gd->gd_verf.value == NULL) {
+ printf("gss_validate: out of memory\n");
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ m_freem(*resultsp);
+ *resultsp = NULL;
+ return (FALSE);
+ }
+ memcpy(gd->gd_verf.value, verf->oa_base, verf->oa_length);
+ gd->gd_verf.length = verf->oa_length;
+
+ return (TRUE);
+ }
+
+ /*
+ * We need to check the verifier against all the requests
+ * we've send for this XID - for unreliable protocols, we
+ * retransmit with the same XID but different sequence
+ * number. We temporarily take this set of requests out of the
+ * list so that we can work through the list without having to
+ * hold the lock.
+ */
+ mtx_lock(&gd->gd_lock);
+ LIST_INIT(&reqs);
+ LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
+ if (pr->pr_xid == xid) {
+ LIST_REMOVE(pr, pr_link);
+ LIST_INSERT_HEAD(&reqs, pr, pr_link);
+ }
+ }
+ mtx_unlock(&gd->gd_lock);
+ LIST_FOREACH(pr, &reqs, pr_link) {
+ if (pr->pr_xid == xid) {
+ seq = pr->pr_seq;
+ num = htonl(seq);
+ signbuf.value = &num;
+ signbuf.length = sizeof(num);
+
+ checksum.value = verf->oa_base;
+ checksum.length = verf->oa_length;
+
+ maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
+ &signbuf, &checksum, &qop_state);
+ if (maj_stat != GSS_S_COMPLETE
+ || qop_state != gd->gd_qop) {
+ continue;
+ }
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ rpc_gss_destroy_context(auth, TRUE);
+ break;
+ }
+ //rpc_gss_purge_reqs(gd, seq);
+ LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr)
+ mem_free(pr, sizeof(*pr));
+
+ if (gd->gd_cred.gc_svc == rpc_gss_svc_none) {
+ return (TRUE);
+ } else {
+ if (!xdr_rpc_gss_unwrap_data(resultsp,
+ gd->gd_ctx, gd->gd_qop,
+ gd->gd_cred.gc_svc, seq)) {
+ return (FALSE);
+ }
+ }
+ return (TRUE);
+ }
+ }
+
+ /*
+ * We didn't match - put back any entries for this XID so that
+ * a future call to validate can retry.
+ */
+ mtx_lock(&gd->gd_lock);
+ LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
+ LIST_REMOVE(pr, pr_link);
+ LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
+ }
+ mtx_unlock(&gd->gd_lock);
+
+ /*
+ * Nothing matches - give up.
+ */
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
+ m_freem(*resultsp);
+ *resultsp = NULL;
+ return (FALSE);
+}
+
+static bool_t
+rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
+{
+ struct thread *td = curthread;
+ struct ucred *crsave;
+ struct rpc_gss_data *gd;
+ struct rpc_gss_init_res gr;
+ gss_buffer_desc principal_desc;
+ gss_buffer_desc *recv_tokenp, recv_token, send_token;
+ gss_name_t name;
+ OM_uint32 maj_stat, min_stat, call_stat;
+ const char *mech;
+ struct rpc_callextra ext;
+
+ rpc_gss_log_debug("in rpc_gss_refresh()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ mtx_lock(&gd->gd_lock);
+ /*
+ * If the context isn't in START state, someone else is
+ * refreshing - we wait till they are done. If they fail, they
+ * will put the state back to START and we can try (most
+ * likely to also fail).
+ */
+ while (gd->gd_state != RPCSEC_GSS_START
+ && gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
+ msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
+ }
+ if (gd->gd_state == RPCSEC_GSS_ESTABLISHED) {
+ mtx_unlock(&gd->gd_lock);
+ return (TRUE);
+ }
+ gd->gd_state = RPCSEC_GSS_CONTEXT;
+ mtx_unlock(&gd->gd_lock);
+
+ principal_desc.value = (void *)gd->gd_principal;
+ principal_desc.length = strlen(gd->gd_principal);
+ maj_stat = gss_import_name(&min_stat, &principal_desc,
+ GSS_C_NT_HOSTBASED_SERVICE, &name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ options_ret->major_status = maj_stat;
+ options_ret->minor_status = min_stat;
+ goto out;
+ }
+
+ /* GSS context establishment loop. */
+ gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
+ gd->gd_cred.gc_seq = 0;
+
+ memset(&recv_token, 0, sizeof(recv_token));
+ memset(&gr, 0, sizeof(gr));
+ memset(options_ret, 0, sizeof(*options_ret));
+ options_ret->major_status = GSS_S_FAILURE;
+ recv_tokenp = GSS_C_NO_BUFFER;
+
+ for (;;) {
+ crsave = td->td_ucred;
+ td->td_ucred = gd->gd_ucred;
+ maj_stat = gss_init_sec_context(&min_stat,
+ gd->gd_options.my_cred,
+ &gd->gd_ctx,
+ name,
+ gd->gd_mech,
+ gd->gd_options.req_flags,
+ gd->gd_options.time_req,
+ gd->gd_options.input_channel_bindings,
+ recv_tokenp,
+ &gd->gd_mech, /* used mech */
+ &send_token,
+ &options_ret->ret_flags,
+ &options_ret->time_req);
+ td->td_ucred = crsave;
+
+ /*
+ * Free the token which we got from the server (if
+ * any). Remember that this was allocated by XDR, not
+ * GSS-API.
+ */
+ if (recv_tokenp != GSS_C_NO_BUFFER) {
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &recv_token);
+ recv_tokenp = GSS_C_NO_BUFFER;
+ }
+ if (gd->gd_mech && rpc_gss_oid_to_mech(gd->gd_mech, &mech)) {
+ strlcpy(options_ret->actual_mechanism,
+ mech,
+ sizeof(options_ret->actual_mechanism));
+ }
+ if (maj_stat != GSS_S_COMPLETE &&
+ maj_stat != GSS_S_CONTINUE_NEEDED) {
+ rpc_gss_log_status("gss_init_sec_context", gd->gd_mech,
+ maj_stat, min_stat);
+ options_ret->major_status = maj_stat;
+ options_ret->minor_status = min_stat;
+ break;
+ }
+ if (send_token.length != 0) {
+ memset(&gr, 0, sizeof(gr));
+
+ bzero(&ext, sizeof(ext));
+ ext.rc_auth = auth;
+ call_stat = CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
+ (xdrproc_t)xdr_gss_buffer_desc,
+ &send_token,
+ (xdrproc_t)xdr_rpc_gss_init_res,
+ (caddr_t)&gr, AUTH_TIMEOUT);
+
+ gss_release_buffer(&min_stat, &send_token);
+
+ if (call_stat != RPC_SUCCESS)
+ break;
+
+ if (gr.gr_major != GSS_S_COMPLETE &&
+ gr.gr_major != GSS_S_CONTINUE_NEEDED) {
+ rpc_gss_log_status("server reply", gd->gd_mech,
+ gr.gr_major, gr.gr_minor);
+ options_ret->major_status = gr.gr_major;
+ options_ret->minor_status = gr.gr_minor;
+ break;
+ }
+
+ /*
+ * Save the server's gr_handle value, freeing
+ * what we have already (remember that this
+ * was allocated by XDR, not GSS-API).
+ */
+ if (gr.gr_handle.length != 0) {
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_cred.gc_handle);
+ gd->gd_cred.gc_handle = gr.gr_handle;
+ }
+
+ /*
+ * Save the server's token as well.
+ */
+ if (gr.gr_token.length != 0) {
+ recv_token = gr.gr_token;
+ recv_tokenp = &recv_token;
+ }
+
+ /*
+ * Since we have copied out all the bits of gr
+ * which XDR allocated for us, we don't need
+ * to free it.
+ */
+ gd->gd_cred.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
+ }
+
+ if (maj_stat == GSS_S_COMPLETE) {
+ gss_buffer_desc bufin;
+ u_int seq, qop_state = 0;
+
+ /*
+ * gss header verifier,
+ * usually checked in gss_validate
+ */
+ seq = htonl(gr.gr_win);
+ bufin.value = (unsigned char *)&seq;
+ bufin.length = sizeof(seq);
+
+ maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
+ &bufin, &gd->gd_verf, &qop_state);
+
+ if (maj_stat != GSS_S_COMPLETE ||
+ qop_state != gd->gd_qop) {
+ rpc_gss_log_status("gss_verify_mic", gd->gd_mech,
+ maj_stat, min_stat);
+ if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
+ rpc_gss_destroy_context(auth, TRUE);
+ }
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR,
+ EPERM);
+ options_ret->major_status = maj_stat;
+ options_ret->minor_status = min_stat;
+ break;
+ }
+
+ options_ret->major_status = GSS_S_COMPLETE;
+ options_ret->minor_status = 0;
+ options_ret->rpcsec_version = gd->gd_cred.gc_version;
+ options_ret->gss_context = gd->gd_ctx;
+
+ gd->gd_cred.gc_proc = RPCSEC_GSS_DATA;
+ gd->gd_seq = 1;
+ gd->gd_win = gr.gr_win;
+ break;
+ }
+ }
+
+ gss_release_name(&min_stat, &name);
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_verf);
+
+out:
+ /* End context negotiation loop. */
+ if (gd->gd_cred.gc_proc != RPCSEC_GSS_DATA) {
+ rpc_createerr.cf_stat = RPC_AUTHERROR;
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
+ if (gd->gd_ctx) {
+ gss_delete_sec_context(&min_stat, &gd->gd_ctx,
+ GSS_C_NO_BUFFER);
+ }
+ mtx_lock(&gd->gd_lock);
+ gd->gd_state = RPCSEC_GSS_START;
+ wakeup(gd);
+ mtx_unlock(&gd->gd_lock);
+ return (FALSE);
+ }
+
+ mtx_lock(&gd->gd_lock);
+ gd->gd_state = RPCSEC_GSS_ESTABLISHED;
+ wakeup(gd);
+ mtx_unlock(&gd->gd_lock);
+
+ return (TRUE);
+}
+
+static bool_t
+rpc_gss_refresh(AUTH *auth, void *msg)
+{
+ struct rpc_msg *reply = (struct rpc_msg *) msg;
+ rpc_gss_options_ret_t options;
+
+ /*
+ * If the error was RPCSEC_GSS_CREDPROBLEM of
+ * RPCSEC_GSS_CTXPROBLEM we start again from scratch. All
+ * other errors are fatal.
+ */
+ if (reply->rm_reply.rp_stat == MSG_DENIED
+ && reply->rm_reply.rp_rjct.rj_stat == AUTH_ERROR
+ && (reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CREDPROBLEM
+ || reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CTXPROBLEM)) {
+ rpc_gss_destroy_context(auth, FALSE);
+ memset(&options, 0, sizeof(options));
+ return (rpc_gss_init(auth, &options));
+ }
+
+ return (FALSE);
+}
+
+static void
+rpc_gss_destroy_context(AUTH *auth, bool_t send_destroy)
+{
+ struct rpc_gss_data *gd;
+ struct rpc_pending_request *pr;
+ OM_uint32 min_stat;
+ struct rpc_callextra ext;
+
+ rpc_gss_log_debug("in rpc_gss_destroy_context()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ mtx_lock(&gd->gd_lock);
+ /*
+ * If the context isn't in ESTABISHED state, someone else is
+ * destroying/refreshing - we wait till they are done.
+ */
+ if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
+ while (gd->gd_state != RPCSEC_GSS_START
+ && gd->gd_state != RPCSEC_GSS_ESTABLISHED)
+ msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
+ mtx_unlock(&gd->gd_lock);
+ return;
+ }
+ gd->gd_state = RPCSEC_GSS_DESTROYING;
+ mtx_unlock(&gd->gd_lock);
+
+ if (send_destroy) {
+ gd->gd_cred.gc_proc = RPCSEC_GSS_DESTROY;
+ bzero(&ext, sizeof(ext));
+ ext.rc_auth = auth;
+ CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
+ (xdrproc_t)xdr_void, NULL,
+ (xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT);
+ }
+
+ while ((pr = LIST_FIRST(&gd->gd_reqs)) != NULL) {
+ LIST_REMOVE(pr, pr_link);
+ mem_free(pr, sizeof(*pr));
+ }
+
+ /*
+ * Free the context token. Remember that this was
+ * allocated by XDR, not GSS-API.
+ */
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_cred.gc_handle);
+ gd->gd_cred.gc_handle.length = 0;
+
+ if (gd->gd_ctx != GSS_C_NO_CONTEXT)
+ gss_delete_sec_context(&min_stat, &gd->gd_ctx, NULL);
+
+ mtx_lock(&gd->gd_lock);
+ gd->gd_state = RPCSEC_GSS_START;
+ wakeup(gd);
+ mtx_unlock(&gd->gd_lock);
+}
+
+static void
+rpc_gss_destroy(AUTH *auth)
+{
+ struct rpc_gss_data *gd;
+
+ rpc_gss_log_debug("in rpc_gss_destroy()");
+
+ gd = AUTH_PRIVATE(auth);
+
+ if (!refcount_release(&gd->gd_refs))
+ return;
+
+ rpc_gss_destroy_context(auth, TRUE);
+
+ CLNT_RELEASE(gd->gd_clnt);
+ crfree(gd->gd_ucred);
+ free(gd->gd_principal, M_RPC);
+ if (gd->gd_verf.value)
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc,
+ (char *) &gd->gd_verf);
+ mtx_destroy(&gd->gd_lock);
+
+ mem_free(gd, sizeof(*gd));
+ mem_free(auth, sizeof(*auth));
+}
+
+int
+rpc_gss_max_data_length(AUTH *auth, int max_tp_unit_len)
+{
+ struct rpc_gss_data *gd;
+ int want_conf;
+ OM_uint32 max;
+ OM_uint32 maj_stat, min_stat;
+ int result;
+
+ gd = AUTH_PRIVATE(auth);
+
+ switch (gd->gd_cred.gc_svc) {
+ case rpc_gss_svc_none:
+ return (max_tp_unit_len);
+ break;
+
+ case rpc_gss_svc_default:
+ case rpc_gss_svc_integrity:
+ want_conf = FALSE;
+ break;
+
+ case rpc_gss_svc_privacy:
+ want_conf = TRUE;
+ break;
+
+ default:
+ return (0);
+ }
+
+ maj_stat = gss_wrap_size_limit(&min_stat, gd->gd_ctx, want_conf,
+ gd->gd_qop, max_tp_unit_len, &max);
+
+ if (maj_stat == GSS_S_COMPLETE) {
+ result = (int) max;
+ if (result < 0)
+ result = 0;
+ return (result);
+ } else {
+ rpc_gss_log_status("gss_wrap_size_limit", gd->gd_mech,
+ maj_stat, min_stat);
+ return (0);
+ }
+}
OpenPOWER on IntegriCloud