| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| |
| /* |
| * Copyright 2008 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved. |
| * |
| * $Header: |
| * /afs/gza.com/product/secure/rel-eng/src/1.1/rpc/RCS/auth_gssapi.c,v |
| * 1.14 1995/03/22 22:07:55 jik Exp $ |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <thread.h> |
| #include <syslog.h> |
| #include <gssapi/gssapi.h> |
| #include <rpc/rpc.h> |
| #include <rpc/rpcsec_defs.h> |
| |
| static void rpc_gss_nextverf(); |
| static bool_t rpc_gss_marshall(); |
| static bool_t rpc_gss_validate(); |
| static bool_t rpc_gss_refresh(); |
| static void rpc_gss_destroy(); |
| static void rpc_gss_destroy_pvt(); |
| static bool_t rpc_gss_seccreate_pvt(); |
| static bool_t validate_seqwin(); |
| |
| /* |
| * Globals that should have header files but don't. |
| */ |
| extern bool_t xdr_opaque_auth(XDR *, struct opaque_auth *); |
| |
| |
| static struct auth_ops rpc_gss_ops = { |
| rpc_gss_nextverf, |
| rpc_gss_marshall, |
| rpc_gss_validate, |
| rpc_gss_refresh, |
| rpc_gss_destroy |
| }; |
| |
| /* |
| * Private data for RPCSEC_GSS. |
| */ |
| typedef struct _rpc_gss_data { |
| bool_t established; /* TRUE when established */ |
| CLIENT *clnt; /* associated client handle */ |
| uint_t version; /* RPCSEC version */ |
| gss_ctx_id_t context; /* GSS context id */ |
| gss_buffer_desc ctx_handle; /* RPCSEC context handle */ |
| uint_t seq_num; /* last sequence number rcvd */ |
| gss_cred_id_t my_cred; /* GSS credentials */ |
| OM_uint32 qop; /* requested QOP */ |
| rpc_gss_service_t service; /* requested service */ |
| uint_t gss_proc; /* GSS control procedure */ |
| gss_name_t target_name; /* target server */ |
| int req_flags; /* GSS request bits */ |
| gss_OID mech_type; /* GSS mechanism */ |
| OM_uint32 time_req; /* requested cred lifetime */ |
| bool_t invalid; /* can't use this any more */ |
| OM_uint32 seq_window; /* server sequence window */ |
| struct opaque_auth *verifier; /* rpc reply verifier saved for */ |
| /* validating the sequence window */ |
| gss_channel_bindings_t icb; |
| } rpc_gss_data; |
| #define AUTH_PRIVATE(auth) ((rpc_gss_data *)auth->ah_private) |
| |
| /* |
| * Create a context. |
| */ |
| AUTH * |
| __rpc_gss_seccreate(clnt, server_name, mech, service, qop, options_req, |
| options_ret) |
| CLIENT *clnt; /* associated client handle */ |
| char *server_name; /* target server */ |
| char *mech; /* security mechanism */ |
| rpc_gss_service_t service; /* security service */ |
| char *qop; /* requested QOP */ |
| rpc_gss_options_req_t *options_req; /* requested options */ |
| rpc_gss_options_ret_t *options_ret; /* returned options */ |
| { |
| OM_uint32 gssstat; |
| OM_uint32 minor_stat; |
| gss_name_t target_name; |
| gss_OID mech_type; |
| OM_uint32 ret_flags; |
| OM_uint32 time_rec; |
| gss_buffer_desc input_name; |
| AUTH *auth = NULL; |
| rpc_gss_data *ap = NULL; |
| OM_uint32 qop_num; |
| |
| /* |
| * convert ascii strings to GSS values |
| */ |
| if (!__rpc_gss_qop_to_num(qop, mech, &qop_num)) { |
| return (NULL); |
| } |
| |
| if (!__rpc_gss_mech_to_oid(mech, &mech_type)) { |
| return (NULL); |
| } |
| |
| /* |
| * convert name to GSS internal type |
| */ |
| input_name.value = server_name; |
| input_name.length = strlen(server_name); |
| gssstat = gss_import_name(&minor_stat, &input_name, |
| (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, |
| &target_name); |
| if (gssstat != GSS_S_COMPLETE) { |
| rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; |
| rpc_gss_err.system_error = ENOMEM; |
| return (NULL); |
| } |
| |
| /* |
| * Create AUTH handle. Save the necessary interface information |
| * so that the client can refresh the handle later if needed. |
| */ |
| if ((auth = (AUTH *) malloc(sizeof (*auth))) != NULL) |
| ap = (rpc_gss_data *) malloc(sizeof (*ap)); |
| if (auth == NULL || ap == NULL) { |
| rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; |
| rpc_gss_err.system_error = ENOMEM; |
| if (auth != NULL) |
| free((char *)auth); |
| (void) gss_release_name(&minor_stat, &target_name); |
| return (NULL); |
| } |
| |
| memset((char *)ap, 0, sizeof (*ap)); |
| ap->clnt = clnt; |
| ap->version = RPCSEC_GSS_VERSION; |
| if (options_req != NULL) { |
| ap->my_cred = options_req->my_cred; |
| ap->req_flags = options_req->req_flags; |
| ap->time_req = options_req->time_req; |
| ap->icb = options_req->input_channel_bindings; |
| } else { |
| ap->my_cred = GSS_C_NO_CREDENTIAL; |
| ap->req_flags = GSS_C_MUTUAL_FLAG; |
| ap->time_req = 0; |
| ap->icb = NULL; |
| } |
| if ((ap->service = service) == rpc_gss_svc_default) |
| ap->service = rpc_gss_svc_integrity; |
| ap->qop = qop_num; |
| ap->target_name = target_name; |
| ap->mech_type = mech_type; |
| |
| /* |
| * Now invoke the real interface that sets up the context from |
| * the information stashed away in the private data. |
| */ |
| if (!rpc_gss_seccreate_pvt(&gssstat, &minor_stat, auth, ap, |
| &mech_type, &ret_flags, &time_rec)) { |
| if (ap->target_name) |
| (void) gss_release_name(&minor_stat, &ap->target_name); |
| free((char *)ap); |
| free((char *)auth); |
| return (NULL); |
| } |
| |
| /* |
| * Make sure that the requested service is supported. In all |
| * cases, integrity service must be available. |
| */ |
| if ((ap->service == rpc_gss_svc_privacy && |
| !(ret_flags & GSS_C_CONF_FLAG)) || |
| !(ret_flags & GSS_C_INTEG_FLAG)) { |
| rpc_gss_destroy(auth); |
| rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; |
| rpc_gss_err.system_error = EPROTONOSUPPORT; |
| return (NULL); |
| } |
| |
| /* |
| * return option values if requested |
| */ |
| if (options_ret != NULL) { |
| char *s; |
| |
| options_ret->major_status = gssstat; |
| options_ret->minor_status = minor_stat; |
| options_ret->rpcsec_version = ap->version; |
| options_ret->ret_flags = ret_flags; |
| options_ret->time_ret = time_rec; |
| options_ret->gss_context = ap->context; |
| if ((s = __rpc_gss_oid_to_mech(mech_type)) != NULL) |
| strcpy(options_ret->actual_mechanism, s); |
| else |
| options_ret->actual_mechanism[0] = '\0'; |
| } |
| return (auth); |
| } |
| |
| /* |
| * Private interface to create a context. This is the interface |
| * that's invoked when the context has to be refreshed. |
| */ |
| static bool_t |
| rpc_gss_seccreate_pvt(gssstat, minor_stat, auth, ap, actual_mech_type, |
| ret_flags, time_rec) |
| OM_uint32 *gssstat; |
| OM_uint32 *minor_stat; |
| AUTH *auth; |
| rpc_gss_data *ap; |
| gss_OID *actual_mech_type; |
| OM_uint32 *ret_flags; |
| OM_uint32 *time_rec; |
| { |
| CLIENT *clnt = ap->clnt; |
| AUTH *save_auth; |
| enum clnt_stat callstat; |
| rpc_gss_init_arg call_arg; |
| rpc_gss_init_res call_res; |
| gss_buffer_desc *input_token_p, input_token; |
| bool_t free_results = FALSE; |
| |
| /* |
| * initialize error |
| */ |
| memset(&rpc_createerr, 0, sizeof (rpc_createerr)); |
| |
| /* |
| * (re)initialize AUTH handle and private data. |
| */ |
| memset((char *)auth, 0, sizeof (*auth)); |
| auth->ah_ops = &rpc_gss_ops; |
| auth->ah_private = (caddr_t)ap; |
| auth->ah_cred.oa_flavor = RPCSEC_GSS; |
| |
| ap->established = FALSE; |
| ap->ctx_handle.length = 0; |
| ap->ctx_handle.value = NULL; |
| ap->context = GSS_C_NO_CONTEXT; |
| ap->seq_num = 0; |
| ap->gss_proc = RPCSEC_GSS_INIT; |
| |
| /* |
| * should not change clnt->cl_auth at this time, so save |
| * old handle |
| */ |
| save_auth = clnt->cl_auth; |
| clnt->cl_auth = auth; |
| |
| /* |
| * set state for starting context setup |
| */ |
| input_token_p = GSS_C_NO_BUFFER; |
| |
| next_token: |
| *gssstat = gss_init_sec_context(minor_stat, |
| ap->my_cred, |
| &ap->context, |
| ap->target_name, |
| ap->mech_type, |
| ap->req_flags, |
| ap->time_req, |
| NULL, |
| input_token_p, |
| actual_mech_type, |
| &call_arg, |
| ret_flags, |
| time_rec); |
| |
| if (input_token_p != GSS_C_NO_BUFFER) { |
| OM_uint32 minor_stat2; |
| |
| (void) gss_release_buffer(&minor_stat2, input_token_p); |
| input_token_p = GSS_C_NO_BUFFER; |
| } |
| |
| if (*gssstat != GSS_S_COMPLETE && *gssstat != GSS_S_CONTINUE_NEEDED) { |
| |
| goto cleanup; |
| } |
| |
| /* |
| * if we got a token, pass it on |
| */ |
| if (call_arg.length != 0) { |
| struct timeval timeout = {30, 0}; |
| |
| memset((char *)&call_res, 0, sizeof (call_res)); |
| callstat = clnt_call(clnt, NULLPROC, |
| __xdr_rpc_gss_init_arg, (caddr_t)&call_arg, |
| __xdr_rpc_gss_init_res, (caddr_t)&call_res, |
| timeout); |
| (void) gss_release_buffer(minor_stat, &call_arg); |
| |
| if (callstat != RPC_SUCCESS) { |
| goto cleanup; |
| } |
| /* |
| * we have results - note that these need to be freed |
| */ |
| free_results = TRUE; |
| |
| if (call_res.gss_major != GSS_S_COMPLETE && |
| call_res.gss_major != GSS_S_CONTINUE_NEEDED) |
| goto cleanup; |
| |
| ap->gss_proc = RPCSEC_GSS_CONTINUE_INIT; |
| |
| /* |
| * check for ctx_handle |
| */ |
| if (ap->ctx_handle.length == 0) { |
| if (call_res.ctx_handle.length == 0) |
| goto cleanup; |
| GSS_DUP_BUFFER(ap->ctx_handle, |
| call_res.ctx_handle); |
| } else if (!GSS_BUFFERS_EQUAL(ap->ctx_handle, |
| call_res.ctx_handle)) |
| goto cleanup; |
| |
| /* |
| * check for token |
| */ |
| if (call_res.token.length != 0) { |
| if (*gssstat == GSS_S_COMPLETE) |
| goto cleanup; |
| GSS_DUP_BUFFER(input_token, call_res.token); |
| input_token_p = &input_token; |
| |
| } else if (*gssstat != GSS_S_COMPLETE) |
| goto cleanup; |
| |
| /* save the sequence window value; validate later */ |
| ap->seq_window = call_res.seq_window; |
| xdr_free(__xdr_rpc_gss_init_res, (caddr_t)&call_res); |
| free_results = FALSE; |
| } |
| |
| /* |
| * results were okay.. continue if necessary |
| */ |
| if (*gssstat == GSS_S_CONTINUE_NEEDED) |
| goto next_token; |
| |
| /* |
| * Validate the sequence window - RFC 2203 section 5.2.3.1 |
| */ |
| if (!validate_seqwin(ap)) { |
| goto cleanup; |
| } |
| |
| /* |
| * Done! Security context creation is successful. |
| * Ready for exchanging data. |
| */ |
| ap->established = TRUE; |
| ap->seq_num = 1; |
| ap->gss_proc = RPCSEC_GSS_DATA; |
| ap->invalid = FALSE; |
| |
| clnt->cl_auth = save_auth; /* restore cl_auth */ |
| return (TRUE); |
| |
| cleanup: |
| if (ap->context != GSS_C_NO_CONTEXT) |
| rpc_gss_destroy_pvt(auth); |
| if (free_results) |
| xdr_free(__xdr_rpc_gss_init_res, (caddr_t)&call_res); |
| clnt->cl_auth = save_auth; /* restore cl_auth */ |
| |
| /* |
| * if (rpc_createerr.cf_stat == 0) |
| * rpc_createerr.cf_stat = RPC_AUTHERROR; |
| */ |
| if (rpc_createerr.cf_stat == 0) { |
| rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; |
| rpc_gss_err.system_error = RPC_AUTHERROR; |
| } |
| |
| return (FALSE); |
| } |
| |
| /* |
| * Set service defaults. |
| */ |
| bool_t |
| __rpc_gss_set_defaults(auth, service, qop) |
| AUTH *auth; |
| rpc_gss_service_t service; |
| char *qop; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| char *mech; |
| OM_uint32 qop_num; |
| |
| switch (service) { |
| case rpc_gss_svc_integrity: |
| case rpc_gss_svc_privacy: |
| case rpc_gss_svc_none: |
| break; |
| case rpc_gss_svc_default: |
| service = rpc_gss_svc_integrity; |
| break; |
| default: |
| return (FALSE); |
| } |
| |
| if ((mech = __rpc_gss_oid_to_mech(ap->mech_type)) == NULL) |
| return (FALSE); |
| |
| if (!__rpc_gss_qop_to_num(qop, mech, &qop_num)) |
| return (FALSE); |
| |
| ap->qop = qop_num; |
| ap->service = service; |
| return (TRUE); |
| } |
| |
| /* |
| * Marshall credentials. |
| */ |
| static bool_t |
| marshall_creds(ap, xdrs) |
| rpc_gss_data *ap; |
| XDR *xdrs; |
| { |
| rpc_gss_creds ag_creds; |
| char cred_buf[MAX_AUTH_BYTES]; |
| struct opaque_auth creds; |
| XDR cred_xdrs; |
| |
| ag_creds.version = ap->version; |
| ag_creds.gss_proc = ap->gss_proc; |
| ag_creds.seq_num = ap->seq_num; |
| ag_creds.service = ap->service; |
| |
| /* |
| * If context has not been set up yet, use NULL handle. |
| */ |
| if (ap->ctx_handle.length > 0) |
| ag_creds.ctx_handle = ap->ctx_handle; |
| else { |
| ag_creds.ctx_handle.length = 0; |
| ag_creds.ctx_handle.value = NULL; |
| } |
| |
| xdrmem_create(&cred_xdrs, (caddr_t)cred_buf, MAX_AUTH_BYTES, |
| XDR_ENCODE); |
| if (!__xdr_rpc_gss_creds(&cred_xdrs, &ag_creds)) { |
| XDR_DESTROY(&cred_xdrs); |
| return (FALSE); |
| } |
| |
| creds.oa_flavor = RPCSEC_GSS; |
| creds.oa_base = cred_buf; |
| creds.oa_length = xdr_getpos(&cred_xdrs); |
| XDR_DESTROY(&cred_xdrs); |
| |
| if (!xdr_opaque_auth(xdrs, &creds)) |
| return (FALSE); |
| |
| return (TRUE); |
| } |
| |
| /* |
| * Marshall verifier. The verifier is the checksum of the RPC header |
| * up to and including the credential field. The XDR handle that's |
| * passed in has the header up to and including the credential field |
| * encoded. A pointer to the transmit buffer is also passed in. |
| */ |
| static bool_t |
| marshall_verf(ap, xdrs, buf) |
| rpc_gss_data *ap; |
| XDR *xdrs; /* send XDR */ |
| char *buf; /* pointer of send buffer */ |
| { |
| struct opaque_auth verf; |
| OM_uint32 major, minor; |
| gss_buffer_desc in_buf, out_buf; |
| bool_t ret = FALSE; |
| |
| /* |
| * If context is not established yet, use NULL verifier. |
| */ |
| if (!ap->established) { |
| verf.oa_flavor = AUTH_NONE; |
| verf.oa_base = NULL; |
| verf.oa_length = 0; |
| return (xdr_opaque_auth(xdrs, &verf)); |
| } |
| |
| verf.oa_flavor = RPCSEC_GSS; |
| in_buf.length = xdr_getpos(xdrs); |
| in_buf.value = buf; |
| if ((major = gss_sign(&minor, ap->context, ap->qop, &in_buf, |
| &out_buf)) != GSS_S_COMPLETE) { |
| if (major == GSS_S_CONTEXT_EXPIRED) { |
| ap->invalid = TRUE; |
| } |
| return (FALSE); |
| } |
| verf.oa_base = out_buf.value; |
| verf.oa_length = out_buf.length; |
| ret = xdr_opaque_auth(xdrs, &verf); |
| (void) gss_release_buffer(&minor, &out_buf); |
| |
| return (ret); |
| } |
| |
| /* |
| * Function: rpc_gss_nextverf. Not used. |
| */ |
| static void |
| rpc_gss_nextverf() |
| { |
| } |
| |
| /* |
| * Function: rpc_gss_marshall - not used. |
| */ |
| static bool_t |
| rpc_gss_marshall(auth, xdrs) |
| AUTH *auth; |
| XDR *xdrs; |
| { |
| if (!xdr_opaque_auth(xdrs, &auth->ah_cred) || |
| !xdr_opaque_auth(xdrs, &auth->ah_verf)) |
| return (FALSE); |
| return (TRUE); |
| } |
| |
| /* |
| * Validate sequence window upon a successful RPCSEC_GSS INIT session. |
| * The sequence window sent back by the server should be verifiable by |
| * the verifier which is a checksum of the sequence window. |
| */ |
| static bool_t |
| validate_seqwin(rpc_gss_data *ap) |
| { |
| uint_t seq_win_net; |
| OM_uint32 major = 0, minor = 0; |
| gss_buffer_desc msg_buf, tok_buf; |
| int qop_state = 0; |
| |
| seq_win_net = (uint_t)htonl(ap->seq_window); |
| msg_buf.length = sizeof (seq_win_net); |
| msg_buf.value = (char *)&seq_win_net; |
| tok_buf.length = ap->verifier->oa_length; |
| tok_buf.value = ap->verifier->oa_base; |
| major = gss_verify(&minor, ap->context, &msg_buf, &tok_buf, &qop_state); |
| if (major != GSS_S_COMPLETE) |
| return (FALSE); |
| return (TRUE); |
| } |
| |
| /* |
| * Validate RPC response verifier from server. The response verifier |
| * is the checksum of the request sequence number. |
| */ |
| static bool_t |
| rpc_gss_validate(auth, verf) |
| AUTH *auth; |
| struct opaque_auth *verf; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| uint_t seq_num_net; |
| OM_uint32 major, minor; |
| gss_buffer_desc msg_buf, tok_buf; |
| int qop_state; |
| |
| /* |
| * If context is not established yet, save the verifier for |
| * validating the sequence window later at the end of context |
| * creation session. |
| */ |
| if (!ap->established) { |
| if (ap->verifier == NULL) { |
| ap->verifier = malloc(sizeof (struct opaque_auth)); |
| memset(ap->verifier, 0, sizeof (struct opaque_auth)); |
| if (verf->oa_length > 0) |
| ap->verifier->oa_base = malloc(verf->oa_length); |
| } else { |
| if (ap->verifier->oa_length > 0) |
| free(ap->verifier->oa_base); |
| if (verf->oa_length > 0) |
| ap->verifier->oa_base = malloc(verf->oa_length); |
| } |
| ap->verifier->oa_length = verf->oa_length; |
| bcopy(verf->oa_base, ap->verifier->oa_base, verf->oa_length); |
| return (TRUE); |
| } |
| |
| seq_num_net = (uint_t)htonl(ap->seq_num); |
| msg_buf.length = sizeof (seq_num_net); |
| msg_buf.value = (char *)&seq_num_net; |
| tok_buf.length = verf->oa_length; |
| tok_buf.value = verf->oa_base; |
| major = gss_verify(&minor, ap->context, &msg_buf, &tok_buf, &qop_state); |
| if (major != GSS_S_COMPLETE) |
| return (FALSE); |
| return (TRUE); |
| } |
| |
| /* |
| * Refresh client context. This is necessary sometimes because the |
| * server will ocassionally destroy contexts based on LRU method, or |
| * because of expired credentials. |
| */ |
| static bool_t |
| rpc_gss_refresh(auth, msg) |
| AUTH *auth; |
| struct rpc_msg *msg; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| OM_uint32 gssstat, minor_stat; |
| |
| /* |
| * The context needs to be recreated only when the error status |
| * returned from the server is one of the following: |
| * RPCSEC_GSS_NOCRED and RPCSEC_GSS_FAILED |
| * The existing context should not be destroyed unless the above |
| * error status codes are received or if the context has not |
| * been set up. |
| */ |
| |
| if (msg->rjcted_rply.rj_why == RPCSEC_GSS_NOCRED || |
| msg->rjcted_rply.rj_why == RPCSEC_GSS_FAILED || |
| !ap->established) { |
| /* |
| * Destroy the context if necessary. Use the same memory |
| * for the new context since we've already passed a pointer |
| * to it to the user. |
| */ |
| if (ap->context != GSS_C_NO_CONTEXT) { |
| (void) gss_delete_sec_context(&minor_stat, &ap->context, |
| NULL); |
| ap->context = GSS_C_NO_CONTEXT; |
| } |
| if (ap->ctx_handle.length != 0) { |
| (void) gss_release_buffer(&minor_stat, |
| &ap->ctx_handle); |
| ap->ctx_handle.length = 0; |
| ap->ctx_handle.value = NULL; |
| } |
| |
| /* |
| * If the context was not already established, don't try to |
| * recreate it. |
| */ |
| if (!ap->established) { |
| ap->invalid = TRUE; |
| return (FALSE); |
| } |
| |
| /* |
| * Recreate context. |
| */ |
| if (rpc_gss_seccreate_pvt(&gssstat, &minor_stat, auth, ap, |
| (gss_OID *)0, (OM_uint32 *)0, (OM_uint32 *)0)) |
| return (TRUE); |
| else { |
| ap->invalid = TRUE; |
| return (FALSE); |
| } |
| } |
| return (FALSE); |
| } |
| |
| /* |
| * Destroy a context. |
| */ |
| static void |
| rpc_gss_destroy(auth) |
| AUTH *auth; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| |
| rpc_gss_destroy_pvt(auth); |
| free((char *)ap); |
| free(auth); |
| } |
| |
| /* |
| * Private interface to destroy a context without freeing up |
| * the memory used by it. We need to do this when a refresh |
| * fails, for example, so the user will still have a handle. |
| */ |
| static void |
| rpc_gss_destroy_pvt(auth) |
| AUTH *auth; |
| { |
| struct timeval timeout; |
| OM_uint32 minor_stat; |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| |
| /* |
| * If we have a server context id, inform server that we are |
| * destroying the context. |
| */ |
| if (ap->ctx_handle.length != 0) { |
| ap->gss_proc = RPCSEC_GSS_DESTROY; |
| timeout.tv_sec = 1; |
| timeout.tv_usec = 0; |
| (void) clnt_call(ap->clnt, NULLPROC, xdr_void, NULL, |
| xdr_void, NULL, timeout); |
| |
| (void) gss_release_buffer(&minor_stat, &ap->ctx_handle); |
| ap->ctx_handle.length = 0; |
| ap->ctx_handle.value = NULL; |
| } |
| |
| /* |
| * Destroy local GSS context. |
| */ |
| if (ap->context != GSS_C_NO_CONTEXT) { |
| (void) gss_delete_sec_context(&minor_stat, &ap->context, NULL); |
| ap->context = GSS_C_NO_CONTEXT; |
| } |
| |
| /* |
| * Looks like we need to release default credentials if we use it. |
| * Non-default creds need to be released by user. |
| */ |
| if (ap->my_cred == GSS_C_NO_CREDENTIAL) |
| (void) gss_release_cred(&minor_stat, &ap->my_cred); |
| |
| /* |
| * Release any internal name structures. |
| */ |
| if (ap->target_name != NULL) { |
| (void) gss_release_name(&minor_stat, &ap->target_name); |
| ap->target_name = NULL; |
| } |
| |
| /* |
| * Free the verifier saved for sequence window checking. |
| */ |
| if (ap->verifier != NULL) { |
| if (ap->verifier->oa_length > 0) |
| free(ap->verifier->oa_base); |
| free(ap->verifier); |
| ap->verifier = NULL; |
| } |
| } |
| |
| /* |
| * Wrap client side data. The encoded header is passed in through |
| * buf and buflen. The header is up to but not including the |
| * credential field. |
| */ |
| bool_t |
| __rpc_gss_wrap(auth, buf, buflen, out_xdrs, xdr_func, xdr_ptr) |
| AUTH *auth; |
| char *buf; /* encoded header */ |
| uint_t buflen; /* encoded header length */ |
| XDR *out_xdrs; |
| bool_t (*xdr_func)(); |
| caddr_t xdr_ptr; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| XDR xdrs; |
| char tmp_buf[512]; |
| |
| |
| /* |
| * Reject an invalid context. |
| */ |
| if (ap->invalid) |
| return (FALSE); |
| |
| /* |
| * If context is established, bump up sequence number. |
| */ |
| if (ap->established) |
| ap->seq_num++; |
| |
| /* |
| * Create the header in a temporary XDR context and buffer |
| * before putting it out. |
| */ |
| xdrmem_create(&xdrs, tmp_buf, sizeof (tmp_buf), XDR_ENCODE); |
| if (!XDR_PUTBYTES(&xdrs, buf, buflen)) |
| return (FALSE); |
| |
| /* |
| * create cred field |
| */ |
| if (!marshall_creds(ap, &xdrs)) |
| return (FALSE); |
| |
| /* |
| * create verifier |
| */ |
| if (!marshall_verf(ap, &xdrs, tmp_buf)) |
| return (FALSE); |
| |
| /* |
| * write out header and destroy temp structures |
| */ |
| if (!XDR_PUTBYTES(out_xdrs, tmp_buf, XDR_GETPOS(&xdrs))) |
| return (FALSE); |
| XDR_DESTROY(&xdrs); |
| |
| /* |
| * If context is not established, or if neither integrity |
| * nor privacy is used, just XDR encode data. |
| */ |
| if (!ap->established || ap->service == rpc_gss_svc_none) |
| return ((*xdr_func)(out_xdrs, xdr_ptr)); |
| |
| return (__rpc_gss_wrap_data(ap->service, ap->qop, ap->context, |
| ap->seq_num, out_xdrs, xdr_func, xdr_ptr)); |
| } |
| |
| /* |
| * Unwrap received data. |
| */ |
| bool_t |
| __rpc_gss_unwrap(auth, in_xdrs, xdr_func, xdr_ptr) |
| AUTH *auth; |
| XDR *in_xdrs; |
| bool_t (*xdr_func)(); |
| caddr_t xdr_ptr; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| |
| /* |
| * If context is not established, of if neither integrity |
| * nor privacy is used, just XDR encode data. |
| */ |
| if (!ap->established || ap->service == rpc_gss_svc_none) |
| return ((*xdr_func)(in_xdrs, xdr_ptr)); |
| |
| return (__rpc_gss_unwrap_data(ap->service, |
| ap->context, |
| ap->seq_num, |
| ap->qop, |
| in_xdrs, xdr_func, xdr_ptr)); |
| } |
| |
| int |
| __rpc_gss_max_data_length(auth, max_tp_unit_len) |
| AUTH *auth; |
| int max_tp_unit_len; |
| { |
| /*LINTED*/ |
| rpc_gss_data *ap = AUTH_PRIVATE(auth); |
| |
| if (!ap->established || max_tp_unit_len <= 0) |
| return (0); |
| |
| return (__find_max_data_length(ap->service, |
| ap->context, |
| ap->qop, |
| max_tp_unit_len)); |
| } |
| |
| void |
| __rpc_gss_get_error(rpc_gss_error_t *error) |
| { |
| *error = rpc_gss_err; |
| } |
| |
| #undef rpc_gss_err |
| |
| rpc_gss_error_t rpc_gss_err; |
| |
| rpc_gss_error_t * |
| __rpc_gss_err() |
| { |
| static thread_key_t rpc_gss_err_key = THR_ONCE_KEY; |
| rpc_gss_error_t *tsd; |
| |
| if (thr_main()) |
| return (&rpc_gss_err); |
| if (thr_keycreate_once(&rpc_gss_err_key, free) != 0) |
| return (&rpc_gss_err); |
| tsd = pthread_getspecific(rpc_gss_err_key); |
| if (tsd == NULL) { |
| tsd = (rpc_gss_error_t *)calloc(1, sizeof (rpc_gss_error_t)); |
| if (thr_setspecific(rpc_gss_err_key, tsd) != 0) { |
| if (tsd) |
| free(tsd); |
| return (&rpc_gss_err); |
| } |
| } |
| return (tsd); |
| } |