4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
28 * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved.
31 * /afs/gza.com/product/secure/rel-eng/src/1.1/rpc/RCS/auth_gssapi.c,v
32 * 1.14 1995/03/22 22:07:55 jik Exp $
42 #include <gssapi/gssapi.h>
44 #include <rpc/rpcsec_defs.h>
46 static void rpc_gss_nextverf();
47 static bool_t
rpc_gss_marshall();
48 static bool_t
rpc_gss_validate();
49 static bool_t
rpc_gss_refresh();
50 static void rpc_gss_destroy();
51 static void rpc_gss_destroy_pvt();
52 static bool_t
rpc_gss_seccreate_pvt();
53 static bool_t
validate_seqwin();
56 * Globals that should have header files but don't.
58 extern bool_t
xdr_opaque_auth(XDR
*, struct opaque_auth
*);
61 static struct auth_ops rpc_gss_ops
= {
70 * Private data for RPCSEC_GSS.
72 typedef struct _rpc_gss_data
{
73 bool_t established
; /* TRUE when established */
74 CLIENT
*clnt
; /* associated client handle */
75 uint_t version
; /* RPCSEC version */
76 gss_ctx_id_t context
; /* GSS context id */
77 gss_buffer_desc ctx_handle
; /* RPCSEC context handle */
78 uint_t seq_num
; /* last sequence number rcvd */
79 gss_cred_id_t my_cred
; /* GSS credentials */
80 OM_uint32 qop
; /* requested QOP */
81 rpc_gss_service_t service
; /* requested service */
82 uint_t gss_proc
; /* GSS control procedure */
83 gss_name_t target_name
; /* target server */
84 int req_flags
; /* GSS request bits */
85 gss_OID mech_type
; /* GSS mechanism */
86 OM_uint32 time_req
; /* requested cred lifetime */
87 bool_t invalid
; /* can't use this any more */
88 OM_uint32 seq_window
; /* server sequence window */
89 struct opaque_auth
*verifier
; /* rpc reply verifier saved for */
90 /* validating the sequence window */
91 gss_channel_bindings_t icb
;
93 #define AUTH_PRIVATE(auth) ((rpc_gss_data *)auth->ah_private)
99 __rpc_gss_seccreate(clnt
, server_name
, mech
, service
, qop
, options_req
,
101 CLIENT
*clnt
; /* associated client handle */
102 char *server_name
; /* target server */
103 char *mech
; /* security mechanism */
104 rpc_gss_service_t service
; /* security service */
105 char *qop
; /* requested QOP */
106 rpc_gss_options_req_t
*options_req
; /* requested options */
107 rpc_gss_options_ret_t
*options_ret
; /* returned options */
110 OM_uint32 minor_stat
;
111 gss_name_t target_name
;
115 gss_buffer_desc input_name
;
117 rpc_gss_data
*ap
= NULL
;
121 * convert ascii strings to GSS values
123 if (!__rpc_gss_qop_to_num(qop
, mech
, &qop_num
)) {
127 if (!__rpc_gss_mech_to_oid(mech
, &mech_type
)) {
132 * convert name to GSS internal type
134 input_name
.value
= server_name
;
135 input_name
.length
= strlen(server_name
);
136 gssstat
= gss_import_name(&minor_stat
, &input_name
,
137 (gss_OID
)GSS_C_NT_HOSTBASED_SERVICE
,
139 if (gssstat
!= GSS_S_COMPLETE
) {
140 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
141 rpc_gss_err
.system_error
= ENOMEM
;
146 * Create AUTH handle. Save the necessary interface information
147 * so that the client can refresh the handle later if needed.
149 if ((auth
= (AUTH
*) malloc(sizeof (*auth
))) != NULL
)
150 ap
= (rpc_gss_data
*) malloc(sizeof (*ap
));
151 if (auth
== NULL
|| ap
== NULL
) {
152 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
153 rpc_gss_err
.system_error
= ENOMEM
;
156 (void) gss_release_name(&minor_stat
, &target_name
);
160 memset((char *)ap
, 0, sizeof (*ap
));
162 ap
->version
= RPCSEC_GSS_VERSION
;
163 if (options_req
!= NULL
) {
164 ap
->my_cred
= options_req
->my_cred
;
165 ap
->req_flags
= options_req
->req_flags
;
166 ap
->time_req
= options_req
->time_req
;
167 ap
->icb
= options_req
->input_channel_bindings
;
169 ap
->my_cred
= GSS_C_NO_CREDENTIAL
;
170 ap
->req_flags
= GSS_C_MUTUAL_FLAG
;
174 if ((ap
->service
= service
) == rpc_gss_svc_default
)
175 ap
->service
= rpc_gss_svc_integrity
;
177 ap
->target_name
= target_name
;
178 ap
->mech_type
= mech_type
;
181 * Now invoke the real interface that sets up the context from
182 * the information stashed away in the private data.
184 if (!rpc_gss_seccreate_pvt(&gssstat
, &minor_stat
, auth
, ap
,
185 &mech_type
, &ret_flags
, &time_rec
)) {
187 (void) gss_release_name(&minor_stat
, &ap
->target_name
);
194 * Make sure that the requested service is supported. In all
195 * cases, integrity service must be available.
197 if ((ap
->service
== rpc_gss_svc_privacy
&&
198 !(ret_flags
& GSS_C_CONF_FLAG
)) ||
199 !(ret_flags
& GSS_C_INTEG_FLAG
)) {
200 rpc_gss_destroy(auth
);
201 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
202 rpc_gss_err
.system_error
= EPROTONOSUPPORT
;
207 * return option values if requested
209 if (options_ret
!= NULL
) {
212 options_ret
->major_status
= gssstat
;
213 options_ret
->minor_status
= minor_stat
;
214 options_ret
->rpcsec_version
= ap
->version
;
215 options_ret
->ret_flags
= ret_flags
;
216 options_ret
->time_ret
= time_rec
;
217 options_ret
->gss_context
= ap
->context
;
218 if ((s
= __rpc_gss_oid_to_mech(mech_type
)) != NULL
)
219 strcpy(options_ret
->actual_mechanism
, s
);
221 options_ret
->actual_mechanism
[0] = '\0';
227 * Private interface to create a context. This is the interface
228 * that's invoked when the context has to be refreshed.
231 rpc_gss_seccreate_pvt(gssstat
, minor_stat
, auth
, ap
, actual_mech_type
,
234 OM_uint32
*minor_stat
;
237 gss_OID
*actual_mech_type
;
238 OM_uint32
*ret_flags
;
241 CLIENT
*clnt
= ap
->clnt
;
243 enum clnt_stat callstat
;
244 rpc_gss_init_arg call_arg
;
245 rpc_gss_init_res call_res
;
246 gss_buffer_desc
*input_token_p
, input_token
;
247 bool_t free_results
= FALSE
;
252 memset(&rpc_createerr
, 0, sizeof (rpc_createerr
));
255 * (re)initialize AUTH handle and private data.
257 memset((char *)auth
, 0, sizeof (*auth
));
258 auth
->ah_ops
= &rpc_gss_ops
;
259 auth
->ah_private
= (caddr_t
)ap
;
260 auth
->ah_cred
.oa_flavor
= RPCSEC_GSS
;
262 ap
->established
= FALSE
;
263 ap
->ctx_handle
.length
= 0;
264 ap
->ctx_handle
.value
= NULL
;
265 ap
->context
= GSS_C_NO_CONTEXT
;
267 ap
->gss_proc
= RPCSEC_GSS_INIT
;
270 * should not change clnt->cl_auth at this time, so save
273 save_auth
= clnt
->cl_auth
;
274 clnt
->cl_auth
= auth
;
277 * set state for starting context setup
279 input_token_p
= GSS_C_NO_BUFFER
;
282 *gssstat
= gss_init_sec_context(minor_stat
,
296 if (input_token_p
!= GSS_C_NO_BUFFER
) {
297 OM_uint32 minor_stat2
;
299 (void) gss_release_buffer(&minor_stat2
, input_token_p
);
300 input_token_p
= GSS_C_NO_BUFFER
;
303 if (*gssstat
!= GSS_S_COMPLETE
&& *gssstat
!= GSS_S_CONTINUE_NEEDED
) {
309 * if we got a token, pass it on
311 if (call_arg
.length
!= 0) {
312 struct timeval timeout
= {30, 0};
314 memset((char *)&call_res
, 0, sizeof (call_res
));
315 callstat
= clnt_call(clnt
, NULLPROC
,
316 __xdr_rpc_gss_init_arg
, (caddr_t
)&call_arg
,
317 __xdr_rpc_gss_init_res
, (caddr_t
)&call_res
,
319 (void) gss_release_buffer(minor_stat
, &call_arg
);
321 if (callstat
!= RPC_SUCCESS
) {
325 * we have results - note that these need to be freed
329 if (call_res
.gss_major
!= GSS_S_COMPLETE
&&
330 call_res
.gss_major
!= GSS_S_CONTINUE_NEEDED
)
333 ap
->gss_proc
= RPCSEC_GSS_CONTINUE_INIT
;
336 * check for ctx_handle
338 if (ap
->ctx_handle
.length
== 0) {
339 if (call_res
.ctx_handle
.length
== 0)
341 GSS_DUP_BUFFER(ap
->ctx_handle
,
342 call_res
.ctx_handle
);
343 } else if (!GSS_BUFFERS_EQUAL(ap
->ctx_handle
,
344 call_res
.ctx_handle
))
350 if (call_res
.token
.length
!= 0) {
351 if (*gssstat
== GSS_S_COMPLETE
)
353 GSS_DUP_BUFFER(input_token
, call_res
.token
);
354 input_token_p
= &input_token
;
356 } else if (*gssstat
!= GSS_S_COMPLETE
)
359 /* save the sequence window value; validate later */
360 ap
->seq_window
= call_res
.seq_window
;
361 xdr_free(__xdr_rpc_gss_init_res
, (caddr_t
)&call_res
);
362 free_results
= FALSE
;
366 * results were okay.. continue if necessary
368 if (*gssstat
== GSS_S_CONTINUE_NEEDED
)
372 * Validate the sequence window - RFC 2203 section 5.2.3.1
374 if (!validate_seqwin(ap
)) {
379 * Done! Security context creation is successful.
380 * Ready for exchanging data.
382 ap
->established
= TRUE
;
384 ap
->gss_proc
= RPCSEC_GSS_DATA
;
387 clnt
->cl_auth
= save_auth
; /* restore cl_auth */
391 if (ap
->context
!= GSS_C_NO_CONTEXT
)
392 rpc_gss_destroy_pvt(auth
);
394 xdr_free(__xdr_rpc_gss_init_res
, (caddr_t
)&call_res
);
395 clnt
->cl_auth
= save_auth
; /* restore cl_auth */
398 * if (rpc_createerr.cf_stat == 0)
399 * rpc_createerr.cf_stat = RPC_AUTHERROR;
401 if (rpc_createerr
.cf_stat
== 0) {
402 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
403 rpc_gss_err
.system_error
= RPC_AUTHERROR
;
410 * Set service defaults.
413 __rpc_gss_set_defaults(auth
, service
, qop
)
415 rpc_gss_service_t service
;
419 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
424 case rpc_gss_svc_integrity
:
425 case rpc_gss_svc_privacy
:
426 case rpc_gss_svc_none
:
428 case rpc_gss_svc_default
:
429 service
= rpc_gss_svc_integrity
;
435 if ((mech
= __rpc_gss_oid_to_mech(ap
->mech_type
)) == NULL
)
438 if (!__rpc_gss_qop_to_num(qop
, mech
, &qop_num
))
442 ap
->service
= service
;
447 * Marshall credentials.
450 marshall_creds(ap
, xdrs
)
454 rpc_gss_creds ag_creds
;
455 char cred_buf
[MAX_AUTH_BYTES
];
456 struct opaque_auth creds
;
459 ag_creds
.version
= ap
->version
;
460 ag_creds
.gss_proc
= ap
->gss_proc
;
461 ag_creds
.seq_num
= ap
->seq_num
;
462 ag_creds
.service
= ap
->service
;
465 * If context has not been set up yet, use NULL handle.
467 if (ap
->ctx_handle
.length
> 0)
468 ag_creds
.ctx_handle
= ap
->ctx_handle
;
470 ag_creds
.ctx_handle
.length
= 0;
471 ag_creds
.ctx_handle
.value
= NULL
;
474 xdrmem_create(&cred_xdrs
, (caddr_t
)cred_buf
, MAX_AUTH_BYTES
,
476 if (!__xdr_rpc_gss_creds(&cred_xdrs
, &ag_creds
)) {
477 XDR_DESTROY(&cred_xdrs
);
481 creds
.oa_flavor
= RPCSEC_GSS
;
482 creds
.oa_base
= cred_buf
;
483 creds
.oa_length
= xdr_getpos(&cred_xdrs
);
484 XDR_DESTROY(&cred_xdrs
);
486 if (!xdr_opaque_auth(xdrs
, &creds
))
493 * Marshall verifier. The verifier is the checksum of the RPC header
494 * up to and including the credential field. The XDR handle that's
495 * passed in has the header up to and including the credential field
496 * encoded. A pointer to the transmit buffer is also passed in.
499 marshall_verf(ap
, xdrs
, buf
)
501 XDR
*xdrs
; /* send XDR */
502 char *buf
; /* pointer of send buffer */
504 struct opaque_auth verf
;
505 OM_uint32 major
, minor
;
506 gss_buffer_desc in_buf
, out_buf
;
510 * If context is not established yet, use NULL verifier.
512 if (!ap
->established
) {
513 verf
.oa_flavor
= AUTH_NONE
;
516 return (xdr_opaque_auth(xdrs
, &verf
));
519 verf
.oa_flavor
= RPCSEC_GSS
;
520 in_buf
.length
= xdr_getpos(xdrs
);
522 if ((major
= gss_sign(&minor
, ap
->context
, ap
->qop
, &in_buf
,
523 &out_buf
)) != GSS_S_COMPLETE
) {
524 if (major
== GSS_S_CONTEXT_EXPIRED
) {
529 verf
.oa_base
= out_buf
.value
;
530 verf
.oa_length
= out_buf
.length
;
531 ret
= xdr_opaque_auth(xdrs
, &verf
);
532 (void) gss_release_buffer(&minor
, &out_buf
);
538 * Function: rpc_gss_nextverf. Not used.
546 * Function: rpc_gss_marshall - not used.
549 rpc_gss_marshall(auth
, xdrs
)
553 if (!xdr_opaque_auth(xdrs
, &auth
->ah_cred
) ||
554 !xdr_opaque_auth(xdrs
, &auth
->ah_verf
))
560 * Validate sequence window upon a successful RPCSEC_GSS INIT session.
561 * The sequence window sent back by the server should be verifiable by
562 * the verifier which is a checksum of the sequence window.
565 validate_seqwin(rpc_gss_data
*ap
)
568 OM_uint32 major
= 0, minor
= 0;
569 gss_buffer_desc msg_buf
, tok_buf
;
572 seq_win_net
= (uint_t
)htonl(ap
->seq_window
);
573 msg_buf
.length
= sizeof (seq_win_net
);
574 msg_buf
.value
= (char *)&seq_win_net
;
575 tok_buf
.length
= ap
->verifier
->oa_length
;
576 tok_buf
.value
= ap
->verifier
->oa_base
;
577 major
= gss_verify(&minor
, ap
->context
, &msg_buf
, &tok_buf
, &qop_state
);
578 if (major
!= GSS_S_COMPLETE
)
584 * Validate RPC response verifier from server. The response verifier
585 * is the checksum of the request sequence number.
588 rpc_gss_validate(auth
, verf
)
590 struct opaque_auth
*verf
;
593 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
595 OM_uint32 major
, minor
;
596 gss_buffer_desc msg_buf
, tok_buf
;
600 * If context is not established yet, save the verifier for
601 * validating the sequence window later at the end of context
604 if (!ap
->established
) {
605 if (ap
->verifier
== NULL
) {
606 ap
->verifier
= malloc(sizeof (struct opaque_auth
));
607 memset(ap
->verifier
, 0, sizeof (struct opaque_auth
));
608 if (verf
->oa_length
> 0)
609 ap
->verifier
->oa_base
= malloc(verf
->oa_length
);
611 if (ap
->verifier
->oa_length
> 0)
612 free(ap
->verifier
->oa_base
);
613 if (verf
->oa_length
> 0)
614 ap
->verifier
->oa_base
= malloc(verf
->oa_length
);
616 ap
->verifier
->oa_length
= verf
->oa_length
;
617 bcopy(verf
->oa_base
, ap
->verifier
->oa_base
, verf
->oa_length
);
621 seq_num_net
= (uint_t
)htonl(ap
->seq_num
);
622 msg_buf
.length
= sizeof (seq_num_net
);
623 msg_buf
.value
= (char *)&seq_num_net
;
624 tok_buf
.length
= verf
->oa_length
;
625 tok_buf
.value
= verf
->oa_base
;
626 major
= gss_verify(&minor
, ap
->context
, &msg_buf
, &tok_buf
, &qop_state
);
627 if (major
!= GSS_S_COMPLETE
)
633 * Refresh client context. This is necessary sometimes because the
634 * server will ocassionally destroy contexts based on LRU method, or
635 * because of expired credentials.
638 rpc_gss_refresh(auth
, msg
)
643 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
644 OM_uint32 gssstat
, minor_stat
;
647 * The context needs to be recreated only when the error status
648 * returned from the server is one of the following:
649 * RPCSEC_GSS_NOCRED and RPCSEC_GSS_FAILED
650 * The existing context should not be destroyed unless the above
651 * error status codes are received or if the context has not
655 if (msg
->rjcted_rply
.rj_why
== RPCSEC_GSS_NOCRED
||
656 msg
->rjcted_rply
.rj_why
== RPCSEC_GSS_FAILED
||
659 * Destroy the context if necessary. Use the same memory
660 * for the new context since we've already passed a pointer
663 if (ap
->context
!= GSS_C_NO_CONTEXT
) {
664 (void) gss_delete_sec_context(&minor_stat
, &ap
->context
,
666 ap
->context
= GSS_C_NO_CONTEXT
;
668 if (ap
->ctx_handle
.length
!= 0) {
669 (void) gss_release_buffer(&minor_stat
,
671 ap
->ctx_handle
.length
= 0;
672 ap
->ctx_handle
.value
= NULL
;
676 * If the context was not already established, don't try to
679 if (!ap
->established
) {
687 if (rpc_gss_seccreate_pvt(&gssstat
, &minor_stat
, auth
, ap
,
688 (gss_OID
*)0, (OM_uint32
*)0, (OM_uint32
*)0))
702 rpc_gss_destroy(auth
)
706 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
708 rpc_gss_destroy_pvt(auth
);
714 * Private interface to destroy a context without freeing up
715 * the memory used by it. We need to do this when a refresh
716 * fails, for example, so the user will still have a handle.
719 rpc_gss_destroy_pvt(auth
)
722 struct timeval timeout
;
723 OM_uint32 minor_stat
;
725 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
728 * If we have a server context id, inform server that we are
729 * destroying the context.
731 if (ap
->ctx_handle
.length
!= 0) {
732 ap
->gss_proc
= RPCSEC_GSS_DESTROY
;
735 (void) clnt_call(ap
->clnt
, NULLPROC
, xdr_void
, NULL
,
736 xdr_void
, NULL
, timeout
);
738 (void) gss_release_buffer(&minor_stat
, &ap
->ctx_handle
);
739 ap
->ctx_handle
.length
= 0;
740 ap
->ctx_handle
.value
= NULL
;
744 * Destroy local GSS context.
746 if (ap
->context
!= GSS_C_NO_CONTEXT
) {
747 (void) gss_delete_sec_context(&minor_stat
, &ap
->context
, NULL
);
748 ap
->context
= GSS_C_NO_CONTEXT
;
752 * Looks like we need to release default credentials if we use it.
753 * Non-default creds need to be released by user.
755 if (ap
->my_cred
== GSS_C_NO_CREDENTIAL
)
756 (void) gss_release_cred(&minor_stat
, &ap
->my_cred
);
759 * Release any internal name structures.
761 if (ap
->target_name
!= NULL
) {
762 (void) gss_release_name(&minor_stat
, &ap
->target_name
);
763 ap
->target_name
= NULL
;
767 * Free the verifier saved for sequence window checking.
769 if (ap
->verifier
!= NULL
) {
770 if (ap
->verifier
->oa_length
> 0)
771 free(ap
->verifier
->oa_base
);
778 * Wrap client side data. The encoded header is passed in through
779 * buf and buflen. The header is up to but not including the
783 __rpc_gss_wrap(auth
, buf
, buflen
, out_xdrs
, xdr_func
, xdr_ptr
)
785 char *buf
; /* encoded header */
786 uint_t buflen
; /* encoded header length */
788 bool_t (*xdr_func
)();
792 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
798 * Reject an invalid context.
804 * If context is established, bump up sequence number.
810 * Create the header in a temporary XDR context and buffer
811 * before putting it out.
813 xdrmem_create(&xdrs
, tmp_buf
, sizeof (tmp_buf
), XDR_ENCODE
);
814 if (!XDR_PUTBYTES(&xdrs
, buf
, buflen
))
820 if (!marshall_creds(ap
, &xdrs
))
826 if (!marshall_verf(ap
, &xdrs
, tmp_buf
))
830 * write out header and destroy temp structures
832 if (!XDR_PUTBYTES(out_xdrs
, tmp_buf
, XDR_GETPOS(&xdrs
)))
837 * If context is not established, or if neither integrity
838 * nor privacy is used, just XDR encode data.
840 if (!ap
->established
|| ap
->service
== rpc_gss_svc_none
)
841 return ((*xdr_func
)(out_xdrs
, xdr_ptr
));
843 return (__rpc_gss_wrap_data(ap
->service
, ap
->qop
, ap
->context
,
844 ap
->seq_num
, out_xdrs
, xdr_func
, xdr_ptr
));
848 * Unwrap received data.
851 __rpc_gss_unwrap(auth
, in_xdrs
, xdr_func
, xdr_ptr
)
854 bool_t (*xdr_func
)();
858 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
861 * If context is not established, of if neither integrity
862 * nor privacy is used, just XDR encode data.
864 if (!ap
->established
|| ap
->service
== rpc_gss_svc_none
)
865 return ((*xdr_func
)(in_xdrs
, xdr_ptr
));
867 return (__rpc_gss_unwrap_data(ap
->service
,
871 in_xdrs
, xdr_func
, xdr_ptr
));
875 __rpc_gss_max_data_length(auth
, max_tp_unit_len
)
880 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
882 if (!ap
->established
|| max_tp_unit_len
<= 0)
885 return (__find_max_data_length(ap
->service
,
892 __rpc_gss_get_error(rpc_gss_error_t
*error
)
894 *error
= rpc_gss_err
;
899 rpc_gss_error_t rpc_gss_err
;
904 static thread_key_t rpc_gss_err_key
= THR_ONCE_KEY
;
905 rpc_gss_error_t
*tsd
;
908 return (&rpc_gss_err
);
909 if (thr_keycreate_once(&rpc_gss_err_key
, free
) != 0)
910 return (&rpc_gss_err
);
911 tsd
= pthread_getspecific(rpc_gss_err_key
);
913 tsd
= (rpc_gss_error_t
*)calloc(1, sizeof (rpc_gss_error_t
));
914 if (thr_setspecific(rpc_gss_err_key
, tsd
) != 0) {
916 return (&rpc_gss_err
);