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 $
35 #pragma ident "%Z%%M% %I% %E% SMI"
44 #include <gssapi/gssapi.h>
46 #include <rpc/rpcsec_defs.h>
48 static void rpc_gss_nextverf();
49 static bool_t
rpc_gss_marshall();
50 static bool_t
rpc_gss_validate();
51 static bool_t
rpc_gss_refresh();
52 static void rpc_gss_destroy();
53 static void rpc_gss_destroy_pvt();
54 static bool_t
rpc_gss_seccreate_pvt();
55 static bool_t
validate_seqwin();
58 * Globals that should have header files but don't.
60 extern bool_t
xdr_opaque_auth(XDR
*, struct opaque_auth
*);
63 static struct auth_ops rpc_gss_ops
= {
72 * Private data for RPCSEC_GSS.
74 typedef struct _rpc_gss_data
{
75 bool_t established
; /* TRUE when established */
76 CLIENT
*clnt
; /* associated client handle */
77 uint_t version
; /* RPCSEC version */
78 gss_ctx_id_t context
; /* GSS context id */
79 gss_buffer_desc ctx_handle
; /* RPCSEC context handle */
80 uint_t seq_num
; /* last sequence number rcvd */
81 gss_cred_id_t my_cred
; /* GSS credentials */
82 OM_uint32 qop
; /* requested QOP */
83 rpc_gss_service_t service
; /* requested service */
84 uint_t gss_proc
; /* GSS control procedure */
85 gss_name_t target_name
; /* target server */
86 int req_flags
; /* GSS request bits */
87 gss_OID mech_type
; /* GSS mechanism */
88 OM_uint32 time_req
; /* requested cred lifetime */
89 bool_t invalid
; /* can't use this any more */
90 OM_uint32 seq_window
; /* server sequence window */
91 struct opaque_auth
*verifier
; /* rpc reply verifier saved for */
92 /* validating the sequence window */
93 gss_channel_bindings_t icb
;
95 #define AUTH_PRIVATE(auth) ((rpc_gss_data *)auth->ah_private)
101 __rpc_gss_seccreate(clnt
, server_name
, mech
, service
, qop
, options_req
,
103 CLIENT
*clnt
; /* associated client handle */
104 char *server_name
; /* target server */
105 char *mech
; /* security mechanism */
106 rpc_gss_service_t service
; /* security service */
107 char *qop
; /* requested QOP */
108 rpc_gss_options_req_t
*options_req
; /* requested options */
109 rpc_gss_options_ret_t
*options_ret
; /* returned options */
112 OM_uint32 minor_stat
;
113 gss_name_t target_name
;
117 gss_buffer_desc input_name
;
119 rpc_gss_data
*ap
= NULL
;
123 * convert ascii strings to GSS values
125 if (!__rpc_gss_qop_to_num(qop
, mech
, &qop_num
)) {
129 if (!__rpc_gss_mech_to_oid(mech
, &mech_type
)) {
134 * convert name to GSS internal type
136 input_name
.value
= server_name
;
137 input_name
.length
= strlen(server_name
);
138 gssstat
= gss_import_name(&minor_stat
, &input_name
,
139 (gss_OID
)GSS_C_NT_HOSTBASED_SERVICE
,
141 if (gssstat
!= GSS_S_COMPLETE
) {
142 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
143 rpc_gss_err
.system_error
= ENOMEM
;
148 * Create AUTH handle. Save the necessary interface information
149 * so that the client can refresh the handle later if needed.
151 if ((auth
= (AUTH
*) malloc(sizeof (*auth
))) != NULL
)
152 ap
= (rpc_gss_data
*) malloc(sizeof (*ap
));
153 if (auth
== NULL
|| ap
== NULL
) {
154 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
155 rpc_gss_err
.system_error
= ENOMEM
;
158 (void) gss_release_name(&minor_stat
, &target_name
);
162 memset((char *)ap
, 0, sizeof (*ap
));
164 ap
->version
= RPCSEC_GSS_VERSION
;
165 if (options_req
!= NULL
) {
166 ap
->my_cred
= options_req
->my_cred
;
167 ap
->req_flags
= options_req
->req_flags
;
168 ap
->time_req
= options_req
->time_req
;
169 ap
->icb
= options_req
->input_channel_bindings
;
171 ap
->my_cred
= GSS_C_NO_CREDENTIAL
;
172 ap
->req_flags
= GSS_C_MUTUAL_FLAG
;
176 if ((ap
->service
= service
) == rpc_gss_svc_default
)
177 ap
->service
= rpc_gss_svc_integrity
;
179 ap
->target_name
= target_name
;
180 ap
->mech_type
= mech_type
;
183 * Now invoke the real interface that sets up the context from
184 * the information stashed away in the private data.
186 if (!rpc_gss_seccreate_pvt(&gssstat
, &minor_stat
, auth
, ap
,
187 &mech_type
, &ret_flags
, &time_rec
)) {
189 (void) gss_release_name(&minor_stat
, &ap
->target_name
);
196 * Make sure that the requested service is supported. In all
197 * cases, integrity service must be available.
199 if ((ap
->service
== rpc_gss_svc_privacy
&&
200 !(ret_flags
& GSS_C_CONF_FLAG
)) ||
201 !(ret_flags
& GSS_C_INTEG_FLAG
)) {
202 rpc_gss_destroy(auth
);
203 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
204 rpc_gss_err
.system_error
= EPROTONOSUPPORT
;
209 * return option values if requested
211 if (options_ret
!= NULL
) {
214 options_ret
->major_status
= gssstat
;
215 options_ret
->minor_status
= minor_stat
;
216 options_ret
->rpcsec_version
= ap
->version
;
217 options_ret
->ret_flags
= ret_flags
;
218 options_ret
->time_ret
= time_rec
;
219 options_ret
->gss_context
= ap
->context
;
220 if ((s
= __rpc_gss_oid_to_mech(mech_type
)) != NULL
)
221 strcpy(options_ret
->actual_mechanism
, s
);
223 options_ret
->actual_mechanism
[0] = '\0';
229 * Private interface to create a context. This is the interface
230 * that's invoked when the context has to be refreshed.
233 rpc_gss_seccreate_pvt(gssstat
, minor_stat
, auth
, ap
, actual_mech_type
,
236 OM_uint32
*minor_stat
;
239 gss_OID
*actual_mech_type
;
240 OM_uint32
*ret_flags
;
243 CLIENT
*clnt
= ap
->clnt
;
245 enum clnt_stat callstat
;
246 rpc_gss_init_arg call_arg
;
247 rpc_gss_init_res call_res
;
248 gss_buffer_desc
*input_token_p
, input_token
;
249 bool_t free_results
= FALSE
;
254 memset(&rpc_createerr
, 0, sizeof (rpc_createerr
));
257 * (re)initialize AUTH handle and private data.
259 memset((char *)auth
, 0, sizeof (*auth
));
260 auth
->ah_ops
= &rpc_gss_ops
;
261 auth
->ah_private
= (caddr_t
)ap
;
262 auth
->ah_cred
.oa_flavor
= RPCSEC_GSS
;
264 ap
->established
= FALSE
;
265 ap
->ctx_handle
.length
= 0;
266 ap
->ctx_handle
.value
= NULL
;
267 ap
->context
= GSS_C_NO_CONTEXT
;
269 ap
->gss_proc
= RPCSEC_GSS_INIT
;
272 * should not change clnt->cl_auth at this time, so save
275 save_auth
= clnt
->cl_auth
;
276 clnt
->cl_auth
= auth
;
279 * set state for starting context setup
281 input_token_p
= GSS_C_NO_BUFFER
;
284 *gssstat
= gss_init_sec_context(minor_stat
,
298 if (input_token_p
!= GSS_C_NO_BUFFER
) {
299 OM_uint32 minor_stat2
;
301 (void) gss_release_buffer(&minor_stat2
, input_token_p
);
302 input_token_p
= GSS_C_NO_BUFFER
;
305 if (*gssstat
!= GSS_S_COMPLETE
&& *gssstat
!= GSS_S_CONTINUE_NEEDED
) {
311 * if we got a token, pass it on
313 if (call_arg
.length
!= 0) {
314 struct timeval timeout
= {30, 0};
316 memset((char *)&call_res
, 0, sizeof (call_res
));
317 callstat
= clnt_call(clnt
, NULLPROC
,
318 __xdr_rpc_gss_init_arg
, (caddr_t
)&call_arg
,
319 __xdr_rpc_gss_init_res
, (caddr_t
)&call_res
,
321 (void) gss_release_buffer(minor_stat
, &call_arg
);
323 if (callstat
!= RPC_SUCCESS
) {
327 * we have results - note that these need to be freed
331 if (call_res
.gss_major
!= GSS_S_COMPLETE
&&
332 call_res
.gss_major
!= GSS_S_CONTINUE_NEEDED
)
335 ap
->gss_proc
= RPCSEC_GSS_CONTINUE_INIT
;
338 * check for ctx_handle
340 if (ap
->ctx_handle
.length
== 0) {
341 if (call_res
.ctx_handle
.length
== 0)
343 GSS_DUP_BUFFER(ap
->ctx_handle
,
344 call_res
.ctx_handle
);
345 } else if (!GSS_BUFFERS_EQUAL(ap
->ctx_handle
,
346 call_res
.ctx_handle
))
352 if (call_res
.token
.length
!= 0) {
353 if (*gssstat
== GSS_S_COMPLETE
)
355 GSS_DUP_BUFFER(input_token
, call_res
.token
);
356 input_token_p
= &input_token
;
358 } else if (*gssstat
!= GSS_S_COMPLETE
)
361 /* save the sequence window value; validate later */
362 ap
->seq_window
= call_res
.seq_window
;
363 xdr_free(__xdr_rpc_gss_init_res
, (caddr_t
)&call_res
);
364 free_results
= FALSE
;
368 * results were okay.. continue if necessary
370 if (*gssstat
== GSS_S_CONTINUE_NEEDED
)
374 * Validate the sequence window - RFC 2203 section 5.2.3.1
376 if (!validate_seqwin(ap
)) {
381 * Done! Security context creation is successful.
382 * Ready for exchanging data.
384 ap
->established
= TRUE
;
386 ap
->gss_proc
= RPCSEC_GSS_DATA
;
389 clnt
->cl_auth
= save_auth
; /* restore cl_auth */
393 if (ap
->context
!= GSS_C_NO_CONTEXT
)
394 rpc_gss_destroy_pvt(auth
);
396 xdr_free(__xdr_rpc_gss_init_res
, (caddr_t
)&call_res
);
397 clnt
->cl_auth
= save_auth
; /* restore cl_auth */
400 * if (rpc_createerr.cf_stat == 0)
401 * rpc_createerr.cf_stat = RPC_AUTHERROR;
403 if (rpc_createerr
.cf_stat
== 0) {
404 rpc_gss_err
.rpc_gss_error
= RPC_GSS_ER_SYSTEMERROR
;
405 rpc_gss_err
.system_error
= RPC_AUTHERROR
;
412 * Set service defaults.
415 __rpc_gss_set_defaults(auth
, service
, qop
)
417 rpc_gss_service_t service
;
421 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
426 case rpc_gss_svc_integrity
:
427 case rpc_gss_svc_privacy
:
428 case rpc_gss_svc_none
:
430 case rpc_gss_svc_default
:
431 service
= rpc_gss_svc_integrity
;
437 if ((mech
= __rpc_gss_oid_to_mech(ap
->mech_type
)) == NULL
)
440 if (!__rpc_gss_qop_to_num(qop
, mech
, &qop_num
))
444 ap
->service
= service
;
449 * Marshall credentials.
452 marshall_creds(ap
, xdrs
)
456 rpc_gss_creds ag_creds
;
457 char cred_buf
[MAX_AUTH_BYTES
];
458 struct opaque_auth creds
;
461 ag_creds
.version
= ap
->version
;
462 ag_creds
.gss_proc
= ap
->gss_proc
;
463 ag_creds
.seq_num
= ap
->seq_num
;
464 ag_creds
.service
= ap
->service
;
467 * If context has not been set up yet, use NULL handle.
469 if (ap
->ctx_handle
.length
> 0)
470 ag_creds
.ctx_handle
= ap
->ctx_handle
;
472 ag_creds
.ctx_handle
.length
= 0;
473 ag_creds
.ctx_handle
.value
= NULL
;
476 xdrmem_create(&cred_xdrs
, (caddr_t
)cred_buf
, MAX_AUTH_BYTES
,
478 if (!__xdr_rpc_gss_creds(&cred_xdrs
, &ag_creds
)) {
479 XDR_DESTROY(&cred_xdrs
);
483 creds
.oa_flavor
= RPCSEC_GSS
;
484 creds
.oa_base
= cred_buf
;
485 creds
.oa_length
= xdr_getpos(&cred_xdrs
);
486 XDR_DESTROY(&cred_xdrs
);
488 if (!xdr_opaque_auth(xdrs
, &creds
))
495 * Marshall verifier. The verifier is the checksum of the RPC header
496 * up to and including the credential field. The XDR handle that's
497 * passed in has the header up to and including the credential field
498 * encoded. A pointer to the transmit buffer is also passed in.
501 marshall_verf(ap
, xdrs
, buf
)
503 XDR
*xdrs
; /* send XDR */
504 char *buf
; /* pointer of send buffer */
506 struct opaque_auth verf
;
507 OM_uint32 major
, minor
;
508 gss_buffer_desc in_buf
, out_buf
;
512 * If context is not established yet, use NULL verifier.
514 if (!ap
->established
) {
515 verf
.oa_flavor
= AUTH_NONE
;
518 return (xdr_opaque_auth(xdrs
, &verf
));
521 verf
.oa_flavor
= RPCSEC_GSS
;
522 in_buf
.length
= xdr_getpos(xdrs
);
524 if ((major
= gss_sign(&minor
, ap
->context
, ap
->qop
, &in_buf
,
525 &out_buf
)) != GSS_S_COMPLETE
) {
526 if (major
== GSS_S_CONTEXT_EXPIRED
) {
531 verf
.oa_base
= out_buf
.value
;
532 verf
.oa_length
= out_buf
.length
;
533 ret
= xdr_opaque_auth(xdrs
, &verf
);
534 (void) gss_release_buffer(&minor
, &out_buf
);
540 * Function: rpc_gss_nextverf. Not used.
548 * Function: rpc_gss_marshall - not used.
551 rpc_gss_marshall(auth
, xdrs
)
555 if (!xdr_opaque_auth(xdrs
, &auth
->ah_cred
) ||
556 !xdr_opaque_auth(xdrs
, &auth
->ah_verf
))
562 * Validate sequence window upon a successful RPCSEC_GSS INIT session.
563 * The sequence window sent back by the server should be verifiable by
564 * the verifier which is a checksum of the sequence window.
567 validate_seqwin(rpc_gss_data
*ap
)
570 OM_uint32 major
= 0, minor
= 0;
571 gss_buffer_desc msg_buf
, tok_buf
;
574 seq_win_net
= (uint_t
)htonl(ap
->seq_window
);
575 msg_buf
.length
= sizeof (seq_win_net
);
576 msg_buf
.value
= (char *)&seq_win_net
;
577 tok_buf
.length
= ap
->verifier
->oa_length
;
578 tok_buf
.value
= ap
->verifier
->oa_base
;
579 major
= gss_verify(&minor
, ap
->context
, &msg_buf
, &tok_buf
, &qop_state
);
580 if (major
!= GSS_S_COMPLETE
)
586 * Validate RPC response verifier from server. The response verifier
587 * is the checksum of the request sequence number.
590 rpc_gss_validate(auth
, verf
)
592 struct opaque_auth
*verf
;
595 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
597 OM_uint32 major
, minor
;
598 gss_buffer_desc msg_buf
, tok_buf
;
602 * If context is not established yet, save the verifier for
603 * validating the sequence window later at the end of context
606 if (!ap
->established
) {
607 if (ap
->verifier
== NULL
) {
608 ap
->verifier
= malloc(sizeof (struct opaque_auth
));
609 memset(ap
->verifier
, 0, sizeof (struct opaque_auth
));
610 if (verf
->oa_length
> 0)
611 ap
->verifier
->oa_base
= malloc(verf
->oa_length
);
613 if (ap
->verifier
->oa_length
> 0)
614 free(ap
->verifier
->oa_base
);
615 if (verf
->oa_length
> 0)
616 ap
->verifier
->oa_base
= malloc(verf
->oa_length
);
618 ap
->verifier
->oa_length
= verf
->oa_length
;
619 bcopy(verf
->oa_base
, ap
->verifier
->oa_base
, verf
->oa_length
);
623 seq_num_net
= (uint_t
)htonl(ap
->seq_num
);
624 msg_buf
.length
= sizeof (seq_num_net
);
625 msg_buf
.value
= (char *)&seq_num_net
;
626 tok_buf
.length
= verf
->oa_length
;
627 tok_buf
.value
= verf
->oa_base
;
628 major
= gss_verify(&minor
, ap
->context
, &msg_buf
, &tok_buf
, &qop_state
);
629 if (major
!= GSS_S_COMPLETE
)
635 * Refresh client context. This is necessary sometimes because the
636 * server will ocassionally destroy contexts based on LRU method, or
637 * because of expired credentials.
640 rpc_gss_refresh(auth
, msg
)
645 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
646 OM_uint32 gssstat
, minor_stat
;
649 * The context needs to be recreated only when the error status
650 * returned from the server is one of the following:
651 * RPCSEC_GSS_NOCRED and RPCSEC_GSS_FAILED
652 * The existing context should not be destroyed unless the above
653 * error status codes are received or if the context has not
657 if (msg
->rjcted_rply
.rj_why
== RPCSEC_GSS_NOCRED
||
658 msg
->rjcted_rply
.rj_why
== RPCSEC_GSS_FAILED
||
661 * Destroy the context if necessary. Use the same memory
662 * for the new context since we've already passed a pointer
665 if (ap
->context
!= GSS_C_NO_CONTEXT
) {
666 (void) gss_delete_sec_context(&minor_stat
, &ap
->context
,
668 ap
->context
= GSS_C_NO_CONTEXT
;
670 if (ap
->ctx_handle
.length
!= 0) {
671 (void) gss_release_buffer(&minor_stat
,
673 ap
->ctx_handle
.length
= 0;
674 ap
->ctx_handle
.value
= NULL
;
678 * If the context was not already established, don't try to
681 if (!ap
->established
) {
689 if (rpc_gss_seccreate_pvt(&gssstat
, &minor_stat
, auth
, ap
,
690 (gss_OID
*)0, (OM_uint32
*)0, (OM_uint32
*)0))
704 rpc_gss_destroy(auth
)
708 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
710 rpc_gss_destroy_pvt(auth
);
716 * Private interface to destroy a context without freeing up
717 * the memory used by it. We need to do this when a refresh
718 * fails, for example, so the user will still have a handle.
721 rpc_gss_destroy_pvt(auth
)
724 struct timeval timeout
;
725 OM_uint32 minor_stat
;
727 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
730 * If we have a server context id, inform server that we are
731 * destroying the context.
733 if (ap
->ctx_handle
.length
!= 0) {
734 ap
->gss_proc
= RPCSEC_GSS_DESTROY
;
737 (void) clnt_call(ap
->clnt
, NULLPROC
, xdr_void
, NULL
,
738 xdr_void
, NULL
, timeout
);
740 (void) gss_release_buffer(&minor_stat
, &ap
->ctx_handle
);
741 ap
->ctx_handle
.length
= 0;
742 ap
->ctx_handle
.value
= NULL
;
746 * Destroy local GSS context.
748 if (ap
->context
!= GSS_C_NO_CONTEXT
) {
749 (void) gss_delete_sec_context(&minor_stat
, &ap
->context
, NULL
);
750 ap
->context
= GSS_C_NO_CONTEXT
;
754 * Looks like we need to release default credentials if we use it.
755 * Non-default creds need to be released by user.
757 if (ap
->my_cred
== GSS_C_NO_CREDENTIAL
)
758 (void) gss_release_cred(&minor_stat
, &ap
->my_cred
);
761 * Release any internal name structures.
763 if (ap
->target_name
!= NULL
) {
764 (void) gss_release_name(&minor_stat
, &ap
->target_name
);
765 ap
->target_name
= NULL
;
769 * Free the verifier saved for sequence window checking.
771 if (ap
->verifier
!= NULL
) {
772 if (ap
->verifier
->oa_length
> 0)
773 free(ap
->verifier
->oa_base
);
780 * Wrap client side data. The encoded header is passed in through
781 * buf and buflen. The header is up to but not including the
785 __rpc_gss_wrap(auth
, buf
, buflen
, out_xdrs
, xdr_func
, xdr_ptr
)
787 char *buf
; /* encoded header */
788 uint_t buflen
; /* encoded header length */
790 bool_t (*xdr_func
)();
794 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
800 * Reject an invalid context.
806 * If context is established, bump up sequence number.
812 * Create the header in a temporary XDR context and buffer
813 * before putting it out.
815 xdrmem_create(&xdrs
, tmp_buf
, sizeof (tmp_buf
), XDR_ENCODE
);
816 if (!XDR_PUTBYTES(&xdrs
, buf
, buflen
))
822 if (!marshall_creds(ap
, &xdrs
))
828 if (!marshall_verf(ap
, &xdrs
, tmp_buf
))
832 * write out header and destroy temp structures
834 if (!XDR_PUTBYTES(out_xdrs
, tmp_buf
, XDR_GETPOS(&xdrs
)))
839 * If context is not established, or if neither integrity
840 * nor privacy is used, just XDR encode data.
842 if (!ap
->established
|| ap
->service
== rpc_gss_svc_none
)
843 return ((*xdr_func
)(out_xdrs
, xdr_ptr
));
845 return (__rpc_gss_wrap_data(ap
->service
, ap
->qop
, ap
->context
,
846 ap
->seq_num
, out_xdrs
, xdr_func
, xdr_ptr
));
850 * Unwrap received data.
853 __rpc_gss_unwrap(auth
, in_xdrs
, xdr_func
, xdr_ptr
)
856 bool_t (*xdr_func
)();
860 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
863 * If context is not established, of if neither integrity
864 * nor privacy is used, just XDR encode data.
866 if (!ap
->established
|| ap
->service
== rpc_gss_svc_none
)
867 return ((*xdr_func
)(in_xdrs
, xdr_ptr
));
869 return (__rpc_gss_unwrap_data(ap
->service
,
873 in_xdrs
, xdr_func
, xdr_ptr
));
877 __rpc_gss_max_data_length(auth
, max_tp_unit_len
)
882 rpc_gss_data
*ap
= AUTH_PRIVATE(auth
);
884 if (!ap
->established
|| max_tp_unit_len
<= 0)
887 return (__find_max_data_length(ap
->service
,
894 __rpc_gss_get_error(rpc_gss_error_t
*error
)
896 *error
= rpc_gss_err
;
901 rpc_gss_error_t rpc_gss_err
;
906 static thread_key_t rpc_gss_err_key
= THR_ONCE_KEY
;
907 rpc_gss_error_t
*tsd
;
910 return (&rpc_gss_err
);
911 if (thr_keycreate_once(&rpc_gss_err_key
, free
) != 0)
912 return (&rpc_gss_err
);
913 tsd
= pthread_getspecific(rpc_gss_err_key
);
915 tsd
= (rpc_gss_error_t
*)calloc(1, sizeof (rpc_gss_error_t
));
916 if (thr_setspecific(rpc_gss_err_key
, tsd
) != 0) {
919 return (&rpc_gss_err
);