1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2008 Doug Rabson
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28 /*
29 auth_gss.c
30
31 RPCSEC_GSS client routines.
32
33 Copyright (c) 2000 The Regents of the University of Michigan.
34 All rights reserved.
35
36 Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
37 All rights reserved, all wrongs reversed.
38
39 Redistribution and use in source and binary forms, with or without
40 modification, are permitted provided that the following conditions
41 are met:
42
43 1. Redistributions of source code must retain the above copyright
44 notice, this list of conditions and the following disclaimer.
45 2. Redistributions in binary form must reproduce the above copyright
46 notice, this list of conditions and the following disclaimer in the
47 documentation and/or other materials provided with the distribution.
48 3. Neither the name of the University nor the names of its
49 contributors may be used to endorse or promote products derived
50 from this software without specific prior written permission.
51
52 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
53 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
54 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
55 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
57 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
58 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
59 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
60 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
61 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
62 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
63
64 $Id: auth_gss.c,v 1.32 2002/01/15 15:43:00 andros Exp $
65 */
66
67 #include <sys/param.h>
68 #include <sys/systm.h>
69 #include <sys/hash.h>
70 #include <sys/jail.h>
71 #include <sys/kernel.h>
72 #include <sys/kobj.h>
73 #include <sys/lock.h>
74 #include <sys/malloc.h>
75 #include <sys/mbuf.h>
76 #include <sys/mutex.h>
77 #include <sys/proc.h>
78 #include <sys/refcount.h>
79 #include <sys/sx.h>
80 #include <sys/ucred.h>
81
82 #include <rpc/rpc.h>
83 #include <rpc/rpcsec_gss.h>
84
85 #include <kgssapi/krb5/kcrypto.h>
86
87 #include "rpcsec_gss_int.h"
88
89 static void rpc_gss_nextverf(AUTH*);
90 static bool_t rpc_gss_marshal(AUTH *, uint32_t, XDR *, struct mbuf *);
91 static bool_t rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret);
92 static bool_t rpc_gss_refresh(AUTH *, void *);
93 static bool_t rpc_gss_validate(AUTH *, uint32_t, struct opaque_auth *,
94 struct mbuf **);
95 static void rpc_gss_destroy(AUTH *);
96 static void rpc_gss_destroy_context(AUTH *, bool_t);
97
98 static const struct auth_ops rpc_gss_ops = {
99 .ah_nextverf = rpc_gss_nextverf,
100 .ah_marshal = rpc_gss_marshal,
101 .ah_validate = rpc_gss_validate,
102 .ah_refresh = rpc_gss_refresh,
103 .ah_destroy = rpc_gss_destroy,
104 };
105
106 enum rpcsec_gss_state {
107 RPCSEC_GSS_START,
108 RPCSEC_GSS_CONTEXT,
109 RPCSEC_GSS_ESTABLISHED,
110 RPCSEC_GSS_DESTROYING
111 };
112
113 struct rpc_pending_request {
114 uint32_t pr_xid; /* XID of rpc */
115 uint32_t pr_seq; /* matching GSS seq */
116 LIST_ENTRY(rpc_pending_request) pr_link;
117 };
118 LIST_HEAD(rpc_pending_request_list, rpc_pending_request);
119
120 struct rpc_gss_data {
121 volatile u_int gd_refs; /* number of current users */
122 struct mtx gd_lock;
123 uint32_t gd_hash;
124 AUTH *gd_auth; /* link back to AUTH */
125 struct ucred *gd_ucred; /* matching local cred */
126 char *gd_principal; /* server principal name */
127 char *gd_clntprincipal; /* client principal name */
128 rpc_gss_options_req_t gd_options; /* GSS context options */
129 enum rpcsec_gss_state gd_state; /* connection state */
130 gss_buffer_desc gd_verf; /* save GSS_S_COMPLETE
131 * NULL RPC verfier to
132 * process at end of
133 * context negotiation */
134 CLIENT *gd_clnt; /* client handle */
135 gss_OID gd_mech; /* mechanism to use */
136 gss_qop_t gd_qop; /* quality of protection */
137 gss_ctx_id_t gd_ctx; /* context id */
138 struct rpc_gss_cred gd_cred; /* client credentials */
139 uint32_t gd_seq; /* next sequence number */
140 u_int gd_win; /* sequence window */
141 struct rpc_pending_request_list gd_reqs;
142 TAILQ_ENTRY(rpc_gss_data) gd_link;
143 TAILQ_ENTRY(rpc_gss_data) gd_alllink;
144 };
145 TAILQ_HEAD(rpc_gss_data_list, rpc_gss_data);
146
147 #define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private)
148
149 static struct timeval AUTH_TIMEOUT = { 25, 0 };
150
151 #define RPC_GSS_HASH_SIZE 11
152 #define RPC_GSS_MAX 256
153 static struct rpc_gss_data_list rpc_gss_cache[RPC_GSS_HASH_SIZE];
154 static struct rpc_gss_data_list rpc_gss_all;
155 static struct sx rpc_gss_lock;
156 static int rpc_gss_count;
157
158 static AUTH *rpc_gss_seccreate_int(CLIENT *, struct ucred *, const char *,
159 const char *, gss_OID, rpc_gss_service_t, u_int, rpc_gss_options_req_t *,
160 rpc_gss_options_ret_t *);
161
162 static void
rpc_gss_hashinit(void * dummy)163 rpc_gss_hashinit(void *dummy)
164 {
165 int i;
166
167 for (i = 0; i < RPC_GSS_HASH_SIZE; i++)
168 TAILQ_INIT(&rpc_gss_cache[i]);
169 TAILQ_INIT(&rpc_gss_all);
170 sx_init(&rpc_gss_lock, "rpc_gss_lock");
171 }
172 SYSINIT(rpc_gss_hashinit, SI_SUB_KMEM, SI_ORDER_ANY, rpc_gss_hashinit, NULL);
173
174 static uint32_t
rpc_gss_hash(const char * principal,gss_OID mech,struct ucred * cred,rpc_gss_service_t service)175 rpc_gss_hash(const char *principal, gss_OID mech,
176 struct ucred *cred, rpc_gss_service_t service)
177 {
178 uint32_t h;
179
180 h = HASHSTEP(HASHINIT, cred->cr_uid);
181 h = hash32_str(principal, h);
182 h = hash32_buf(mech->elements, mech->length, h);
183 h = HASHSTEP(h, (int) service);
184
185 return (h % RPC_GSS_HASH_SIZE);
186 }
187
188 /*
189 * Simplified interface to create a security association for the
190 * current thread's * ucred.
191 */
192 AUTH *
rpc_gss_secfind(CLIENT * clnt,struct ucred * cred,const char * principal,gss_OID mech_oid,rpc_gss_service_t service)193 rpc_gss_secfind(CLIENT *clnt, struct ucred *cred, const char *principal,
194 gss_OID mech_oid, rpc_gss_service_t service)
195 {
196 uint32_t h, th;
197 AUTH *auth;
198 struct rpc_gss_data *gd, *tgd;
199 rpc_gss_options_ret_t options;
200
201 if (rpc_gss_count > RPC_GSS_MAX) {
202 while (rpc_gss_count > RPC_GSS_MAX) {
203 sx_xlock(&rpc_gss_lock);
204 tgd = TAILQ_FIRST(&rpc_gss_all);
205 th = tgd->gd_hash;
206 TAILQ_REMOVE(&rpc_gss_cache[th], tgd, gd_link);
207 TAILQ_REMOVE(&rpc_gss_all, tgd, gd_alllink);
208 rpc_gss_count--;
209 sx_xunlock(&rpc_gss_lock);
210 AUTH_DESTROY(tgd->gd_auth);
211 }
212 }
213
214 /*
215 * See if we already have an AUTH which matches.
216 */
217 h = rpc_gss_hash(principal, mech_oid, cred, service);
218
219 again:
220 sx_slock(&rpc_gss_lock);
221 TAILQ_FOREACH(gd, &rpc_gss_cache[h], gd_link) {
222 if (gd->gd_ucred->cr_uid == cred->cr_uid
223 && !strcmp(gd->gd_principal, principal)
224 && gd->gd_mech == mech_oid
225 && gd->gd_cred.gc_svc == service) {
226 refcount_acquire(&gd->gd_refs);
227 if (sx_try_upgrade(&rpc_gss_lock)) {
228 /*
229 * Keep rpc_gss_all LRU sorted.
230 */
231 TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
232 TAILQ_INSERT_TAIL(&rpc_gss_all, gd,
233 gd_alllink);
234 sx_xunlock(&rpc_gss_lock);
235 } else {
236 sx_sunlock(&rpc_gss_lock);
237 }
238
239 /*
240 * If the state != ESTABLISHED, try and initialize
241 * the authenticator again. This will happen if the
242 * user's credentials have expired. It may succeed now,
243 * if they have done a kinit or similar.
244 */
245 if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
246 memset(&options, 0, sizeof (options));
247 (void) rpc_gss_init(gd->gd_auth, &options);
248 }
249 return (gd->gd_auth);
250 }
251 }
252 sx_sunlock(&rpc_gss_lock);
253
254 /*
255 * We missed in the cache - create a new association.
256 */
257 auth = rpc_gss_seccreate_int(clnt, cred, NULL, principal, mech_oid,
258 service, GSS_C_QOP_DEFAULT, NULL, NULL);
259 if (!auth)
260 return (NULL);
261
262 gd = AUTH_PRIVATE(auth);
263 gd->gd_hash = h;
264
265 sx_xlock(&rpc_gss_lock);
266 TAILQ_FOREACH(tgd, &rpc_gss_cache[h], gd_link) {
267 if (tgd->gd_ucred->cr_uid == cred->cr_uid
268 && !strcmp(tgd->gd_principal, principal)
269 && tgd->gd_mech == mech_oid
270 && tgd->gd_cred.gc_svc == service) {
271 /*
272 * We lost a race to create the AUTH that
273 * matches this cred.
274 */
275 sx_xunlock(&rpc_gss_lock);
276 AUTH_DESTROY(auth);
277 goto again;
278 }
279 }
280
281 rpc_gss_count++;
282 TAILQ_INSERT_TAIL(&rpc_gss_cache[h], gd, gd_link);
283 TAILQ_INSERT_TAIL(&rpc_gss_all, gd, gd_alllink);
284 refcount_acquire(&gd->gd_refs); /* one for the cache, one for user */
285 sx_xunlock(&rpc_gss_lock);
286
287 return (auth);
288 }
289
290 void
rpc_gss_secpurge(CLIENT * clnt)291 rpc_gss_secpurge(CLIENT *clnt)
292 {
293 uint32_t h;
294 struct rpc_gss_data *gd, *tgd;
295
296 TAILQ_FOREACH_SAFE(gd, &rpc_gss_all, gd_alllink, tgd) {
297 if (gd->gd_clnt == clnt) {
298 sx_xlock(&rpc_gss_lock);
299 h = gd->gd_hash;
300 TAILQ_REMOVE(&rpc_gss_cache[h], gd, gd_link);
301 TAILQ_REMOVE(&rpc_gss_all, gd, gd_alllink);
302 rpc_gss_count--;
303 sx_xunlock(&rpc_gss_lock);
304 AUTH_DESTROY(gd->gd_auth);
305 }
306 }
307 }
308
309 AUTH *
rpc_gss_seccreate(CLIENT * clnt,struct ucred * cred,const char * clnt_principal,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)310 rpc_gss_seccreate(CLIENT *clnt, struct ucred *cred, const char *clnt_principal,
311 const char *principal, const char *mechanism, rpc_gss_service_t service,
312 const char *qop, rpc_gss_options_req_t *options_req,
313 rpc_gss_options_ret_t *options_ret)
314 {
315 gss_OID oid;
316 u_int qop_num;
317
318 /*
319 * Bail out now if we don't know this mechanism.
320 */
321 if (!rpc_gss_mech_to_oid(mechanism, &oid))
322 return (NULL);
323
324 if (qop) {
325 if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num))
326 return (NULL);
327 } else {
328 qop_num = GSS_C_QOP_DEFAULT;
329 }
330
331 return (rpc_gss_seccreate_int(clnt, cred, clnt_principal, principal,
332 oid, service, qop_num, options_req, options_ret));
333 }
334
335 void
rpc_gss_refresh_auth(AUTH * auth)336 rpc_gss_refresh_auth(AUTH *auth)
337 {
338 struct rpc_gss_data *gd;
339 rpc_gss_options_ret_t options;
340
341 gd = AUTH_PRIVATE(auth);
342 /*
343 * If the state != ESTABLISHED, try and initialize
344 * the authenticator again. This will happen if the
345 * user's credentials have expired. It may succeed now,
346 * if they have done a kinit or similar.
347 */
348 if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
349 memset(&options, 0, sizeof (options));
350 (void) rpc_gss_init(auth, &options);
351 }
352 }
353
354 static AUTH *
rpc_gss_seccreate_int(CLIENT * clnt,struct ucred * cred,const char * clnt_principal,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)355 rpc_gss_seccreate_int(CLIENT *clnt, struct ucred *cred,
356 const char *clnt_principal, const char *principal, gss_OID mech_oid,
357 rpc_gss_service_t service, u_int qop_num,
358 rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
359 {
360 AUTH *auth;
361 rpc_gss_options_ret_t options;
362 struct rpc_gss_data *gd;
363
364 /*
365 * If the caller doesn't want the options, point at local
366 * storage to simplify the code below.
367 */
368 if (!options_ret)
369 options_ret = &options;
370
371 /*
372 * Default service is integrity.
373 */
374 if (service == rpc_gss_svc_default)
375 service = rpc_gss_svc_integrity;
376
377 memset(options_ret, 0, sizeof(*options_ret));
378
379 rpc_gss_log_debug("in rpc_gss_seccreate()");
380
381 memset(&rpc_createerr, 0, sizeof(rpc_createerr));
382
383 auth = mem_alloc(sizeof(*auth));
384 if (auth == NULL) {
385 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
386 rpc_createerr.cf_error.re_errno = ENOMEM;
387 return (NULL);
388 }
389 gd = mem_alloc(sizeof(*gd));
390 if (gd == NULL) {
391 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
392 rpc_createerr.cf_error.re_errno = ENOMEM;
393 mem_free(auth, sizeof(*auth));
394 return (NULL);
395 }
396
397 auth->ah_ops = &rpc_gss_ops;
398 auth->ah_private = (caddr_t) gd;
399 auth->ah_cred.oa_flavor = RPCSEC_GSS;
400
401 refcount_init(&gd->gd_refs, 1);
402 mtx_init(&gd->gd_lock, "gd->gd_lock", NULL, MTX_DEF);
403 gd->gd_auth = auth;
404 gd->gd_ucred = crdup(cred);
405 gd->gd_principal = strdup(principal, M_RPC);
406 if (clnt_principal != NULL)
407 gd->gd_clntprincipal = strdup(clnt_principal, M_RPC);
408 else
409 gd->gd_clntprincipal = NULL;
410
411
412 if (options_req) {
413 gd->gd_options = *options_req;
414 } else {
415 gd->gd_options.req_flags = GSS_C_MUTUAL_FLAG;
416 gd->gd_options.time_req = 0;
417 gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
418 gd->gd_options.input_channel_bindings = NULL;
419 }
420 CLNT_ACQUIRE(clnt);
421 gd->gd_clnt = clnt;
422 gd->gd_ctx = GSS_C_NO_CONTEXT;
423 gd->gd_mech = mech_oid;
424 gd->gd_qop = qop_num;
425
426 gd->gd_cred.gc_version = RPCSEC_GSS_VERSION;
427 gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
428 gd->gd_cred.gc_seq = 0;
429 gd->gd_cred.gc_svc = service;
430 LIST_INIT(&gd->gd_reqs);
431
432 if (!rpc_gss_init(auth, options_ret)) {
433 goto bad;
434 }
435
436 return (auth);
437
438 bad:
439 AUTH_DESTROY(auth);
440 return (NULL);
441 }
442
443 bool_t
rpc_gss_set_defaults(AUTH * auth,rpc_gss_service_t service,const char * qop)444 rpc_gss_set_defaults(AUTH *auth, rpc_gss_service_t service, const char *qop)
445 {
446 struct rpc_gss_data *gd;
447 u_int qop_num;
448 const char *mechanism;
449
450 gd = AUTH_PRIVATE(auth);
451 if (!rpc_gss_oid_to_mech(gd->gd_mech, &mechanism)) {
452 return (FALSE);
453 }
454
455 if (qop) {
456 if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num)) {
457 return (FALSE);
458 }
459 } else {
460 qop_num = GSS_C_QOP_DEFAULT;
461 }
462
463 gd->gd_cred.gc_svc = service;
464 gd->gd_qop = qop_num;
465 return (TRUE);
466 }
467
468 static void
rpc_gss_purge_xid(struct rpc_gss_data * gd,uint32_t xid)469 rpc_gss_purge_xid(struct rpc_gss_data *gd, uint32_t xid)
470 {
471 struct rpc_pending_request *pr, *npr;
472 struct rpc_pending_request_list reqs;
473
474 LIST_INIT(&reqs);
475 mtx_lock(&gd->gd_lock);
476 LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
477 if (pr->pr_xid == xid) {
478 LIST_REMOVE(pr, pr_link);
479 LIST_INSERT_HEAD(&reqs, pr, pr_link);
480 }
481 }
482
483 mtx_unlock(&gd->gd_lock);
484
485 LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
486 mem_free(pr, sizeof(*pr));
487 }
488 }
489
490 static uint32_t
rpc_gss_alloc_seq(struct rpc_gss_data * gd)491 rpc_gss_alloc_seq(struct rpc_gss_data *gd)
492 {
493 uint32_t seq;
494
495 mtx_lock(&gd->gd_lock);
496 seq = gd->gd_seq;
497 gd->gd_seq++;
498 mtx_unlock(&gd->gd_lock);
499
500 return (seq);
501 }
502
503 static void
rpc_gss_nextverf(__unused AUTH * auth)504 rpc_gss_nextverf(__unused AUTH *auth)
505 {
506
507 /* not used */
508 }
509
510 static bool_t
rpc_gss_marshal(AUTH * auth,uint32_t xid,XDR * xdrs,struct mbuf * args)511 rpc_gss_marshal(AUTH *auth, uint32_t xid, XDR *xdrs, struct mbuf *args)
512 {
513 struct rpc_gss_data *gd;
514 struct rpc_pending_request *pr;
515 uint32_t seq;
516 XDR tmpxdrs;
517 struct rpc_gss_cred gsscred;
518 char credbuf[MAX_AUTH_BYTES];
519 struct opaque_auth creds, verf;
520 gss_buffer_desc rpcbuf, checksum;
521 OM_uint32 maj_stat, min_stat;
522 bool_t xdr_stat;
523
524 rpc_gss_log_debug("in rpc_gss_marshal()");
525
526 gd = AUTH_PRIVATE(auth);
527
528 gsscred = gd->gd_cred;
529 seq = rpc_gss_alloc_seq(gd);
530 gsscred.gc_seq = seq;
531
532 xdrmem_create(&tmpxdrs, credbuf, sizeof(credbuf), XDR_ENCODE);
533 if (!xdr_rpc_gss_cred(&tmpxdrs, &gsscred)) {
534 XDR_DESTROY(&tmpxdrs);
535 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
536 return (FALSE);
537 }
538 creds.oa_flavor = RPCSEC_GSS;
539 creds.oa_base = credbuf;
540 creds.oa_length = XDR_GETPOS(&tmpxdrs);
541 XDR_DESTROY(&tmpxdrs);
542
543 xdr_opaque_auth(xdrs, &creds);
544
545 if (gd->gd_cred.gc_proc == RPCSEC_GSS_INIT ||
546 gd->gd_cred.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
547 if (!xdr_opaque_auth(xdrs, &_null_auth)) {
548 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
549 return (FALSE);
550 }
551 return (xdr_putmbuf(xdrs, args));
552 } else {
553 /*
554 * Keep track of this XID + seq pair so that we can do
555 * the matching gss_verify_mic in AUTH_VALIDATE.
556 */
557 pr = mem_alloc(sizeof(struct rpc_pending_request));
558 mtx_lock(&gd->gd_lock);
559 pr->pr_xid = xid;
560 pr->pr_seq = seq;
561 LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
562 mtx_unlock(&gd->gd_lock);
563
564 /*
565 * Checksum serialized RPC header, up to and including
566 * credential. For the in-kernel environment, we
567 * assume that our XDR stream is on a contiguous
568 * memory buffer (e.g. an mbuf).
569 */
570 rpcbuf.length = XDR_GETPOS(xdrs);
571 XDR_SETPOS(xdrs, 0);
572 rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length);
573
574 maj_stat = gss_get_mic(&min_stat, gd->gd_ctx, gd->gd_qop,
575 &rpcbuf, &checksum);
576
577 if (maj_stat != GSS_S_COMPLETE) {
578 rpc_gss_log_status("gss_get_mic", gd->gd_mech,
579 maj_stat, min_stat);
580 if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
581 rpc_gss_destroy_context(auth, TRUE);
582 }
583 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
584 return (FALSE);
585 }
586
587 verf.oa_flavor = RPCSEC_GSS;
588 verf.oa_base = checksum.value;
589 verf.oa_length = checksum.length;
590
591 xdr_stat = xdr_opaque_auth(xdrs, &verf);
592 gss_release_buffer(&min_stat, &checksum);
593 if (!xdr_stat) {
594 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
595 return (FALSE);
596 }
597 if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
598 gd->gd_cred.gc_svc == rpc_gss_svc_none) {
599 return (xdr_putmbuf(xdrs, args));
600 } else {
601 if (!xdr_rpc_gss_wrap_data(&args,
602 gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
603 seq))
604 return (FALSE);
605 return (xdr_putmbuf(xdrs, args));
606 }
607 }
608
609 return (TRUE);
610 }
611
612 static bool_t
rpc_gss_validate(AUTH * auth,uint32_t xid,struct opaque_auth * verf,struct mbuf ** resultsp)613 rpc_gss_validate(AUTH *auth, uint32_t xid, struct opaque_auth *verf,
614 struct mbuf **resultsp)
615 {
616 struct rpc_gss_data *gd;
617 struct rpc_pending_request *pr, *npr;
618 struct rpc_pending_request_list reqs;
619 gss_qop_t qop_state;
620 uint32_t num, seq;
621 gss_buffer_desc signbuf, checksum;
622 OM_uint32 maj_stat, min_stat;
623
624 rpc_gss_log_debug("in rpc_gss_validate()");
625
626 gd = AUTH_PRIVATE(auth);
627
628 /*
629 * The client will call us with a NULL verf when it gives up
630 * on an XID.
631 */
632 if (!verf) {
633 rpc_gss_purge_xid(gd, xid);
634 return (TRUE);
635 }
636
637 if (gd->gd_state == RPCSEC_GSS_CONTEXT) {
638 /*
639 * Save the on the wire verifier to validate last INIT
640 * phase packet after decode if the major status is
641 * GSS_S_COMPLETE.
642 */
643 if (gd->gd_verf.value)
644 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
645 (char *) &gd->gd_verf);
646 gd->gd_verf.value = mem_alloc(verf->oa_length);
647 if (gd->gd_verf.value == NULL) {
648 printf("gss_validate: out of memory\n");
649 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
650 m_freem(*resultsp);
651 *resultsp = NULL;
652 return (FALSE);
653 }
654 memcpy(gd->gd_verf.value, verf->oa_base, verf->oa_length);
655 gd->gd_verf.length = verf->oa_length;
656
657 return (TRUE);
658 }
659
660 /*
661 * We need to check the verifier against all the requests
662 * we've send for this XID - for unreliable protocols, we
663 * retransmit with the same XID but different sequence
664 * number. We temporarily take this set of requests out of the
665 * list so that we can work through the list without having to
666 * hold the lock.
667 */
668 mtx_lock(&gd->gd_lock);
669 LIST_INIT(&reqs);
670 LIST_FOREACH_SAFE(pr, &gd->gd_reqs, pr_link, npr) {
671 if (pr->pr_xid == xid) {
672 LIST_REMOVE(pr, pr_link);
673 LIST_INSERT_HEAD(&reqs, pr, pr_link);
674 }
675 }
676 mtx_unlock(&gd->gd_lock);
677 LIST_FOREACH(pr, &reqs, pr_link) {
678 if (pr->pr_xid == xid) {
679 seq = pr->pr_seq;
680 num = htonl(seq);
681 signbuf.value = #
682 signbuf.length = sizeof(num);
683
684 checksum.value = verf->oa_base;
685 checksum.length = verf->oa_length;
686
687 maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
688 &signbuf, &checksum, &qop_state);
689 if (maj_stat != GSS_S_COMPLETE
690 || qop_state != gd->gd_qop) {
691 continue;
692 }
693 if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
694 rpc_gss_destroy_context(auth, TRUE);
695 break;
696 }
697 //rpc_gss_purge_reqs(gd, seq);
698 LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr)
699 mem_free(pr, sizeof(*pr));
700
701 if (gd->gd_cred.gc_svc == rpc_gss_svc_none) {
702 return (TRUE);
703 } else {
704 if (!xdr_rpc_gss_unwrap_data(resultsp,
705 gd->gd_ctx, gd->gd_qop,
706 gd->gd_cred.gc_svc, seq)) {
707 return (FALSE);
708 }
709 }
710 return (TRUE);
711 }
712 }
713
714 /*
715 * We didn't match - put back any entries for this XID so that
716 * a future call to validate can retry.
717 */
718 mtx_lock(&gd->gd_lock);
719 LIST_FOREACH_SAFE(pr, &reqs, pr_link, npr) {
720 LIST_REMOVE(pr, pr_link);
721 LIST_INSERT_HEAD(&gd->gd_reqs, pr, pr_link);
722 }
723 mtx_unlock(&gd->gd_lock);
724
725 /*
726 * Nothing matches - give up.
727 */
728 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
729 m_freem(*resultsp);
730 *resultsp = NULL;
731 return (FALSE);
732 }
733
734 static bool_t
rpc_gss_init(AUTH * auth,rpc_gss_options_ret_t * options_ret)735 rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
736 {
737 struct thread *td = curthread;
738 struct ucred *crsave;
739 struct rpc_gss_data *gd;
740 struct rpc_gss_init_res gr;
741 gss_buffer_desc principal_desc;
742 gss_buffer_desc *recv_tokenp, recv_token, send_token;
743 gss_name_t name;
744 OM_uint32 maj_stat, min_stat, call_stat;
745 const char *mech;
746 struct rpc_callextra ext;
747 gss_OID mech_oid;
748 gss_OID_set mechlist;
749
750 rpc_gss_log_debug("in rpc_gss_refresh()");
751
752 gd = AUTH_PRIVATE(auth);
753
754 mtx_lock(&gd->gd_lock);
755 /*
756 * If the context isn't in START state, someone else is
757 * refreshing - we wait till they are done. If they fail, they
758 * will put the state back to START and we can try (most
759 * likely to also fail).
760 */
761 while (gd->gd_state != RPCSEC_GSS_START
762 && gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
763 msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
764 }
765 if (gd->gd_state == RPCSEC_GSS_ESTABLISHED) {
766 mtx_unlock(&gd->gd_lock);
767 return (TRUE);
768 }
769 gd->gd_state = RPCSEC_GSS_CONTEXT;
770 mtx_unlock(&gd->gd_lock);
771
772 gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
773 gd->gd_cred.gc_seq = 0;
774
775 /*
776 * XXX Threads from inside jails can get here via calls
777 * to clnt_vc_call()->AUTH_REFRESH()->rpc_gss_refresh()
778 * but the NFS mount is always done outside of the
779 * jails in vnet0. Since the thread credentials won't
780 * necessarily have cr_prison == vnet0 and this function
781 * has no access to the socket, using vnet0 seems the
782 * only option. This is broken if NFS mounts are enabled
783 * within vnet prisons.
784 */
785 KGSS_CURVNET_SET_QUIET(vnet0);
786 /*
787 * For KerberosV, if there is a client principal name, that implies
788 * that this is a host based initiator credential in the default
789 * keytab file. For this case, it is necessary to do a
790 * gss_acquire_cred(). When this is done, the gssd daemon will
791 * do the equivalent of "kinit -k" to put a TGT for the name in
792 * the credential cache file for the gssd daemon.
793 */
794 if (gd->gd_clntprincipal != NULL &&
795 rpc_gss_mech_to_oid("kerberosv5", &mech_oid) &&
796 gd->gd_mech == mech_oid) {
797 /* Get rid of any old credential. */
798 if (gd->gd_options.my_cred != GSS_C_NO_CREDENTIAL) {
799 gss_release_cred(&min_stat, &gd->gd_options.my_cred);
800 gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
801 }
802
803 /*
804 * The mechanism must be set to KerberosV for acquisition
805 * of credentials to work reliably.
806 */
807 maj_stat = gss_create_empty_oid_set(&min_stat, &mechlist);
808 if (maj_stat != GSS_S_COMPLETE) {
809 options_ret->major_status = maj_stat;
810 options_ret->minor_status = min_stat;
811 goto out;
812 }
813 maj_stat = gss_add_oid_set_member(&min_stat, gd->gd_mech,
814 &mechlist);
815 if (maj_stat != GSS_S_COMPLETE) {
816 options_ret->major_status = maj_stat;
817 options_ret->minor_status = min_stat;
818 gss_release_oid_set(&min_stat, &mechlist);
819 goto out;
820 }
821
822 principal_desc.value = (void *)gd->gd_clntprincipal;
823 principal_desc.length = strlen(gd->gd_clntprincipal);
824 maj_stat = gss_import_name(&min_stat, &principal_desc,
825 GSS_C_NT_HOSTBASED_SERVICE, &name);
826 if (maj_stat != GSS_S_COMPLETE) {
827 options_ret->major_status = maj_stat;
828 options_ret->minor_status = min_stat;
829 gss_release_oid_set(&min_stat, &mechlist);
830 goto out;
831 }
832 /* Acquire the credentials. */
833 maj_stat = gss_acquire_cred(&min_stat, name, 0,
834 mechlist, GSS_C_INITIATE,
835 &gd->gd_options.my_cred, NULL, NULL);
836 gss_release_name(&min_stat, &name);
837 gss_release_oid_set(&min_stat, &mechlist);
838 if (maj_stat != GSS_S_COMPLETE) {
839 options_ret->major_status = maj_stat;
840 options_ret->minor_status = min_stat;
841 goto out;
842 }
843 }
844
845 principal_desc.value = (void *)gd->gd_principal;
846 principal_desc.length = strlen(gd->gd_principal);
847 maj_stat = gss_import_name(&min_stat, &principal_desc,
848 GSS_C_NT_HOSTBASED_SERVICE, &name);
849 if (maj_stat != GSS_S_COMPLETE) {
850 options_ret->major_status = maj_stat;
851 options_ret->minor_status = min_stat;
852 goto out;
853 }
854
855 /* GSS context establishment loop. */
856 memset(&recv_token, 0, sizeof(recv_token));
857 memset(&gr, 0, sizeof(gr));
858 memset(options_ret, 0, sizeof(*options_ret));
859 options_ret->major_status = GSS_S_FAILURE;
860 recv_tokenp = GSS_C_NO_BUFFER;
861
862 for (;;) {
863 crsave = td->td_ucred;
864 td->td_ucred = gd->gd_ucred;
865 maj_stat = gss_init_sec_context(&min_stat,
866 gd->gd_options.my_cred,
867 &gd->gd_ctx,
868 name,
869 gd->gd_mech,
870 gd->gd_options.req_flags,
871 gd->gd_options.time_req,
872 gd->gd_options.input_channel_bindings,
873 recv_tokenp,
874 &gd->gd_mech, /* used mech */
875 &send_token,
876 &options_ret->ret_flags,
877 &options_ret->time_req);
878 td->td_ucred = crsave;
879
880 /*
881 * Free the token which we got from the server (if
882 * any). Remember that this was allocated by XDR, not
883 * GSS-API.
884 */
885 if (recv_tokenp != GSS_C_NO_BUFFER) {
886 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
887 (char *) &recv_token);
888 recv_tokenp = GSS_C_NO_BUFFER;
889 }
890 if (gd->gd_mech && rpc_gss_oid_to_mech(gd->gd_mech, &mech)) {
891 strlcpy(options_ret->actual_mechanism,
892 mech,
893 sizeof(options_ret->actual_mechanism));
894 }
895 if (maj_stat != GSS_S_COMPLETE &&
896 maj_stat != GSS_S_CONTINUE_NEEDED) {
897 rpc_gss_log_status("gss_init_sec_context", gd->gd_mech,
898 maj_stat, min_stat);
899 options_ret->major_status = maj_stat;
900 options_ret->minor_status = min_stat;
901 break;
902 }
903 if (send_token.length != 0) {
904 memset(&gr, 0, sizeof(gr));
905
906 bzero(&ext, sizeof(ext));
907 ext.rc_auth = auth;
908 call_stat = CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
909 (xdrproc_t)xdr_gss_buffer_desc,
910 &send_token,
911 (xdrproc_t)xdr_rpc_gss_init_res,
912 (caddr_t)&gr, AUTH_TIMEOUT);
913
914 gss_release_buffer(&min_stat, &send_token);
915
916 if (call_stat != RPC_SUCCESS)
917 break;
918
919 if (gr.gr_major != GSS_S_COMPLETE &&
920 gr.gr_major != GSS_S_CONTINUE_NEEDED) {
921 rpc_gss_log_status("server reply", gd->gd_mech,
922 gr.gr_major, gr.gr_minor);
923 options_ret->major_status = gr.gr_major;
924 options_ret->minor_status = gr.gr_minor;
925 break;
926 }
927
928 /*
929 * Save the server's gr_handle value, freeing
930 * what we have already (remember that this
931 * was allocated by XDR, not GSS-API).
932 */
933 if (gr.gr_handle.length != 0) {
934 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
935 (char *) &gd->gd_cred.gc_handle);
936 gd->gd_cred.gc_handle = gr.gr_handle;
937 }
938
939 /*
940 * Save the server's token as well.
941 */
942 if (gr.gr_token.length != 0) {
943 recv_token = gr.gr_token;
944 recv_tokenp = &recv_token;
945 }
946
947 /*
948 * Since we have copied out all the bits of gr
949 * which XDR allocated for us, we don't need
950 * to free it.
951 */
952 gd->gd_cred.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
953 }
954
955 if (maj_stat == GSS_S_COMPLETE) {
956 gss_buffer_desc bufin;
957 u_int seq, qop_state = 0;
958
959 /*
960 * gss header verifier,
961 * usually checked in gss_validate
962 */
963 seq = htonl(gr.gr_win);
964 bufin.value = (unsigned char *)&seq;
965 bufin.length = sizeof(seq);
966
967 maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
968 &bufin, &gd->gd_verf, &qop_state);
969
970 if (maj_stat != GSS_S_COMPLETE ||
971 qop_state != gd->gd_qop) {
972 rpc_gss_log_status("gss_verify_mic", gd->gd_mech,
973 maj_stat, min_stat);
974 if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
975 rpc_gss_destroy_context(auth, TRUE);
976 }
977 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR,
978 EPERM);
979 options_ret->major_status = maj_stat;
980 options_ret->minor_status = min_stat;
981 break;
982 }
983
984 options_ret->major_status = GSS_S_COMPLETE;
985 options_ret->minor_status = 0;
986 options_ret->rpcsec_version = gd->gd_cred.gc_version;
987 options_ret->gss_context = gd->gd_ctx;
988
989 gd->gd_cred.gc_proc = RPCSEC_GSS_DATA;
990 gd->gd_seq = 1;
991 gd->gd_win = gr.gr_win;
992 break;
993 }
994 }
995
996 gss_release_name(&min_stat, &name);
997 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
998 (char *) &gd->gd_verf);
999
1000 out:
1001 /* End context negotiation loop. */
1002 if (gd->gd_cred.gc_proc != RPCSEC_GSS_DATA) {
1003 rpc_createerr.cf_stat = RPC_AUTHERROR;
1004 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
1005 if (gd->gd_ctx) {
1006 gss_delete_sec_context(&min_stat, &gd->gd_ctx,
1007 GSS_C_NO_BUFFER);
1008 }
1009 KGSS_CURVNET_RESTORE();
1010 mtx_lock(&gd->gd_lock);
1011 gd->gd_state = RPCSEC_GSS_START;
1012 wakeup(gd);
1013 mtx_unlock(&gd->gd_lock);
1014 return (FALSE);
1015 }
1016 KGSS_CURVNET_RESTORE();
1017
1018 mtx_lock(&gd->gd_lock);
1019 gd->gd_state = RPCSEC_GSS_ESTABLISHED;
1020 wakeup(gd);
1021 mtx_unlock(&gd->gd_lock);
1022
1023 return (TRUE);
1024 }
1025
1026 static bool_t
rpc_gss_refresh(AUTH * auth,void * msg)1027 rpc_gss_refresh(AUTH *auth, void *msg)
1028 {
1029 struct rpc_msg *reply = (struct rpc_msg *) msg;
1030 rpc_gss_options_ret_t options;
1031 struct rpc_gss_data *gd;
1032
1033 gd = AUTH_PRIVATE(auth);
1034
1035 /*
1036 * If the context is in DESTROYING state, then just return, since
1037 * there is no point in refreshing the credentials.
1038 */
1039 mtx_lock(&gd->gd_lock);
1040 if (gd->gd_state == RPCSEC_GSS_DESTROYING) {
1041 mtx_unlock(&gd->gd_lock);
1042 return (FALSE);
1043 }
1044 mtx_unlock(&gd->gd_lock);
1045
1046 /*
1047 * If the error was RPCSEC_GSS_CREDPROBLEM of
1048 * RPCSEC_GSS_CTXPROBLEM we start again from scratch. All
1049 * other errors are fatal.
1050 */
1051 if (reply->rm_reply.rp_stat == MSG_DENIED
1052 && reply->rm_reply.rp_rjct.rj_stat == AUTH_ERROR
1053 && (reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CREDPROBLEM
1054 || reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CTXPROBLEM)) {
1055 rpc_gss_destroy_context(auth, FALSE);
1056 memset(&options, 0, sizeof(options));
1057 return (rpc_gss_init(auth, &options));
1058 }
1059
1060 return (FALSE);
1061 }
1062
1063 static void
rpc_gss_destroy_context(AUTH * auth,bool_t send_destroy)1064 rpc_gss_destroy_context(AUTH *auth, bool_t send_destroy)
1065 {
1066 struct rpc_gss_data *gd;
1067 struct rpc_pending_request *pr;
1068 OM_uint32 min_stat;
1069 struct rpc_callextra ext;
1070
1071 rpc_gss_log_debug("in rpc_gss_destroy_context()");
1072
1073 gd = AUTH_PRIVATE(auth);
1074
1075 mtx_lock(&gd->gd_lock);
1076 /*
1077 * If the context isn't in ESTABISHED state, someone else is
1078 * destroying/refreshing - we wait till they are done.
1079 */
1080 if (gd->gd_state != RPCSEC_GSS_ESTABLISHED) {
1081 while (gd->gd_state != RPCSEC_GSS_START
1082 && gd->gd_state != RPCSEC_GSS_ESTABLISHED)
1083 msleep(gd, &gd->gd_lock, 0, "gssstate", 0);
1084 mtx_unlock(&gd->gd_lock);
1085 return;
1086 }
1087 gd->gd_state = RPCSEC_GSS_DESTROYING;
1088 mtx_unlock(&gd->gd_lock);
1089
1090 if (send_destroy) {
1091 gd->gd_cred.gc_proc = RPCSEC_GSS_DESTROY;
1092 bzero(&ext, sizeof(ext));
1093 ext.rc_auth = auth;
1094 CLNT_CALL_EXT(gd->gd_clnt, &ext, NULLPROC,
1095 (xdrproc_t)xdr_void, NULL,
1096 (xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT);
1097 }
1098
1099 while ((pr = LIST_FIRST(&gd->gd_reqs)) != NULL) {
1100 LIST_REMOVE(pr, pr_link);
1101 mem_free(pr, sizeof(*pr));
1102 }
1103
1104 /*
1105 * Free the context token. Remember that this was
1106 * allocated by XDR, not GSS-API.
1107 */
1108 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
1109 (char *) &gd->gd_cred.gc_handle);
1110 gd->gd_cred.gc_handle.length = 0;
1111
1112 if (gd->gd_ctx != GSS_C_NO_CONTEXT)
1113 gss_delete_sec_context(&min_stat, &gd->gd_ctx, NULL);
1114
1115 mtx_lock(&gd->gd_lock);
1116 gd->gd_state = RPCSEC_GSS_START;
1117 wakeup(gd);
1118 mtx_unlock(&gd->gd_lock);
1119 }
1120
1121 static void
rpc_gss_destroy(AUTH * auth)1122 rpc_gss_destroy(AUTH *auth)
1123 {
1124 struct rpc_gss_data *gd;
1125
1126 rpc_gss_log_debug("in rpc_gss_destroy()");
1127
1128 gd = AUTH_PRIVATE(auth);
1129
1130 if (!refcount_release(&gd->gd_refs))
1131 return;
1132
1133 rpc_gss_destroy_context(auth, TRUE);
1134
1135 CLNT_RELEASE(gd->gd_clnt);
1136 crfree(gd->gd_ucred);
1137 free(gd->gd_principal, M_RPC);
1138 if (gd->gd_clntprincipal != NULL)
1139 free(gd->gd_clntprincipal, M_RPC);
1140 if (gd->gd_verf.value)
1141 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
1142 (char *) &gd->gd_verf);
1143 mtx_destroy(&gd->gd_lock);
1144
1145 mem_free(gd, sizeof(*gd));
1146 mem_free(auth, sizeof(*auth));
1147 }
1148
1149 int
rpc_gss_max_data_length(AUTH * auth,int max_tp_unit_len)1150 rpc_gss_max_data_length(AUTH *auth, int max_tp_unit_len)
1151 {
1152 struct rpc_gss_data *gd;
1153 int want_conf;
1154 OM_uint32 max;
1155 OM_uint32 maj_stat, min_stat;
1156 int result;
1157
1158 gd = AUTH_PRIVATE(auth);
1159
1160 switch (gd->gd_cred.gc_svc) {
1161 case rpc_gss_svc_none:
1162 return (max_tp_unit_len);
1163 break;
1164
1165 case rpc_gss_svc_default:
1166 case rpc_gss_svc_integrity:
1167 want_conf = FALSE;
1168 break;
1169
1170 case rpc_gss_svc_privacy:
1171 want_conf = TRUE;
1172 break;
1173
1174 default:
1175 return (0);
1176 }
1177
1178 maj_stat = gss_wrap_size_limit(&min_stat, gd->gd_ctx, want_conf,
1179 gd->gd_qop, max_tp_unit_len, &max);
1180
1181 if (maj_stat == GSS_S_COMPLETE) {
1182 result = (int) max;
1183 if (result < 0)
1184 result = 0;
1185 return (result);
1186 } else {
1187 rpc_gss_log_status("gss_wrap_size_limit", gd->gd_mech,
1188 maj_stat, min_stat);
1189 return (0);
1190 }
1191 }
1192