rpcsec_gss.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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
* 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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved.
*
* $Header:
* 1.14 1995/03/22 22:07:55 jik Exp $
*/
#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();
#if 0
static void rpc_gss_destroy_pvt();
#endif
static void rpc_gss_free_pvt();
static int rpc_gss_seccreate_pvt();
static bool_t rpc_gss_wrap();
static bool_t rpc_gss_unwrap();
static bool_t validate_seqwin();
#ifdef DEBUG
#endif
static struct auth_ops rpc_gss_ops = {
};
/*
* Private data for RPCSEC_GSS.
*/
typedef struct _rpc_gss_data {
int version; /* RPCSEC version */
int req_flags; /* GSS request bits */
/* validating the sequence window */
} rpc_gss_data;
/*
* RPCSEC_GSS auth cache definitions.
*/
/* expiration */
/* The table size must be a power of two. */
#define GSSAUTH_TABLESIZE 16
/*
* gss auth cache entry.
*/
typedef struct ga_cache_entry {
void *cache_key;
struct ga_cache_entry *next;
} *ga_cache_list;
static krwlock_t ga_cache_table_lock;
static struct kmem_cache *ga_cache_handle;
static void gssauth_cache_reclaim(void *);
static void gssauth_zone_fini(zoneid_t, void *);
static zone_key_t gssauth_zone_key;
int ga_cache_hit;
int ga_cache_miss;
int ga_cache_reclaim;
void
gssauth_init(void)
{
/*
* Initialize gss auth cache table lock
*/
/*
* Allocate gss auth cache handle
*/
}
/*
* Destroy the structures previously initialized in gssauth_init()
* This routine is called by _init() if mod_install() failed.
*/
void
gssauth_fini(void)
{
(void) zone_key_delete(gssauth_zone_key);
}
/*
* This is a cleanup routine to release cached entries when a zone is being
* destroyed. The code is also used when kmem calls us to free up memory, at
* which point ``zoneid'' will be ALL_ZONES. We don't honor the cache timeout
* when the zone is going away, since the zoneid (and all associated cached
* entries) are invalid.
*/
/* ARGSUSED */
static void
{
int i;
for (i = 0; i < GSSAUTH_TABLESIZE; i++) {
for (p = ga_cache_table[i]; p; p = next) {
/*
* Free entries that have not been
* used for rpc_gss_cache_time seconds.
*/
now = gethrestime_sec();
if ((p->ref_time + rpc_gss_cache_time >
if ((p->ref_time + rpc_gss_cache_time <=
"reclaim: in_use\n");
}
prev = p;
continue;
}
} else {
prev = p;
continue;
}
}
"%p\n", (void *)p->auth);
rpc_gss_destroy(p->auth);
kmem_cache_free(ga_cache_handle, (void *)p);
ga_cache_table[i] = next;
} else {
}
}
}
}
/*
* Called by the kernel memory allocator when
* memory is low. Free unused cache entries.
* If that's not enough, the VM system will
* call again for some more.
*/
/*ARGSUSED*/
static void
gssauth_cache_reclaim(void *cdrarg)
{
}
/*
* Get the client gss security service handle.
* If it is in the cache table, get it, otherwise, create
* a new one by calling rpc_gss_seccreate().
*/
int
char *principal,
void *cache_key,
{
int status = 0;
return (EINVAL);
IS_ALIGNED(cr);
#ifdef DEBUG
}
#endif
/*
* Get a valid gss auth handle from the cache table.
* If auth in cache is invalid and not in use, destroy it.
*/
ga_cache_hit++;
((gethrestime_sec() + CONTEXT_WINDOW) >=
current->ctx_expired_time))) {
"refresh the auth\n");
} else {
}
} else {
}
break;
} else {
}
}
/*
* If no valid gss auth handle can be found in the cache, create
* a new one.
*/
if (!auth) {
if (options_ret == NULL)
options_ret = &opt_ret;
if (status == 0) {
(void *)auth);
if (new) {
} else {
}
}
/* done with opt_ret */
if (options_ret == &opt_ret) {
}
}
}
return (status);
}
/*
* rpc_gss_secfree will destroy a rpcsec_gss context only if
* the auth handle is not in the cache table.
*/
void
{
int i;
/*
* Check the cache table to find the auth.
* Marked it unused.
*/
for (i = 0; i < GSSAUTH_TABLESIZE; i++) {
return;
}
}
}
}
/*
* Create a gss security service context.
*/
int
char *principal, /* target service@server */
{
int ret_flags;
int error;
/*
* convert name to GSS internal type
*/
if (gssstat != GSS_S_COMPLETE) {
RPCGSS_LOG0(1,
"rpc_gss_seccreate: unable to import gss name\n");
return (ENOMEM);
}
/*
* Create AUTH handle. Save the necessary interface information
* so that the client can refresh the handle later if needed.
*/
return (ENOMEM);
}
if (options_req != NULL) {
} else {
}
/*
* Now invoke the real interface that sets up the context from
* the information stashed away in the private data.
*/
if (ap->target_name) {
}
" errno=%d\n", error);
return (error);
}
/*
* Make sure that the requested service is supported. In all
* cases, integrity service must be available.
*/
!(ret_flags & GSS_C_CONF_FLAG)) ||
!(ret_flags & GSS_C_INTEG_FLAG)) {
return (EPROTONOSUPPORT);
}
/*
* return option values if requested
*/
if (options_ret != NULL) {
/*
* Caller's responsibility to free this.
*/
}
return (0);
}
/*
* Private interface to create a context. This is the interface
* that's invoked when the context has to be refreshed.
*/
static int
int *ret_flags;
int isrefresh;
{
int free_results = 0;
int error = 0;
/*
* (re)initialize AUTH handle and private data.
*/
/*
* should not change clnt->cl_auth at this time, so save
* old handle
*/
/*
* set state for starting context setup
*/
NULL,
&call_arg,
if (input_token_p != GSS_C_NO_BUFFER) {
}
"rpcsec_gss_secreate_pvt:gss_init_sec_context");
goto cleanup;
}
/*
* if we got a token, pass it on
*/
int rpcsec_retry = isrefresh ?
while (rpcsec_retry > 0) {
timeout);
if (callstat == RPC_SUCCESS) {
error = 0;
if (isrefresh &&
rpcsec_retry--;
/*
* Pause a little and try again.
*/
} else {
break;
}
}
continue;
}
break;
}
if (callstat == RPC_TIMEDOUT) {
break;
}
if (callstat == RPC_XPRTFAILED) {
error = ECONNRESET;
break;
}
break;
}
if (callstat == RPC_INPROGRESS) {
continue;
}
break;
}
if (callstat != RPC_SUCCESS) {
RPCGSS_LOG(1,
"rpc_gss_seccreate_pvt: clnt_call failed %d\n",
callstat);
goto cleanup;
}
/*
* we have results - note that these need to be freed
*/
free_results = 1;
"call_res gss_major %x, gss_minor %x\n",
goto cleanup;
}
/*
* check for ctx_handle
*/
"length handle in response\n");
goto cleanup;
}
call_res.ctx_handle)) {
RPCGSS_LOG0(1,
"rpc_gss_seccreate_pvt: ctx_handle not the same\n");
goto cleanup;
}
/*
* check for token
*/
if (*gssstat == GSS_S_COMPLETE) {
"zero length token in response, but "
"gsstat == GSS_S_COMPLETE\n");
goto cleanup;
}
} else if (*gssstat != GSS_S_COMPLETE) {
"token in response, but "
"gsstat != GSS_S_COMPLETE\n");
goto cleanup;
}
/* save the sequence window value; validate later */
free_results = 0;
}
/*
* results were okay.. continue if necessary
*/
if (*gssstat == GSS_S_CONTINUE_NEEDED) {
goto next_token;
}
/*
* Context is established. Now use kgss_export_sec_context and
* kgss_import_sec_context to transfer the context from the user
* land to kernel if the mechanism specific kernel module is
* available.
*/
if (*gssstat == GSS_S_NAME_NOT_MN) {
"Kernel Module unavailable gssstat = 0x%x\n",
*gssstat);
goto done;
} else if (*gssstat != GSS_S_COMPLETE) {
"rpcsec_gss_secreate_pvt:gss_export_sec_context");
(void) kgss_delete_sec_context(minor_stat,
goto cleanup;
} else if (process_token.length == 0) {
"token in response for export_sec_context, but "
"gsstat == GSS_S_COMPLETE\n");
(void) kgss_delete_sec_context(minor_stat,
goto cleanup;
} else
if (*gssstat == GSS_S_COMPLETE) {
} else {
"rpcsec_gss_secreate_pvt:gss_import_sec_context");
(void) kgss_delete_sec_context(minor_stat,
goto cleanup;
}
done:
/*
* 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.
*/
return (0);
if (free_results)
/*
* If need to retry for AUTH_REFRESH, do not cleanup the
* auth private data.
*/
return (error);
}
}
}
/*
* Marshall credentials.
*/
static bool_t
{
char *cred_buf;
struct opaque_auth creds;
/*
* If context has not been set up yet, use NULL handle.
*/
else {
}
return (FALSE);
}
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
char *buf; /* pointer of send buffer */
{
struct opaque_auth verf;
/*
* If context is not established yet, use NULL verifier.
*/
if (!ap->established) {
}
&out_buf)) != GSS_S_COMPLETE) {
if (major == GSS_S_CONTEXT_EXPIRED) {
}
RPCGSS_LOG1(1,
"marshall_verf: kgss_sign failed GSS Major %x Minor %x\n",
return (FALSE);
}
return (ret);
}
/*
* 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
{
int qop_state = 0;
&qop_state);
if (major != GSS_S_COMPLETE) {
RPCGSS_LOG1(1,
"validate_seqwin: kgss_verify failed GSS Major %x Minor %x\n",
return (FALSE);
}
return (TRUE);
}
/*
* Validate RPC response verifier from server. The response verifier
* is the checksum of the request sequence number.
*/
static bool_t
struct opaque_auth *verf;
{
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) {
KM_SLEEP);
KM_SLEEP);
} else {
KM_SLEEP);
}
return (TRUE);
}
&qop_state);
if (major != GSS_S_COMPLETE) {
RPCGSS_LOG1(1,
"rpc_gss_validate: kgss_verify failed GSS Major %x Minor %x\n",
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
{
int error;
/*
* 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.
*/
!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 the context was not already established, don't try to
* recreate it.
*/
if (!ap->established) {
RPCGSS_LOG0(1,
"rpc_gss_refresh: context was not established\n");
goto out;
}
/*
* Recreate context.
*/
switch (error) {
case 0:
RPCGSS_LOG(1,
"rpc_gss_refresh: auth %p refreshed\n", (void *)auth);
goto out;
case ETIMEDOUT:
case ECONNRESET:
(void) kgss_delete_sec_context(&minor_stat,
}
(void) gss_release_buffer(&minor_stat,
&ap->ctx_handle);
}
/*
* Restore the original value for the caller to
* try again later.
*/
return (FALSE);
default:
"auth, error=%d\n", error);
goto out;
}
}
return (FALSE);
out:
(void) kgss_delete_sec_context(&minor_stat,
}
if (ctx_hdle_sav.length != 0) {
}
return (error == 0);
}
/*
* Destroy a context.
*/
static void
{
/*
* XXX Currently, we do not ping the server (rpc_gss_destroy_pvt)
* to destroy the context in the server cache.
* context cache on the server side.
*/
}
/*
* Private interface to free memory allocated in the rpcsec_gss private
* data structure (rpc_gss_data).
*/
static void
{
}
/*
* Destroy local GSS context.
*/
}
/*
* Looks like we need to release default credentials if we use it.
* Non-default creds need to be released by user.
*/
/*
* Release any internal name structures.
*/
}
/*
* Free mech_type oid structure.
*/
}
/*
* Free the verifier saved for sequence window checking.
*/
}
}
}
#if 0
/*
* XXX this function is not used right now.
* There is a client handle issue needs to be resolved.
*
* This is a private interface which will 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
{
/*
* If we have a server context id, inform server that we are
* destroying the context.
*/
}
}
#endif
/*
* 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.
*/
char *buf; /* encoded header */
/* has been changed to u_int in the user land */
{
char *tmp_buf;
/*
* Here is how MAX_SIGNED_LEN is estimated.
* Signing a 48 bytes buffer using des_cbc_md5 would end up with
* 88 is derived from RNDUP(33+(24-16)) * 2.
*/
#define MAX_SIGNED_LEN 88
/*
* Reject an invalid context.
*/
return (FALSE);
}
/*
* If context is established, bump up sequence number.
*/
if (ap->established)
/*
* Create the header in a temporary XDR context and buffer
* before putting it out.
*/
return (FALSE);
}
/*
* create cred field
*/
return (FALSE);
}
/*
* create verifier
*/
return (FALSE);
}
/*
* write out header and destroy temp structures
*/
return (FALSE);
}
XDR_DESTROY(&xdrs);
/*
* If context is not established, or if neither integrity
* nor privacy is used, just XDR encode data.
*/
}
}
/*
* Unwrap received data.
*/
{
/*
* If context is not established, of if neither integrity
* nor privacy is used, just XDR encode data.
*/
}
/*
* Revoke an GSSAPI based security credentials
* from the cache table.
*/
int
{
int i;
/*
* Check the cache table against the uid and the
* mechanism type.
*/
for (i = 0; i < GSSAUTH_TABLESIZE; i++) {
} else {
(void *)cur);
}
ga_cache_table[i] = next;
} else {
}
} else {
}
}
}
return (0);
}
/*
* Delete all the entries indexed by the cache_key.
*
* For example, the cache_key used for NFS is the address of the
* security entry for each mount point. When the file system is unmounted,
* all the cache entries indexed by this key should be deleted.
*/
void
rpc_gss_secpurge(void *cache_key)
{
int i;
/*
* Check the cache table against the cache_key.
*/
for (i = 0; i < GSSAUTH_TABLESIZE; i++) {
ga_cache_table[i] = next;
} else {
}
} else {
}
}
}
}
/*
* Function: rpc_gss_nextverf. Not used.
*/
static void
{
}
/*
* Function: rpc_gss_marshall - no op routine.
* rpc_gss_wrap() is doing the marshalling.
*/
/*ARGSUSED*/
static bool_t
{
return (TRUE);
}
/*
* Set service defaults.
* Not supported yet.
*/
/* ARGSUSED */
{
return (FALSE);
}
/* ARGSUSED */
int
{
return (0);
}