2 * SPDX-License-Identifier: BSD-3-Clause
3 * Copyright (c) 1990 The Regents of the University of California.
5 * Copyright (c) 2008 Doug Rabson
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 Copyright (c) 2000 The Regents of the University of Michigan.
35 Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
36 All rights reserved, all wrongs reversed.
38 Redistribution and use in source and binary forms, with or without
39 modification, are permitted provided that the following conditions
42 1. Redistributions of source code must retain the above copyright
43 notice, this list of conditions and the following disclaimer.
44 2. Redistributions in binary form must reproduce the above copyright
45 notice, this list of conditions and the following disclaimer in the
46 documentation and/or other materials provided with the distribution.
47 3. Neither the name of the University nor the names of its
48 contributors may be used to endorse or promote products derived
49 from this software without specific prior written permission.
51 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
52 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
53 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
54 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
55 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
56 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
57 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
58 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
59 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
60 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
61 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63 $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
66 #include <sys/cdefs.h>
67 __FBSDID("$FreeBSD$");
69 #include <sys/param.h>
70 #include <sys/systm.h>
72 #include <sys/kernel.h>
75 #include <sys/malloc.h>
77 #include <sys/mutex.h>
80 #include <sys/ucred.h>
83 #include <rpc/rpcsec_gss.h>
85 #include "rpcsec_gss_int.h"
87 static bool_t svc_rpc_gss_wrap(SVCAUTH *, struct mbuf **);
88 static bool_t svc_rpc_gss_unwrap(SVCAUTH *, struct mbuf **);
89 static void svc_rpc_gss_release(SVCAUTH *);
90 static enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
91 static int rpc_gss_svc_getcred(struct svc_req *, struct ucred **, int *);
93 static struct svc_auth_ops svc_auth_gss_ops = {
99 struct sx svc_rpc_gss_lock;
101 struct svc_rpc_gss_callback {
102 SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
103 rpc_gss_callback_t cb_callback;
105 static SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
106 svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(svc_rpc_gss_callbacks);
108 struct svc_rpc_gss_svc_name {
109 SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
113 gss_cred_id_t sn_cred;
117 static SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
118 svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(svc_rpc_gss_svc_names);
120 enum svc_rpc_gss_client_state {
121 CLIENT_NEW, /* still authenticating */
122 CLIENT_ESTABLISHED, /* context established */
123 CLIENT_STALE /* garbage to collect */
126 #define SVC_RPC_GSS_SEQWINDOW 128
128 struct svc_rpc_gss_clientid {
129 unsigned long ci_hostid;
130 uint32_t ci_boottime;
134 struct svc_rpc_gss_client {
135 TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
136 TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
137 volatile u_int cl_refs;
139 struct svc_rpc_gss_clientid cl_id;
140 time_t cl_expiration; /* when to gc */
141 enum svc_rpc_gss_client_state cl_state; /* client state */
142 bool_t cl_locked; /* fixed service+qop */
143 gss_ctx_id_t cl_ctx; /* context id */
144 gss_cred_id_t cl_creds; /* delegated creds */
145 gss_name_t cl_cname; /* client name */
146 struct svc_rpc_gss_svc_name *cl_sname; /* server name used */
147 rpc_gss_rawcred_t cl_rawcred; /* raw credentials */
148 rpc_gss_ucred_t cl_ucred; /* unix-style credentials */
149 struct ucred *cl_cred; /* kernel-style credentials */
150 int cl_rpcflavor; /* RPC pseudo sec flavor */
151 bool_t cl_done_callback; /* TRUE after call */
152 void *cl_cookie; /* user cookie from callback */
153 gid_t cl_gid_storage[NGROUPS];
154 gss_OID cl_mech; /* mechanism */
155 gss_qop_t cl_qop; /* quality of protection */
156 uint32_t cl_seqlast; /* sequence window origin */
157 uint32_t cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
159 TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
162 * This structure holds enough information to unwrap arguments or wrap
163 * results for a given request. We use the rq_clntcred area for this
164 * (which is a per-request buffer).
166 struct svc_rpc_gss_cookedcred {
167 struct svc_rpc_gss_client *cc_client;
168 rpc_gss_service_t cc_service;
172 #define CLIENT_HASH_SIZE 256
173 #define CLIENT_MAX 1024
174 u_int svc_rpc_gss_client_max = CLIENT_MAX;
175 u_int svc_rpc_gss_client_hash_size = CLIENT_HASH_SIZE;
177 SYSCTL_NODE(_kern, OID_AUTO, rpc, CTLFLAG_RW, 0, "RPC");
178 SYSCTL_NODE(_kern_rpc, OID_AUTO, gss, CTLFLAG_RW, 0, "GSS");
180 SYSCTL_UINT(_kern_rpc_gss, OID_AUTO, client_max, CTLFLAG_RW,
181 &svc_rpc_gss_client_max, 0,
182 "Max number of rpc-gss clients");
184 SYSCTL_UINT(_kern_rpc_gss, OID_AUTO, client_hash, CTLFLAG_RDTUN,
185 &svc_rpc_gss_client_hash_size, 0,
186 "Size of rpc-gss client hash table");
188 static u_int svc_rpc_gss_client_count;
189 SYSCTL_UINT(_kern_rpc_gss, OID_AUTO, client_count, CTLFLAG_RD,
190 &svc_rpc_gss_client_count, 0,
191 "Number of rpc-gss clients");
193 struct svc_rpc_gss_client_list *svc_rpc_gss_client_hash;
194 struct svc_rpc_gss_client_list svc_rpc_gss_clients;
195 static uint32_t svc_rpc_gss_next_clientid = 1;
198 svc_rpc_gss_init(void *arg)
202 svc_rpc_gss_client_hash = mem_alloc(sizeof(struct svc_rpc_gss_client_list) * svc_rpc_gss_client_hash_size);
203 for (i = 0; i < svc_rpc_gss_client_hash_size; i++)
204 TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
205 TAILQ_INIT(&svc_rpc_gss_clients);
206 svc_auth_reg(RPCSEC_GSS, svc_rpc_gss, rpc_gss_svc_getcred);
207 sx_init(&svc_rpc_gss_lock, "gsslock");
209 SYSINIT(svc_rpc_gss_init, SI_SUB_KMEM, SI_ORDER_ANY, svc_rpc_gss_init, NULL);
212 rpc_gss_set_callback(rpc_gss_callback_t *cb)
214 struct svc_rpc_gss_callback *scb;
216 scb = mem_alloc(sizeof(struct svc_rpc_gss_callback));
218 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
221 scb->cb_callback = *cb;
222 sx_xlock(&svc_rpc_gss_lock);
223 SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
224 sx_xunlock(&svc_rpc_gss_lock);
230 rpc_gss_clear_callback(rpc_gss_callback_t *cb)
232 struct svc_rpc_gss_callback *scb;
234 sx_xlock(&svc_rpc_gss_lock);
235 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
236 if (scb->cb_callback.program == cb->program
237 && scb->cb_callback.version == cb->version
238 && scb->cb_callback.callback == cb->callback) {
239 SLIST_REMOVE(&svc_rpc_gss_callbacks, scb,
240 svc_rpc_gss_callback, cb_link);
241 sx_xunlock(&svc_rpc_gss_lock);
242 mem_free(scb, sizeof(*scb));
246 sx_xunlock(&svc_rpc_gss_lock);
250 rpc_gss_acquire_svc_cred(struct svc_rpc_gss_svc_name *sname)
252 OM_uint32 maj_stat, min_stat;
253 gss_buffer_desc namebuf;
255 gss_OID_set_desc oid_set;
258 oid_set.elements = sname->sn_mech;
260 namebuf.value = (void *) sname->sn_principal;
261 namebuf.length = strlen(sname->sn_principal);
263 maj_stat = gss_import_name(&min_stat, &namebuf,
264 GSS_C_NT_HOSTBASED_SERVICE, &name);
265 if (maj_stat != GSS_S_COMPLETE)
268 if (sname->sn_cred != GSS_C_NO_CREDENTIAL)
269 gss_release_cred(&min_stat, &sname->sn_cred);
271 maj_stat = gss_acquire_cred(&min_stat, name,
272 sname->sn_req_time, &oid_set, GSS_C_ACCEPT, &sname->sn_cred,
274 if (maj_stat != GSS_S_COMPLETE) {
275 gss_release_name(&min_stat, &name);
278 gss_release_name(&min_stat, &name);
284 rpc_gss_set_svc_name(const char *principal, const char *mechanism,
285 u_int req_time, u_int program, u_int version)
287 struct svc_rpc_gss_svc_name *sname;
290 if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
293 sname = mem_alloc(sizeof(*sname));
296 sname->sn_principal = strdup(principal, M_RPC);
297 sname->sn_mech = mech_oid;
298 sname->sn_req_time = req_time;
299 sname->sn_cred = GSS_C_NO_CREDENTIAL;
300 sname->sn_program = program;
301 sname->sn_version = version;
303 if (!rpc_gss_acquire_svc_cred(sname)) {
304 free(sname->sn_principal, M_RPC);
305 mem_free(sname, sizeof(*sname));
309 sx_xlock(&svc_rpc_gss_lock);
310 SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
311 sx_xunlock(&svc_rpc_gss_lock);
317 rpc_gss_clear_svc_name(u_int program, u_int version)
320 struct svc_rpc_gss_svc_name *sname;
322 sx_xlock(&svc_rpc_gss_lock);
323 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
324 if (sname->sn_program == program
325 && sname->sn_version == version) {
326 SLIST_REMOVE(&svc_rpc_gss_svc_names, sname,
327 svc_rpc_gss_svc_name, sn_link);
328 sx_xunlock(&svc_rpc_gss_lock);
329 gss_release_cred(&min_stat, &sname->sn_cred);
330 free(sname->sn_principal, M_RPC);
331 mem_free(sname, sizeof(*sname));
335 sx_xunlock(&svc_rpc_gss_lock);
339 rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
340 const char *mech, const char *name, const char *node, const char *domain)
342 OM_uint32 maj_stat, min_stat;
346 gss_name_t gss_name, gss_mech_name;
347 rpc_gss_principal_t result;
349 if (!rpc_gss_mech_to_oid(mech, &mech_oid))
353 * Construct a gss_buffer containing the full name formatted
354 * as "name/node@domain" where node and domain are optional.
356 namelen = strlen(name) + 1;
358 namelen += strlen(node) + 1;
361 namelen += strlen(domain) + 1;
364 buf.value = mem_alloc(namelen);
365 buf.length = namelen;
366 strcpy((char *) buf.value, name);
368 strcat((char *) buf.value, "/");
369 strcat((char *) buf.value, node);
372 strcat((char *) buf.value, "@");
373 strcat((char *) buf.value, domain);
377 * Convert that to a gss_name_t and then convert that to a
378 * mechanism name in the selected mechanism.
380 maj_stat = gss_import_name(&min_stat, &buf,
381 GSS_C_NT_USER_NAME, &gss_name);
382 mem_free(buf.value, buf.length);
383 if (maj_stat != GSS_S_COMPLETE) {
384 rpc_gss_log_status("gss_import_name", mech_oid, maj_stat, min_stat);
387 maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
389 if (maj_stat != GSS_S_COMPLETE) {
390 rpc_gss_log_status("gss_canonicalize_name", mech_oid, maj_stat,
392 gss_release_name(&min_stat, &gss_name);
395 gss_release_name(&min_stat, &gss_name);
398 * Export the mechanism name and use that to construct the
399 * rpc_gss_principal_t result.
401 maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
402 if (maj_stat != GSS_S_COMPLETE) {
403 rpc_gss_log_status("gss_export_name", mech_oid, maj_stat, min_stat);
404 gss_release_name(&min_stat, &gss_mech_name);
407 gss_release_name(&min_stat, &gss_mech_name);
409 result = mem_alloc(sizeof(int) + buf.length);
411 gss_release_buffer(&min_stat, &buf);
414 result->len = buf.length;
415 memcpy(result->name, buf.value, buf.length);
416 gss_release_buffer(&min_stat, &buf);
423 rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
424 rpc_gss_ucred_t **ucred, void **cookie)
426 struct svc_rpc_gss_cookedcred *cc;
427 struct svc_rpc_gss_client *client;
429 if (req->rq_cred.oa_flavor != RPCSEC_GSS)
432 cc = req->rq_clntcred;
433 client = cc->cc_client;
435 *rcred = &client->cl_rawcred;
437 *ucred = &client->cl_ucred;
439 *cookie = client->cl_cookie;
444 * This simpler interface is used by svc_getcred to copy the cred data
445 * into a kernel cred structure.
448 rpc_gss_svc_getcred(struct svc_req *req, struct ucred **crp, int *flavorp)
451 struct svc_rpc_gss_cookedcred *cc;
452 struct svc_rpc_gss_client *client;
455 if (req->rq_cred.oa_flavor != RPCSEC_GSS)
458 cc = req->rq_clntcred;
459 client = cc->cc_client;
462 *flavorp = client->cl_rpcflavor;
464 if (client->cl_cred) {
465 *crp = crhold(client->cl_cred);
469 uc = &client->cl_ucred;
470 cr = client->cl_cred = crget();
471 cr->cr_uid = cr->cr_ruid = cr->cr_svuid = uc->uid;
472 cr->cr_rgid = cr->cr_svgid = uc->gid;
473 crsetgroups(cr, uc->gidlen, uc->gidlist);
474 cr->cr_prison = &prison0;
475 prison_hold(cr->cr_prison);
482 rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
484 struct svc_rpc_gss_cookedcred *cc = req->rq_clntcred;
485 struct svc_rpc_gss_client *client = cc->cc_client;
488 OM_uint32 maj_stat, min_stat;
491 switch (client->cl_rawcred.service) {
492 case rpc_gss_svc_none:
493 return (max_tp_unit_len);
496 case rpc_gss_svc_default:
497 case rpc_gss_svc_integrity:
501 case rpc_gss_svc_privacy:
509 maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
510 client->cl_qop, max_tp_unit_len, &max);
512 if (maj_stat == GSS_S_COMPLETE) {
518 rpc_gss_log_status("gss_wrap_size_limit", client->cl_mech,
524 static struct svc_rpc_gss_client *
525 svc_rpc_gss_find_client(struct svc_rpc_gss_clientid *id)
527 struct svc_rpc_gss_client *client;
528 struct svc_rpc_gss_client_list *list;
529 struct timeval boottime;
530 unsigned long hostid;
532 rpc_gss_log_debug("in svc_rpc_gss_find_client(%d)", id->ci_id);
534 getcredhostid(curthread->td_ucred, &hostid);
535 getboottime(&boottime);
536 if (id->ci_hostid != hostid || id->ci_boottime != boottime.tv_sec)
539 list = &svc_rpc_gss_client_hash[id->ci_id % svc_rpc_gss_client_hash_size];
540 sx_xlock(&svc_rpc_gss_lock);
541 TAILQ_FOREACH(client, list, cl_link) {
542 if (client->cl_id.ci_id == id->ci_id) {
544 * Move this client to the front of the LRU
547 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
548 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
550 refcount_acquire(&client->cl_refs);
554 sx_xunlock(&svc_rpc_gss_lock);
559 static struct svc_rpc_gss_client *
560 svc_rpc_gss_create_client(void)
562 struct svc_rpc_gss_client *client;
563 struct svc_rpc_gss_client_list *list;
564 struct timeval boottime;
565 unsigned long hostid;
567 rpc_gss_log_debug("in svc_rpc_gss_create_client()");
569 client = mem_alloc(sizeof(struct svc_rpc_gss_client));
570 memset(client, 0, sizeof(struct svc_rpc_gss_client));
573 * Set the initial value of cl_refs to two. One for the caller
574 * and the other to hold onto the client structure until it expires.
576 refcount_init(&client->cl_refs, 2);
577 sx_init(&client->cl_lock, "GSS-client");
578 getcredhostid(curthread->td_ucred, &hostid);
579 client->cl_id.ci_hostid = hostid;
580 getboottime(&boottime);
581 client->cl_id.ci_boottime = boottime.tv_sec;
582 client->cl_id.ci_id = svc_rpc_gss_next_clientid++;
585 * Start the client off with a short expiration time. We will
586 * try to get a saner value from the client creds later.
588 client->cl_state = CLIENT_NEW;
589 client->cl_locked = FALSE;
590 client->cl_expiration = time_uptime + 5*60;
592 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % svc_rpc_gss_client_hash_size];
593 sx_xlock(&svc_rpc_gss_lock);
594 TAILQ_INSERT_HEAD(list, client, cl_link);
595 TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
596 svc_rpc_gss_client_count++;
597 sx_xunlock(&svc_rpc_gss_lock);
602 svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
606 rpc_gss_log_debug("in svc_rpc_gss_destroy_client()");
609 gss_delete_sec_context(&min_stat,
610 &client->cl_ctx, GSS_C_NO_BUFFER);
612 if (client->cl_cname)
613 gss_release_name(&min_stat, &client->cl_cname);
615 if (client->cl_rawcred.client_principal)
616 mem_free(client->cl_rawcred.client_principal,
617 sizeof(*client->cl_rawcred.client_principal)
618 + client->cl_rawcred.client_principal->len);
621 crfree(client->cl_cred);
623 sx_destroy(&client->cl_lock);
624 mem_free(client, sizeof(*client));
628 * Drop a reference to a client and free it if that was the last reference.
631 svc_rpc_gss_release_client(struct svc_rpc_gss_client *client)
634 if (!refcount_release(&client->cl_refs))
636 svc_rpc_gss_destroy_client(client);
640 * Remove a client from our global lists.
641 * Must be called with svc_rpc_gss_lock held.
644 svc_rpc_gss_forget_client_locked(struct svc_rpc_gss_client *client)
646 struct svc_rpc_gss_client_list *list;
648 sx_assert(&svc_rpc_gss_lock, SX_XLOCKED);
649 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % svc_rpc_gss_client_hash_size];
650 TAILQ_REMOVE(list, client, cl_link);
651 TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
652 svc_rpc_gss_client_count--;
656 * Remove a client from our global lists and free it if we can.
659 svc_rpc_gss_forget_client(struct svc_rpc_gss_client *client)
661 struct svc_rpc_gss_client_list *list;
662 struct svc_rpc_gss_client *tclient;
664 list = &svc_rpc_gss_client_hash[client->cl_id.ci_id % svc_rpc_gss_client_hash_size];
665 sx_xlock(&svc_rpc_gss_lock);
666 TAILQ_FOREACH(tclient, list, cl_link) {
668 * Make sure this client has not already been removed
669 * from the lists by svc_rpc_gss_forget_client() or
670 * svc_rpc_gss_forget_client_locked().
672 if (client == tclient) {
673 svc_rpc_gss_forget_client_locked(client);
674 sx_xunlock(&svc_rpc_gss_lock);
675 svc_rpc_gss_release_client(client);
679 sx_xunlock(&svc_rpc_gss_lock);
683 svc_rpc_gss_timeout_clients(void)
685 struct svc_rpc_gss_client *client;
686 time_t now = time_uptime;
688 rpc_gss_log_debug("in svc_rpc_gss_timeout_clients()");
691 * First enforce the max client limit. We keep
692 * svc_rpc_gss_clients in LRU order.
694 sx_xlock(&svc_rpc_gss_lock);
695 client = TAILQ_LAST(&svc_rpc_gss_clients, svc_rpc_gss_client_list);
696 while (svc_rpc_gss_client_count > svc_rpc_gss_client_max && client != NULL) {
697 svc_rpc_gss_forget_client_locked(client);
698 sx_xunlock(&svc_rpc_gss_lock);
699 svc_rpc_gss_release_client(client);
700 sx_xlock(&svc_rpc_gss_lock);
701 client = TAILQ_LAST(&svc_rpc_gss_clients,
702 svc_rpc_gss_client_list);
705 TAILQ_FOREACH(client, &svc_rpc_gss_clients, cl_alllink) {
706 if (client->cl_state == CLIENT_STALE
707 || now > client->cl_expiration) {
708 svc_rpc_gss_forget_client_locked(client);
709 sx_xunlock(&svc_rpc_gss_lock);
710 rpc_gss_log_debug("expiring client %p", client);
711 svc_rpc_gss_release_client(client);
712 sx_xlock(&svc_rpc_gss_lock);
716 sx_xunlock(&svc_rpc_gss_lock);
721 * OID<->string routines. These are uuuuugly.
724 gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
727 unsigned long number;
729 size_t string_length;
734 /* Decoded according to krb5/gssapi_krb5.c */
736 /* First determine the size of the string */
740 cp = (unsigned char *) oid->elements;
741 number = (unsigned long) cp[0];
742 sprintf(numstr, "%ld ", number/40);
743 string_length += strlen(numstr);
744 sprintf(numstr, "%ld ", number%40);
745 string_length += strlen(numstr);
746 for (i=1; i<oid->length; i++) {
747 if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
748 number = (number << 7) | (cp[i] & 0x7f);
753 return(GSS_S_FAILURE);
755 if ((cp[i] & 0x80) == 0) {
756 sprintf(numstr, "%ld ", number);
757 string_length += strlen(numstr);
763 * If we get here, we've calculated the length of "n n n ... n ". Add 4
764 * here for "{ " and "}\0".
767 if ((bp = malloc(string_length, M_GSSAPI, M_WAITOK | M_ZERO))) {
769 number = (unsigned long) cp[0];
770 sprintf(numstr, "%ld ", number/40);
772 sprintf(numstr, "%ld ", number%40);
775 cp = (unsigned char *) oid->elements;
776 for (i=1; i<oid->length; i++) {
777 number = (number << 7) | (cp[i] & 0x7f);
778 if ((cp[i] & 0x80) == 0) {
779 sprintf(numstr, "%ld ", number);
785 oid_str->length = strlen(bp)+1;
786 oid_str->value = (void *) bp;
788 return(GSS_S_COMPLETE);
791 return(GSS_S_FAILURE);
796 svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
797 const gss_name_t name)
799 OM_uint32 maj_stat, min_stat;
800 rpc_gss_ucred_t *uc = &client->cl_ucred;
805 uc->gidlist = client->cl_gid_storage;
808 maj_stat = gss_pname_to_unix_cred(&min_stat, name, client->cl_mech,
809 &uc->uid, &uc->gid, &numgroups, &uc->gidlist[0]);
810 if (GSS_ERROR(maj_stat))
813 uc->gidlen = numgroups;
817 svc_rpc_gss_set_flavor(struct svc_rpc_gss_client *client)
819 static gss_OID_desc krb5_mech_oid =
820 {9, (void *) "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02" };
823 * Attempt to translate mech type and service into a
824 * 'pseudo flavor'. Hardwire in krb5 support for now.
826 if (kgss_oid_equal(client->cl_mech, &krb5_mech_oid)) {
827 switch (client->cl_rawcred.service) {
828 case rpc_gss_svc_default:
829 case rpc_gss_svc_none:
830 client->cl_rpcflavor = RPCSEC_GSS_KRB5;
832 case rpc_gss_svc_integrity:
833 client->cl_rpcflavor = RPCSEC_GSS_KRB5I;
835 case rpc_gss_svc_privacy:
836 client->cl_rpcflavor = RPCSEC_GSS_KRB5P;
840 client->cl_rpcflavor = RPCSEC_GSS;
845 svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
846 struct svc_req *rqst,
847 struct rpc_gss_init_res *gr,
848 struct rpc_gss_cred *gc)
850 gss_buffer_desc recv_tok;
852 OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
853 OM_uint32 cred_lifetime;
854 struct svc_rpc_gss_svc_name *sname;
856 rpc_gss_log_debug("in svc_rpc_gss_accept_context()");
858 /* Deserialize arguments. */
859 memset(&recv_tok, 0, sizeof(recv_tok));
861 if (!svc_getargs(rqst,
862 (xdrproc_t) xdr_gss_buffer_desc,
863 (caddr_t) &recv_tok)) {
864 client->cl_state = CLIENT_STALE;
869 * First time round, try all the server names we have until
870 * one matches. Afterwards, stick with that one.
872 sx_xlock(&svc_rpc_gss_lock);
873 if (!client->cl_sname) {
874 SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
875 if (sname->sn_program == rqst->rq_prog
876 && sname->sn_version == rqst->rq_vers) {
878 gr->gr_major = gss_accept_sec_context(
883 GSS_C_NO_CHANNEL_BINDINGS,
891 GSS_S_CREDENTIALS_EXPIRED) {
893 * Either our creds really did
897 if (rpc_gss_acquire_svc_cred(sname))
900 client->cl_sname = sname;
905 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
907 sx_xunlock(&svc_rpc_gss_lock);
911 gr->gr_major = gss_accept_sec_context(
914 client->cl_sname->sn_cred,
916 GSS_C_NO_CHANNEL_BINDINGS,
924 sx_xunlock(&svc_rpc_gss_lock);
926 xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
929 * If we get an error from gss_accept_sec_context, send the
930 * reply anyway so that the client gets a chance to see what
933 if (gr->gr_major != GSS_S_COMPLETE &&
934 gr->gr_major != GSS_S_CONTINUE_NEEDED) {
935 rpc_gss_log_status("accept_sec_context", client->cl_mech,
936 gr->gr_major, gr->gr_minor);
937 client->cl_state = CLIENT_STALE;
941 gr->gr_handle.value = &client->cl_id;
942 gr->gr_handle.length = sizeof(client->cl_id);
943 gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
945 /* Save client info. */
946 client->cl_mech = mech;
947 client->cl_qop = GSS_C_QOP_DEFAULT;
948 client->cl_done_callback = FALSE;
950 if (gr->gr_major == GSS_S_COMPLETE) {
951 gss_buffer_desc export_name;
954 * Change client expiration time to be near when the
955 * client creds expire (or 24 hours if we can't figure
958 if (cred_lifetime == GSS_C_INDEFINITE)
959 cred_lifetime = time_uptime + 24*60*60;
961 client->cl_expiration = time_uptime + cred_lifetime;
964 * Fill in cred details in the rawcred structure.
966 client->cl_rawcred.version = RPCSEC_GSS_VERSION;
967 rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
968 maj_stat = gss_export_name(&min_stat, client->cl_cname,
970 if (maj_stat != GSS_S_COMPLETE) {
971 rpc_gss_log_status("gss_export_name", client->cl_mech,
975 client->cl_rawcred.client_principal =
976 mem_alloc(sizeof(*client->cl_rawcred.client_principal)
977 + export_name.length);
978 client->cl_rawcred.client_principal->len = export_name.length;
979 memcpy(client->cl_rawcred.client_principal->name,
980 export_name.value, export_name.length);
981 gss_release_buffer(&min_stat, &export_name);
982 client->cl_rawcred.svc_principal =
983 client->cl_sname->sn_principal;
984 client->cl_rawcred.service = gc->gc_svc;
987 * Use gss_pname_to_uid to map to unix creds. For
988 * kerberos5, this uses krb5_aname_to_localname.
990 svc_rpc_gss_build_ucred(client, client->cl_cname);
991 svc_rpc_gss_set_flavor(client);
992 gss_release_name(&min_stat, &client->cl_cname);
996 gss_buffer_desc mechname;
998 gss_oid_to_str(&min_stat, mech, &mechname);
1000 rpc_gss_log_debug("accepted context for %s with "
1001 "<mech %.*s, qop %d, svc %d>",
1002 client->cl_rawcred.client_principal->name,
1003 mechname.length, (char *)mechname.value,
1004 client->cl_qop, client->cl_rawcred.service);
1006 gss_release_buffer(&min_stat, &mechname);
1014 svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
1015 gss_qop_t *qop, rpc_gss_proc_t gcproc)
1017 struct opaque_auth *oa;
1018 gss_buffer_desc rpcbuf, checksum;
1019 OM_uint32 maj_stat, min_stat;
1020 gss_qop_t qop_state;
1021 int32_t rpchdr[128 / sizeof(int32_t)];
1024 rpc_gss_log_debug("in svc_rpc_gss_validate()");
1026 memset(rpchdr, 0, sizeof(rpchdr));
1028 /* Reconstruct RPC header for signing (from xdr_callmsg). */
1030 IXDR_PUT_LONG(buf, msg->rm_xid);
1031 IXDR_PUT_ENUM(buf, msg->rm_direction);
1032 IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
1033 IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
1034 IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
1035 IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
1036 oa = &msg->rm_call.cb_cred;
1037 IXDR_PUT_ENUM(buf, oa->oa_flavor);
1038 IXDR_PUT_LONG(buf, oa->oa_length);
1039 if (oa->oa_length) {
1040 memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
1041 buf += RNDUP(oa->oa_length) / sizeof(int32_t);
1043 rpcbuf.value = rpchdr;
1044 rpcbuf.length = (u_char *)buf - (u_char *)rpchdr;
1046 checksum.value = msg->rm_call.cb_verf.oa_base;
1047 checksum.length = msg->rm_call.cb_verf.oa_length;
1049 maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
1052 if (maj_stat != GSS_S_COMPLETE) {
1053 rpc_gss_log_status("gss_verify_mic", client->cl_mech,
1054 maj_stat, min_stat);
1056 * A bug in some versions of the Linux client generates a
1057 * Destroy operation with a bogus encrypted checksum. Deleting
1058 * the credential handle for that case causes the mount to fail.
1059 * Since the checksum is bogus (gss_verify_mic() failed), it
1060 * doesn't make sense to destroy the handle and not doing so
1061 * fixes the Linux mount.
1063 if (gcproc != RPCSEC_GSS_DESTROY)
1064 client->cl_state = CLIENT_STALE;
1073 svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
1074 struct svc_req *rqst, u_int seq)
1076 gss_buffer_desc signbuf;
1077 gss_buffer_desc mic;
1078 OM_uint32 maj_stat, min_stat;
1081 rpc_gss_log_debug("in svc_rpc_gss_nextverf()");
1084 signbuf.value = &nseq;
1085 signbuf.length = sizeof(nseq);
1087 maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
1090 if (maj_stat != GSS_S_COMPLETE) {
1091 rpc_gss_log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
1092 client->cl_state = CLIENT_STALE;
1096 KASSERT(mic.length <= MAX_AUTH_BYTES,
1097 ("MIC too large for RPCSEC_GSS"));
1099 rqst->rq_verf.oa_flavor = RPCSEC_GSS;
1100 rqst->rq_verf.oa_length = mic.length;
1101 bcopy(mic.value, rqst->rq_verf.oa_base, mic.length);
1103 gss_release_buffer(&min_stat, &mic);
1109 svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
1111 struct svc_rpc_gss_callback *scb;
1112 rpc_gss_lock_t lock;
1118 * See if we have a callback for this guy.
1121 SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
1122 if (scb->cb_callback.program == rqst->rq_prog
1123 && scb->cb_callback.version == rqst->rq_vers) {
1125 * This one matches. Call the callback and see
1126 * if it wants to veto or something.
1128 lock.locked = FALSE;
1129 lock.raw_cred = &client->cl_rawcred;
1130 cb_res = scb->cb_callback.callback(rqst,
1137 client->cl_state = CLIENT_STALE;
1143 * The callback accepted the connection - it
1144 * is responsible for freeing client->cl_creds
1147 client->cl_creds = GSS_C_NO_CREDENTIAL;
1148 client->cl_locked = lock.locked;
1149 client->cl_cookie = cookie;
1155 * Either no callback exists for this program/version or one
1156 * of the callbacks rejected the connection. We just need to
1157 * clean up the delegated client creds, if any.
1159 if (client->cl_creds) {
1161 gss_release_cred(&min_ver, &client->cl_creds);
1167 svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
1173 sx_xlock(&client->cl_lock);
1174 if (seq <= client->cl_seqlast) {
1176 * The request sequence number is less than
1177 * the largest we have seen so far. If it is
1178 * outside the window or if we have seen a
1179 * request with this sequence before, silently
1182 offset = client->cl_seqlast - seq;
1183 if (offset >= SVC_RPC_GSS_SEQWINDOW) {
1189 if (client->cl_seqmask[word] & (1 << bit)) {
1197 sx_xunlock(&client->cl_lock);
1202 svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
1204 int offset, i, word, bit;
1205 uint32_t carry, newcarry;
1207 sx_xlock(&client->cl_lock);
1208 if (seq > client->cl_seqlast) {
1210 * This request has a sequence number greater
1211 * than any we have seen so far. Advance the
1212 * seq window and set bit zero of the window
1213 * (which corresponds to the new sequence
1216 offset = seq - client->cl_seqlast;
1217 while (offset > 32) {
1218 for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
1220 client->cl_seqmask[i] = client->cl_seqmask[i-1];
1222 client->cl_seqmask[0] = 0;
1226 for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
1227 newcarry = client->cl_seqmask[i] >> (32 - offset);
1228 client->cl_seqmask[i] =
1229 (client->cl_seqmask[i] << offset) | carry;
1232 client->cl_seqmask[0] |= 1;
1233 client->cl_seqlast = seq;
1235 offset = client->cl_seqlast - seq;
1238 client->cl_seqmask[word] |= (1 << bit);
1240 sx_xunlock(&client->cl_lock);
1244 svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
1249 struct svc_rpc_gss_cookedcred *cc;
1250 struct svc_rpc_gss_client *client;
1251 struct rpc_gss_cred gc;
1252 struct rpc_gss_init_res gr;
1255 enum auth_stat result;
1257 rpc_gss_log_debug("in svc_rpc_gss()");
1259 /* Garbage collect old clients. */
1260 svc_rpc_gss_timeout_clients();
1262 /* Initialize reply. */
1263 rqst->rq_verf = _null_auth;
1265 /* Deserialize client credentials. */
1266 if (rqst->rq_cred.oa_length <= 0)
1267 return (AUTH_BADCRED);
1269 memset(&gc, 0, sizeof(gc));
1271 xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
1272 rqst->rq_cred.oa_length, XDR_DECODE);
1274 if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
1276 return (AUTH_BADCRED);
1282 /* Check version. */
1283 if (gc.gc_version != RPCSEC_GSS_VERSION) {
1284 result = AUTH_BADCRED;
1288 /* Check the proc and find the client (or create it) */
1289 if (gc.gc_proc == RPCSEC_GSS_INIT) {
1290 if (gc.gc_handle.length != 0) {
1291 result = AUTH_BADCRED;
1294 client = svc_rpc_gss_create_client();
1296 struct svc_rpc_gss_clientid *p;
1297 if (gc.gc_handle.length != sizeof(*p)) {
1298 result = AUTH_BADCRED;
1301 p = gc.gc_handle.value;
1302 client = svc_rpc_gss_find_client(p);
1305 * Can't find the client - we may have
1306 * destroyed it - tell the other side to
1309 result = RPCSEC_GSS_CREDPROBLEM;
1313 cc = rqst->rq_clntcred;
1314 cc->cc_client = client;
1315 cc->cc_service = gc.gc_svc;
1316 cc->cc_seq = gc.gc_seq;
1319 * The service and sequence number must be ignored for
1320 * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
1322 if (gc.gc_proc != RPCSEC_GSS_INIT
1323 && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
1325 * Check for sequence number overflow.
1327 if (gc.gc_seq >= MAXSEQ) {
1328 result = RPCSEC_GSS_CTXPROBLEM;
1333 * Check for valid service.
1335 if (gc.gc_svc != rpc_gss_svc_none &&
1336 gc.gc_svc != rpc_gss_svc_integrity &&
1337 gc.gc_svc != rpc_gss_svc_privacy) {
1338 result = AUTH_BADCRED;
1343 /* Handle RPCSEC_GSS control procedure. */
1344 switch (gc.gc_proc) {
1346 case RPCSEC_GSS_INIT:
1347 case RPCSEC_GSS_CONTINUE_INIT:
1348 if (rqst->rq_proc != NULLPROC) {
1349 result = AUTH_REJECTEDCRED;
1353 memset(&gr, 0, sizeof(gr));
1354 if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
1355 result = AUTH_REJECTEDCRED;
1359 if (gr.gr_major == GSS_S_COMPLETE) {
1361 * We borrow the space for the call verf to
1362 * pack our reply verf.
1364 rqst->rq_verf = msg->rm_call.cb_verf;
1365 if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
1366 result = AUTH_REJECTEDCRED;
1370 rqst->rq_verf = _null_auth;
1373 call_stat = svc_sendreply(rqst,
1374 (xdrproc_t) xdr_rpc_gss_init_res,
1377 gss_release_buffer(&min_stat, &gr.gr_token);
1380 result = AUTH_FAILED;
1384 if (gr.gr_major == GSS_S_COMPLETE)
1385 client->cl_state = CLIENT_ESTABLISHED;
1387 result = RPCSEC_GSS_NODISPATCH;
1390 case RPCSEC_GSS_DATA:
1391 case RPCSEC_GSS_DESTROY:
1392 if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
1393 result = RPCSEC_GSS_NODISPATCH;
1397 if (!svc_rpc_gss_validate(client, msg, &qop, gc.gc_proc)) {
1398 result = RPCSEC_GSS_CREDPROBLEM;
1403 * We borrow the space for the call verf to pack our
1406 rqst->rq_verf = msg->rm_call.cb_verf;
1407 if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
1408 result = RPCSEC_GSS_CTXPROBLEM;
1412 svc_rpc_gss_update_seq(client, gc.gc_seq);
1415 * Change the SVCAUTH ops on the request to point at
1416 * our own code so that we can unwrap the arguments
1417 * and wrap the result. The caller will re-set this on
1418 * every request to point to a set of null wrap/unwrap
1419 * methods. Acquire an extra reference to the client
1420 * which will be released by svc_rpc_gss_release()
1421 * after the request has finished processing.
1423 refcount_acquire(&client->cl_refs);
1424 rqst->rq_auth.svc_ah_ops = &svc_auth_gss_ops;
1425 rqst->rq_auth.svc_ah_private = cc;
1427 if (gc.gc_proc == RPCSEC_GSS_DATA) {
1429 * We might be ready to do a callback to the server to
1430 * see if it wants to accept/reject the connection.
1432 sx_xlock(&client->cl_lock);
1433 if (!client->cl_done_callback) {
1434 client->cl_done_callback = TRUE;
1435 client->cl_qop = qop;
1436 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1437 client->cl_rawcred.mechanism, qop);
1438 if (!svc_rpc_gss_callback(client, rqst)) {
1439 result = AUTH_REJECTEDCRED;
1440 sx_xunlock(&client->cl_lock);
1444 sx_xunlock(&client->cl_lock);
1447 * If the server has locked this client to a
1448 * particular service+qop pair, enforce that
1451 if (client->cl_locked) {
1452 if (client->cl_rawcred.service != gc.gc_svc) {
1453 result = AUTH_FAILED;
1455 } else if (client->cl_qop != qop) {
1456 result = AUTH_BADVERF;
1462 * If the qop changed, look up the new qop
1465 if (client->cl_qop != qop) {
1466 client->cl_qop = qop;
1467 client->cl_rawcred.qop = _rpc_gss_num_to_qop(
1468 client->cl_rawcred.mechanism, qop);
1472 * Make sure we use the right service value
1475 if (client->cl_rawcred.service != gc.gc_svc) {
1476 client->cl_rawcred.service = gc.gc_svc;
1477 svc_rpc_gss_set_flavor(client);
1482 if (rqst->rq_proc != NULLPROC) {
1483 result = AUTH_REJECTEDCRED;
1487 call_stat = svc_sendreply(rqst,
1488 (xdrproc_t) xdr_void, (caddr_t) NULL);
1491 result = AUTH_FAILED;
1495 svc_rpc_gss_forget_client(client);
1497 result = RPCSEC_GSS_NODISPATCH;
1503 result = AUTH_BADCRED;
1508 svc_rpc_gss_release_client(client);
1510 xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
1515 svc_rpc_gss_wrap(SVCAUTH *auth, struct mbuf **mp)
1517 struct svc_rpc_gss_cookedcred *cc;
1518 struct svc_rpc_gss_client *client;
1520 rpc_gss_log_debug("in svc_rpc_gss_wrap()");
1522 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1523 client = cc->cc_client;
1524 if (client->cl_state != CLIENT_ESTABLISHED
1525 || cc->cc_service == rpc_gss_svc_none || *mp == NULL) {
1529 return (xdr_rpc_gss_wrap_data(mp,
1530 client->cl_ctx, client->cl_qop,
1531 cc->cc_service, cc->cc_seq));
1535 svc_rpc_gss_unwrap(SVCAUTH *auth, struct mbuf **mp)
1537 struct svc_rpc_gss_cookedcred *cc;
1538 struct svc_rpc_gss_client *client;
1540 rpc_gss_log_debug("in svc_rpc_gss_unwrap()");
1542 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1543 client = cc->cc_client;
1544 if (client->cl_state != CLIENT_ESTABLISHED
1545 || cc->cc_service == rpc_gss_svc_none) {
1549 return (xdr_rpc_gss_unwrap_data(mp,
1550 client->cl_ctx, client->cl_qop,
1551 cc->cc_service, cc->cc_seq));
1555 svc_rpc_gss_release(SVCAUTH *auth)
1557 struct svc_rpc_gss_cookedcred *cc;
1558 struct svc_rpc_gss_client *client;
1560 rpc_gss_log_debug("in svc_rpc_gss_release()");
1562 cc = (struct svc_rpc_gss_cookedcred *) auth->svc_ah_private;
1563 client = cc->cc_client;
1564 svc_rpc_gss_release_client(client);