svc_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.
*
* $Id: svc_auth_gssapi.c,v 1.19 1994/10/27 12:38:51 jik Exp $
*/
/*
* Server side handling of RPCSEC_GSS flavor.
*/
#include <gssapi/gssapi_ext.h>
#include <rpc/rpcsec_defs.h>
#ifdef DEBUG
extern void prom_printf();
#endif
#ifdef _KERNEL
#endif
/*
* Sequence window definitions.
*/
#define SEQ_ARR_SIZE 4
#define SEQ_HI_BIT 0x80000000
#define SEQ_LO_BIT 1
#define DIV_BY_32 5
#define SEQ_MASK 0x1f
#define SEQ_MAX ((unsigned int)0x80000000)
/* cache retransmit data */
typedef struct _retrans_entry {
/*
* Server side RPCSEC_GSS context information.
*/
typedef struct _svc_rpc_gss_data {
void *cookie;
int ref_cnt;
/*
* Data structures used for LRU based context management.
*/
/* Size of hash table for svc_rpc_gss_data structures */
#define GSS_DATA_HASH_SIZE 1024
/*
* The following two defines specify a time delta that is used in
* sweep_clients. When the last_ref_time of a context is older than
* than the current time minus the delta, i.e, the context has not
* been referenced in the last delta seconds, we will return the
* context back to the cache if the ref_cnt is zero. The first delta
* value will be used when sweep_clients is called from
* svc_data_reclaim, the kmem_cache reclaim call back. We will reclaim
* all entries except those that are currently "active". By active we
* mean those that have been referenced in the last ACTIVE_DELTA
* seconds. If sweep_client is not being called from reclaim, then we
* will reclaim all entries that are "inactive". By inactive we mean
* those entries that have not been accessed in INACTIVE_DELTA
* seconds. Note we always assume that ACTIVE_DELTA is less than
* INACTIVE_DELTA, so that reaping entries from a reclaim operation
* will necessarily imply reaping all "inactive" entries and then
* some.
*/
/*
* If low on memory reap cache entries that have not been active for
* ACTIVE_DELTA seconds and have a ref_cnt equal to zero.
*/
/*
* If in sweeping contexts we find contexts with a ref_cnt equal to zero
* and the context has not been referenced in INACTIVE_DELTA seconds, return
* the entry to the cache.
*/
static svc_rpc_gss_data **clients;
static time_t last_swept = 0;
static int num_gss_contexts = 0;
static kmem_cache_t *svc_data_handle;
/*
*/
/*
* Data structure to contain cache statistics
*/
static struct {
/*
* lock used with server credential variables list
*
* server cred list locking guidelines:
* - Writer's lock holder has exclusive access to the list
*/
/*
* server callback list
*/
typedef struct rpc_gss_cblist_s {
struct rpc_gss_cblist_s *next;
/*
* lock used with callback variables
*/
/*
* forward declarations
*/
static bool_t svc_rpc_gss_wrap();
static bool_t svc_rpc_gss_unwrap();
static svc_rpc_gss_data *create_client();
static svc_rpc_gss_data *get_client();
static svc_rpc_gss_data *find_client();
static void destroy_client();
static void sweep_clients(bool_t);
static void insert_client();
int *, uid_t);
static bool_t set_response_verf();
rpc_gss_init_res *);
static void retrans_del(svc_rpc_gss_data *);
static void common_client_data_free(svc_rpc_gss_data *);
/*
*/
struct svc_auth_ops svc_rpc_gss_ops = {
};
/*ARGSUSED*/
static int
{
return (0);
}
/*ARGSUSED*/
static void
{
}
/*ARGSUSED*/
static void
svc_gss_data_reclaim(void *pdata)
{
}
/*
* Init stuff on the server side.
*/
void
{
clients = (svc_rpc_gss_data **)
KM_SLEEP);
sizeof (svc_rpc_gss_data), 0,
}
/*
* Destroy structures allocated in svc_gss_init().
* This routine is called by _init() if mod_install() failed.
*/
void
{
}
/*
* Cleanup routine for destroying context, called after service
* procedure is executed. Actually we just decrement the reference count
* associated with this context. If the reference count is zero and the
* context is marked as stale, we would then destroy the context. Additionally,
* we check if its been longer than sweep_interval since the last sweep_clients
* was run, and if so run sweep_clients to free all stale contexts with zero
* reference counts or contexts that are old. (Haven't been access in
* svc_rpc_inactive_delta seconds).
*/
void
{
/*
* First check if current context needs to be cleaned up.
* There might be other threads stale this client data
* in between.
*/
} else
}
/*
* Check for other expired contexts.
*/
}
/*
* Shift the array arr of length arrlen right by nbits bits.
*/
static void
int arrlen;
int nbits;
{
int i, j;
/*
* If the number of bits to be shifted exceeds SEQ_WIN, just
* zero out the array.
*/
for (i = 0; i < nbits; i++) {
hi = 0;
for (j = 0; j < arrlen; j++) {
arr[j] >>= 1;
if (hi)
arr[j] |= SEQ_HI_BIT;
}
}
} else {
for (j = 0; j < arrlen; j++)
arr[j] = 0;
}
}
/*
* Check that the received sequence number seq_num is valid.
*/
static bool_t
{
int i, j;
/*
* If it exceeds the maximum, kill context.
*/
*kill_context = TRUE;
return (FALSE);
}
/*
* If greater than the last seen sequence number, just shift
* the sequence window so that it starts at the new sequence
* number and extends downwards by SEQ_WIN.
*/
return (TRUE);
}
/*
* If it is outside the sequence window, return failure.
*/
if (i >= SEQ_WIN) {
return (FALSE);
}
/*
* If within sequence window, set the bit corresponding to it
* if not already seen; if already seen, return failure.
*/
i >>= DIV_BY_32;
return (FALSE);
}
return (TRUE);
}
/*
* Set server callback.
*/
{
return (FALSE);
}
/* check if there is already an entry in the rpc_gss_cblist. */
if (rpc_gss_cblist) {
return (TRUE);
}
}
}
/* Not in rpc_gss_cblist. Create a new entry. */
== NULL) {
return (FALSE);
}
return (TRUE);
}
/*
* Locate callback (if specified) and call server. Release any
* delegated credentials unless passed to server and the server
* accepts the context. If a callback is not specified, accept
* the incoming context.
*/
static bool_t
{
continue;
if (ret) {
}
break;
}
if (!found) {
}
}
return (ret);
}
/*
* Get caller credentials.
*/
void **cookie;
{
}
if (client_data->u_cred_set == 0 ||
if (client_data->u_cred_set == 0) {
if ((gssstat = kgsscred_expname_to_unix_cred(
"kgsscred_expname_to_unix_cred failed %x\n",
gssstat);
} else {
}
if ((gssstat = kgss_get_group_info(
"kgss_get_group_info failed %x\n",
gssstat);
} else {
}
}
}
}
return (TRUE);
}
/*
* Transfer the context data from the user land to the kernel.
*/
/*
* Call kgss_export_sec_context
* if an error is returned log a message
* go to error handling
* Otherwise call kgss_import_sec_context to
* convert the token into a context
*/
/*
* if export_sec_context returns an error we delete the
* context just to be safe.
*/
if (gssstat == GSS_S_NAME_NOT_MN) {
"Kernel mod unavailable\n");
} else if (gssstat != GSS_S_COMPLETE) {
" gssstat = 0x%x\n", gssstat);
NULL);
return (FALSE);
} else if (process_token.length == 0) {
"for export_sec_context, but "
"gsstat == GSS_S_COMPLETE\n");
NULL);
return (FALSE);
} else {
if (gssstat != GSS_S_COMPLETE) {
" failed gssstat = 0x%x\n", gssstat);
(void) kgss_delete_sec_context(&minor,
return (FALSE);
}
}
return (TRUE);
}
/*
* Server side authentication for RPCSEC_GSS.
*/
enum auth_stat
{
struct opaque_auth *cred;
int free_mech_type = 1;
*no_dispatch = FALSE;
/*
* Initialize response verifier to NULL verifier. If
* necessary, this will be changed later.
*/
/*
* Need to null out results to start with.
*/
/*
* Pull out and check credential and verifier.
*/
/*
* Initialize output_token.
*/
output_token.length = 0;
ret = AUTH_BADCRED;
goto error;
}
XDR_DESTROY(&xdrs);
ret = AUTH_BADCRED;
goto error;
}
XDR_DESTROY(&xdrs);
/*
* If this is a control message and proc is GSSAPI_INIT, then
* create a client handle for this client. Otherwise, look up
* the existing handle.
*/
ret = AUTH_BADCRED;
goto error;
}
RPCGSS_LOG0(1,
"_svcrpcsec_gss: can't create a new cache entry\n");
ret = AUTH_FAILED;
goto error;
}
} else {
/*
* Only verify values for service parameter when proc
* not RPCSEC_GSS_INIT or RPCSEC_GSS_CONTINUE_INIT.
* RFC2203 says contents for sequence and service args
* are undefined for creation procs.
*
* Note: only need to check for *CONTINUE_INIT here because
* if() clause already checked for RPCSEC_GSS_INIT
*/
case rpc_gss_svc_none:
case rpc_gss_svc_integrity:
case rpc_gss_svc_privacy:
break;
default:
ret = AUTH_BADCRED;
goto error;
}
}
ret = AUTH_BADCRED;
goto error;
}
goto error;
}
}
/*
* lock the client data until it's safe; if it's already stale,
* no more processing is possible
*/
if (client_data->stale) {
goto error2;
}
/*
* Any response we send will use ctx_handle, so set it now;
* also set seq_window since this won't change.
*/
/*
*/
/*
* Keep copy of parameters we'll need for response, for the
* sake of reentrancy (we don't want to look in the context
* data because when we are sending a response, another
* request may have come in).
*/
if (!client_data->established) {
"message but context not established\n");
goto error2;
}
/*
* If the context is not established, then only
* RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT
* requests are valid.
*/
"CONTINUE_INIT message (0x%x) and context not "
goto error2;
}
/*
* call is for us, deserialize arguments
*/
goto error2;
}
minor = 0;
minor_stat = 0;
(void) gss_release_buffer(&minor,
}
&call_arg,
&time_rec,
/*
* Don't need a delegated cred back.
* No memory will be allocated if
* passing NULL.
*/
NULL,
if (gssstat == GSS_S_COMPLETE) {
/*
* Server_creds was right - set it. Also
* set the raw and unix credentials at this
* point. This saves a lot of computation
* later when credentials are retrieved.
*/
}
/*
* client_data is now responsible for freeing
* the data of 'mech_type'.
*/
free_mech_type = 0;
client_principal->len + sizeof (int));
NULL;
}
/*
* The client_name returned from
* kgss_accept_sec_context() is in an
* exported flat format.
*/
if (! __rpc_gss_make_principal(
&client_data->client_name)) {
"make principal failed\n");
(void) gss_release_buffer(&minor_stat,
&output_token);
}
}
if (gssstat != GSS_S_COMPLETE &&
gssstat != GSS_S_CONTINUE_NEEDED) {
/*
* We have a failure - send response and delete
* the context. Don't dispatch. Set ctx_handle
* to NULL and seq_window to 0.
*/
call_res.seq_window = 0;
"_svc_rpcsec_gss gss_accept_sec_context");
*no_dispatch = TRUE;
goto error2;
}
/*
* If appropriate, set established to TRUE *after* sending
* response (otherwise, the client will receive the final
* token encrypted)
*/
if (gssstat == GSS_S_COMPLETE) {
/*
* Context is established. Set expiration time
* for the context.
*/
} else {
time_rec + gethrestime_sec();
}
if (!transfer_sec_context(client_data)) {
RPCGSS_LOG0(1,
"_svc_rpcsec_gss: transfer sec context failed\n");
goto error2;
}
}
/*
* This step succeeded. Send a response, along with
* a token if there's one. Don't dispatch.
*/
if (output_token.length != 0) {
}
/*
* If GSS_S_COMPLETE: set response verifier to
* checksum of SEQ_WIN
*/
if (gssstat == GSS_S_COMPLETE) {
RPCGSS_LOG0(1,
"_svc_rpcsec_gss:set response verifier failed\n");
goto error2;
}
}
/*
* Cache last response in case it is lost and the client
* retries on an established context.
*/
*no_dispatch = TRUE;
client_data->ref_cnt--;
} else {
case RPCSEC_GSS_CONTINUE_INIT:
/*
* This is an established context. Continue to
* satisfy retried continue init requests out of
* the retransmit cache. Throw away any that don't
* have a matching xid or the cach is empty.
* Delete the retransmit cache once the client sends
* a data request.
*/
if (client_data->retrans_data &&
*no_dispatch = TRUE;
client_data->ref_cnt--;
goto success;
}
}
/* fall thru to default */
default:
"on an established context\n");
ret = AUTH_FAILED;
goto error2;
}
}
/*
* Once the context is established and there is no more
* retransmission of last continue init request, it is safe
* to delete the retransmit cache entry.
*/
if (client_data->retrans_data)
/*
* Context is already established. Check verifier, and
* note parameters we will need for response in gss_parms.
*/
goto error2;
}
/*
* Check and invoke callback if necessary.
*/
if (!client_data->done_docallback) {
ret = AUTH_FAILED;
RPCGSS_LOG0(1,
"_svc_rpcsec_gss:callback failed\n");
goto error2;
}
}
/*
* If the context was locked, make sure that the client
* has not changed QOP.
*/
if (client_data->locked &&
ret = AUTH_BADVERF;
goto error2;
}
/*
* Validate sequence number.
*/
&client_data->stale)) {
if (client_data->stale) {
RPCGSS_LOG0(1,
"_svc_rpcsec_gss:check seq failed\n");
} else {
"failed on good context. Ignoring "
"request\n");
/*
* Operational error, drop packet silently.
* The client will recover after timing out,
* assuming this is a client error and not
* a relpay attack. Don't dispatch.
*/
*no_dispatch = TRUE;
}
goto error2;
}
/*
* set response verifier
*/
RPCGSS_LOG0(1,
"_svc_rpcsec_gss:set response verifier failed\n");
goto error2;
}
/*
* If this is a control message RPCSEC_GSS_DESTROY, process
* the call; otherwise, return AUTH_OK so it will be
* dispatched to the application server.
*/
/*
* XXX Kernel client is not issuing this procudure
* right now. Need to revisit.
*/
*no_dispatch = TRUE;
client_data->ref_cnt--;
} else {
/* This should be an RPCSEC_GSS_DATA request. */
/*
* If context is locked, make sure that the client
* has not changed the security service.
*/
if (client_data->locked &&
"security service changed.\n");
ret = AUTH_FAILED;
goto error2;
}
/*
* Set client credentials to raw credential
* structure in context. This is okay, since
* this will not change during the lifetime of
* the context (so it's MT safe).
*/
}
}
/*
* Success.
*/
return (AUTH_OK);
client_data->ref_cnt--;
if (free_mech_type && mech_type)
/*
* Failure.
*/
return (ret);
}
/*
* Check verifier. The verifier is the checksum of the RPC header
* upto and including the credentials field.
*/
/* ARGSUSED */
static bool_t
{
char hdr[128];
struct opaque_auth *oa;
int len;
/*
* We have to reconstruct the RPC header from the previously
* parsed information, since we haven't kept the header intact.
*/
*(buf - 1) = 0;
}
if (gssstat != GSS_S_COMPLETE) {
return (FALSE);
}
return (TRUE);
}
/*
* Set response verifier. This is the checksum of the given number.
* (e.g. sequence number or sequence window)
*/
static bool_t
{
/* XXX uid ? */
&out_buf)) != GSS_S_COMPLETE)
return (FALSE);
return (TRUE);
}
/*
* Create client context.
*/
static svc_rpc_gss_data *
{
KM_SLEEP);
if (client_data == NULL)
return (NULL);
/*
* set up client data structure
*/
client_data->seq_num = 0;
client_data->key = 0;
client_data->u_cred_set = 0;
/*
* The client context handle is a 32-bit key (unsigned int).
* The key is incremented until there is no duplicate for it.
*/
for (;;) {
return (client_data);
}
}
/*NOTREACHED*/
}
/*
* Insert client context into hash list and LRU list.
*/
static void
{
else
}
/*
* Fetch a client, given the client context handle. Move it to the
* top of the LRU list since this is the most recently used context.
*/
static svc_rpc_gss_data *
{
} else {
}
return (NULL);
}
else
}
}
return (cl);
}
/*
* Given the client context handle, find the context corresponding to it.
* Don't change its LRU state since it may not be used.
*/
static svc_rpc_gss_data *
{
break;
}
return (cl);
}
/*
* Destroy a client context.
*/
static void
{
/*
* remove from hash list
*/
else
/*
* remove from LRU list
*/
else
else
/*
* If there is a GSS context, clean up GSS state.
*/
NULL);
}
}
}
}
/*
* Check for expired and stale client contexts.
*/
static void
{
while (cl) {
/*
* We assume here that any manipulation of the LRU pointers
* and hash bucket pointers are only done when holding the
* ctx_mutex.
*/
if (from_reclaim)
} else
} else
} else
}
}
/*
* Encrypt the serialized arguments from xdr_func applied to xdr_ptr
* and write the result to xdrs.
*/
static bool_t
{
/*
* If context is not established, or if neither integrity nor
* privacy service is used, don't wrap - just XDR encode.
* Otherwise, wrap data using service and QOP parameters.
*/
if (!gss_parms->established ||
}
/*
* Decrypt the serialized arguments and XDR decode them.
*/
static bool_t
{
/*
* If context is not established, or if neither integrity nor
* privacy service is used, don't unwrap - just XDR decode.
* Otherwise, unwrap data.
*/
if (!gss_parms->established ||
}
/* ARGSUSED */
int
{
return (0);
}
/*
* Add retransmit entry to the context cache entry for a new xid.
* If there is already an entry, delete it before adding the new one.
*/
{
return;
return;
}
if (client->retrans_data)
}
/*
* Delete the retransmit data from the context cache entry.
*/
static void retrans_del(client)
{
return;
}
}
/*
* This function frees the following fields of svc_rpc_gss_data:
* client_name, raw_cred.client_principal, raw_cred.mechanism.
*/
static void
{
}
sizeof (int));
}
/*
* In the user GSS-API library, mechanism (mech_type returned
* by gss_accept_sec_context) is static storage, however
* since all the work is done for gss_accept_sec_context under
* gssd, what is returned in the kernel, is a copy from the oid
* obtained under from gssd, so need to free it when destroying
* the client data.
*/
}
}