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
* 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 1995-2003 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 <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <gssapi/gssapi.h>
#include <gssapi/gssapi_ext.h>
#include <rpc/rpc.h>
#include <rpc/rpcsec_defs.h>
#include <sys/file.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <syslog.h>
/*
* Sequence window definitions.
*/
#define SEQ_ARR_SIZE 4
#define SEQ_WIN (SEQ_ARR_SIZE*32)
#define SEQ_HI_BIT 0x80000000
#define SEQ_LO_BIT 1
#define DIV_BY_32 5
#define SEQ_MASK 0x1f
#define SEQ_MAX 0x80000000
/* cache retransmit data */
typedef struct _retrans_entry {
uint32_t xid;
rpc_gss_init_res result;
struct _retrans_entry *next, *prev;
} retrans_entry;
/*
* Server side RPCSEC_GSS context information.
*/
typedef struct _svc_rpc_gss_data {
struct _svc_rpc_gss_data *next, *prev;
struct _svc_rpc_gss_data *lru_next, *lru_prev;
bool_t established;
gss_ctx_id_t context;
gss_name_t client_name;
gss_cred_id_t server_creds;
uint_t expiration;
uint_t seq_num;
uint_t seq_bits[SEQ_ARR_SIZE];
uint_t key;
OM_uint32 qop;
bool_t done_docallback;
bool_t locked;
rpc_gss_rawcred_t raw_cred;
rpc_gss_ucred_t u_cred;
bool_t u_cred_set;
void *cookie;
gss_cred_id_t deleg;
mutex_t clm;
int ref_cnt;
bool_t stale;
time_t time_secs_set;
retrans_entry *retrans_data;
} svc_rpc_gss_data;
/*
* Data structures used for LRU based context management.
*/
#define HASHMOD 256
#define HASHMASK 255
static svc_rpc_gss_data *clients[HASHMOD];
static svc_rpc_gss_data *lru_first, *lru_last;
static int num_gss_contexts = 0;
static int max_gss_contexts = 128;
static int sweep_interval = 10;
static int last_swept = 0;
static uint_t max_lifetime = GSS_C_INDEFINITE;
static int init_lifetime = 300;
static uint_t gid_timeout = 43200; /* 43200 secs = 12 hours */
/*
* lock used with context/lru variables
*/
static mutex_t ctx_mutex = DEFAULTMUTEX;
/*
* server credential management data and structures
*/
typedef struct svc_creds_list_s {
struct svc_creds_list_s *next;
gss_cred_id_t cred;
gss_name_t name;
rpcprog_t program;
rpcvers_t version;
gss_OID_set oid_set;
OM_uint32 req_time;
char *server_name;
mutex_t refresh_mutex;
} svc_creds_list_t;
static svc_creds_list_t *svc_creds_list;
static int svc_creds_count = 0;
/*
* lock used with server credential variables list
*
* server cred list locking guidelines:
* - Writer's lock holder has exclusive access to the list
* - Reader's lock holder(s) must also lock (refresh_mutex) each node
* before accessing that node's elements (ie. cred)
*/
static rwlock_t cred_lock = DEFAULTRWLOCK;
/*
* server callback list
*/
typedef struct cblist_s {
struct cblist_s *next;
rpc_gss_callback_t cb;
} cblist_t;
cblist_t *cblist = NULL;
/*
* lock used with callback variables
*/
static mutex_t cb_mutex = DEFAULTMUTEX;
/*
* 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();
static void drop_lru_client();
static void insert_client();
static bool_t check_verf();
static bool_t rpc_gss_refresh_svc_cred();
static bool_t set_response_verf();
static void retrans_add(svc_rpc_gss_data *, uint32_t,
rpc_gss_init_res *);
static void retrans_del(struct _svc_rpc_gss_data *);
/*
* server side wrap/unwrap routines
*/
struct svc_auth_ops svc_rpc_gss_ops = {
svc_rpc_gss_wrap,
svc_rpc_gss_unwrap,
};
/*
* Fetch server side authentication structure.
*/
extern SVCAUTH *__svc_get_svcauth();
/*
* Cleanup routine for destroying context, called after service
* procedure is executed, for MT safeness.
*/
extern void *__svc_set_proc_cleanup_cb();
static void (*old_cleanup_cb)() = NULL;
static bool_t cleanup_cb_set = FALSE;
static void
ctx_cleanup(xprt)
SVCXPRT *xprt;
{
svc_rpc_gss_data *cl;
SVCAUTH *svcauth;
if (old_cleanup_cb != NULL)
(*old_cleanup_cb)(xprt);
/*
* First check if current context needs to be cleaned up.
*/
svcauth = __svc_get_svcauth(xprt);
/*LINTED*/
if ((cl = (svc_rpc_gss_data *)svcauth->svc_ah_private) != NULL) {
mutex_lock(&cl->clm);
if (--cl->ref_cnt == 0 && cl->stale) {
mutex_unlock(&cl->clm);
mutex_lock(&ctx_mutex);
destroy_client(cl);
mutex_unlock(&ctx_mutex);
} else
mutex_unlock(&cl->clm);
}
/*
* Check for other expired contexts.
*/
if ((time(0) - last_swept) > sweep_interval) {
mutex_lock(&ctx_mutex);
/*
* Check again, in case some other thread got in.
*/
if ((time(0) - last_swept) > sweep_interval)
sweep_clients();
mutex_unlock(&ctx_mutex);
}
}
/*
* Set server parameters.
*/
void
__rpc_gss_set_server_parms(init_cred_lifetime, max_cred_lifetime, cache_size)
int init_cred_lifetime;
int max_cred_lifetime;
int cache_size;
{
/*
* Ignore parameters unless greater than zero.
*/
mutex_lock(&ctx_mutex);
if (cache_size > 0)
max_gss_contexts = cache_size;
if (max_cred_lifetime > 0)
max_lifetime = (uint_t)max_cred_lifetime;
if (init_cred_lifetime > 0)
init_lifetime = init_cred_lifetime;
mutex_unlock(&ctx_mutex);
}
/*
* Shift the array arr of length arrlen right by nbits bits.
*/
static void
shift_bits(arr, arrlen, nbits)
uint_t *arr;
int arrlen;
int nbits;
{
int i, j;
uint_t lo, hi;
/*
* If the number of bits to be shifted exceeds SEQ_WIN, just
* zero out the array.
*/
if (nbits < SEQ_WIN) {
for (i = 0; i < nbits; i++) {
hi = 0;
for (j = 0; j < arrlen; j++) {
lo = arr[j] & SEQ_LO_BIT;
arr[j] >>= 1;
if (hi)
arr[j] |= SEQ_HI_BIT;
hi = lo;
}
}
} else {
for (j = 0; j < arrlen; j++)
arr[j] = 0;
}
}
/*
* Check that the received sequence number seq_num is valid.
*/
static bool_t
check_seq(cl, seq_num, kill_context)
svc_rpc_gss_data *cl;
uint_t seq_num;
bool_t *kill_context;
{
int i, j;
uint_t bit;
/*
* If it exceeds the maximum, kill context.
*/
if (seq_num >= SEQ_MAX) {
*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.
*/
if (seq_num > cl->seq_num) {
shift_bits(cl->seq_bits, SEQ_ARR_SIZE, seq_num - cl->seq_num);
cl->seq_bits[0] |= SEQ_HI_BIT;
cl->seq_num = seq_num;
return (TRUE);
}
/*
* If it is outside the sequence window, return failure.
*/
i = cl->seq_num - seq_num;
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.
*/
j = SEQ_MASK - (i & SEQ_MASK);
bit = j > 0 ? (1 << j) : 1;
i >>= DIV_BY_32;
if (cl->seq_bits[i] & bit)
return (FALSE);
cl->seq_bits[i] |= bit;
return (TRUE);
}
/*
* Convert a name in gss exported type to rpc_gss_principal_t type.
*/
static bool_t
__rpc_gss_make_principal(principal, name)
rpc_gss_principal_t *principal;
gss_buffer_desc *name;
{
int plen;
char *s;
plen = RNDUP(name->length) + sizeof (int);
(*principal) = (rpc_gss_principal_t)malloc(plen);
if ((*principal) == NULL)
return (FALSE);
bzero((caddr_t)(*principal), plen);
(*principal)->len = RNDUP(name->length);
s = (*principal)->name;
memcpy(s, name->value, name->length);
return (TRUE);
}
/*
* Convert a name in internal form to the exported type.
*/
static bool_t
set_client_principal(g_name, r_name)
gss_name_t g_name;
rpc_gss_principal_t *r_name;
{
gss_buffer_desc name;
OM_uint32 major, minor;
bool_t ret = FALSE;
major = gss_export_name(&minor, g_name, &name);
if (major != GSS_S_COMPLETE)
return (FALSE);
ret = __rpc_gss_make_principal(r_name, &name);
(void) gss_release_buffer(&minor, &name);
return (ret);
}
/*
* Set server callback.
*/
bool_t
__rpc_gss_set_callback(cb)
rpc_gss_callback_t *cb;
{
cblist_t *cbl;
if (cb->callback == NULL)
return (FALSE);
if ((cbl = (cblist_t *)malloc(sizeof (*cbl))) == NULL)
return (FALSE);
cbl->cb = *cb;
mutex_lock(&cb_mutex);
cbl->next = cblist;
cblist = cbl;
mutex_unlock(&cb_mutex);
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
do_callback(req, client_data)
struct svc_req *req;
svc_rpc_gss_data *client_data;
{
cblist_t *cbl;
bool_t ret = TRUE, found = FALSE;
rpc_gss_lock_t lock;
OM_uint32 minor;
mutex_lock(&cb_mutex);
for (cbl = cblist; cbl != NULL; cbl = cbl->next) {
if (req->rq_prog != cbl->cb.program ||
req->rq_vers != cbl->cb.version)
continue;
found = TRUE;
lock.locked = FALSE;
lock.raw_cred = &client_data->raw_cred;
ret = (*cbl->cb.callback)(req, client_data->deleg,
client_data->context, &lock, &client_data->cookie);
if (ret) {
client_data->locked = lock.locked;
client_data->deleg = GSS_C_NO_CREDENTIAL;
}
break;
}
if (!found) {
if (client_data->deleg != GSS_C_NO_CREDENTIAL) {
(void) gss_release_cred(&minor, &client_data->deleg);
client_data->deleg = GSS_C_NO_CREDENTIAL;
}
}
mutex_unlock(&cb_mutex);
return (ret);
}
/*
* Return caller credentials.
*/
bool_t
__rpc_gss_getcred(req, rcred, ucred, cookie)
struct svc_req *req;
rpc_gss_rawcred_t **rcred;
rpc_gss_ucred_t **ucred;
void **cookie;
{
SVCAUTH *svcauth;
svc_rpc_gss_data *client_data;
svc_rpc_gss_parms_t *gss_parms;
gss_OID oid;
OM_uint32 status;
int len = 0;
struct timeval now;
svcauth = __svc_get_svcauth(req->rq_xprt);
/*LINTED*/
client_data = (svc_rpc_gss_data *)svcauth->svc_ah_private;
gss_parms = &svcauth->svc_gss_parms;
mutex_lock(&client_data->clm);
if (rcred != NULL) {
svcauth->raw_cred = client_data->raw_cred;
svcauth->raw_cred.service = gss_parms->service;
svcauth->raw_cred.qop = __rpc_gss_num_to_qop(
svcauth->raw_cred.mechanism, gss_parms->qop_rcvd);
*rcred = &svcauth->raw_cred;
}
if (ucred != NULL) {
if (!client_data->u_cred_set) {
/*
* Double check making sure ucred is not set
* after acquiring the lock.
*/
if (!client_data->u_cred_set) {
if (!__rpc_gss_mech_to_oid(
(*rcred)->mechanism, &oid)) {
fprintf(stderr, dgettext(TEXT_DOMAIN,
"mech_to_oid failed in getcred.\n"));
*ucred = NULL;
} else {
status = gsscred_name_to_unix_cred(
client_data->client_name, oid,
&client_data->u_cred.uid,
&client_data->u_cred.gid,
&client_data->u_cred.gidlist,
&len);
if (status == GSS_S_COMPLETE) {
client_data->u_cred_set = TRUE;
client_data->u_cred.gidlen =
(short)len;
gettimeofday(&now,
(struct timezone *)NULL);
client_data->time_secs_set =
now.tv_sec;
*ucred = &client_data->u_cred;
} else
*ucred = NULL;
}
}
} else {
/*
* gid's already set;
* check if they have expired.
*/
gettimeofday(&now, (struct timezone *)NULL);
if ((now.tv_sec - client_data->time_secs_set)
> gid_timeout) {
/* Refresh gid's */
status = gss_get_group_info(
client_data->u_cred.uid,
&client_data->u_cred.gid,
&client_data->u_cred.gidlist,
&len);
if (status == GSS_S_COMPLETE) {
client_data->u_cred.gidlen =
(short)len;
gettimeofday(&now,
(struct timezone *)NULL);
client_data->time_secs_set = now.tv_sec;
*ucred = &client_data->u_cred;
} else {
client_data->u_cred_set = FALSE;
*ucred = NULL;
}
}
else
*ucred = &client_data->u_cred;
}
}
if (cookie != NULL)
*cookie = client_data->cookie;
mutex_unlock(&client_data->clm);
return (TRUE);
}
/*
* Server side authentication for RPCSEC_GSS.
*/
enum auth_stat
__svcrpcsec_gss(rqst, msg, no_dispatch)
struct svc_req *rqst;
struct rpc_msg *msg;
bool_t *no_dispatch;
{
XDR xdrs;
rpc_gss_creds creds;
rpc_gss_init_arg call_arg;
rpc_gss_init_res call_res, *retrans_result;
gss_buffer_desc output_token;
OM_uint32 gssstat, minor_stat, time_rec, ret_flags;
struct opaque_auth *cred;
svc_rpc_gss_data *client_data;
int ret;
svc_creds_list_t *sc;
SVCAUTH *svcauth;
svc_rpc_gss_parms_t *gss_parms;
gss_OID mech_type = GSS_C_NULL_OID;
/*
* Initialize response verifier to NULL verifier. If
* necessary, this will be changed later.
*/
rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NONE;
rqst->rq_xprt->xp_verf.oa_base = NULL;
rqst->rq_xprt->xp_verf.oa_length = 0;
/*
* Need to null out results to start with.
*/
memset((char *)&call_res, 0, sizeof (call_res));
/*
* Pull out and check credential and verifier.
*/
cred = &msg->rm_call.cb_cred;
if (cred->oa_length == 0) {
ret = AUTH_BADCRED;
goto error;
}
xdrmem_create(&xdrs, cred->oa_base, cred->oa_length, XDR_DECODE);
memset((char *)&creds, 0, sizeof (creds));
if (!__xdr_rpc_gss_creds(&xdrs, &creds)) {
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.
*/
if (creds.gss_proc == RPCSEC_GSS_INIT) {
if (creds.ctx_handle.length != 0) {
ret = AUTH_BADCRED;
goto error;
}
if ((client_data = create_client()) == NULL) {
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
*/
if (creds.gss_proc != RPCSEC_GSS_CONTINUE_INIT) {
switch (creds.service) {
case rpc_gss_svc_none:
case rpc_gss_svc_integrity:
case rpc_gss_svc_privacy:
break;
default:
ret = AUTH_BADCRED;
goto error;
}
}
if (creds.ctx_handle.length == 0) {
ret = AUTH_BADCRED;
goto error;
}
if ((client_data = get_client(&creds.ctx_handle)) == NULL) {
ret = RPCSEC_GSS_NOCRED;
goto error;
}
}
/*
* lock the client data until it's safe; if it's already stale,
* no more processing is possible
*/
mutex_lock(&client_data->clm);
if (client_data->stale) {
ret = RPCSEC_GSS_NOCRED;
goto error2;
}
/*
* Any response we send will use ctx_handle, so set it now;
* also set seq_window since this won't change.
*/
call_res.ctx_handle.length = sizeof (client_data->key);
call_res.ctx_handle.value = (char *)&client_data->key;
call_res.seq_window = SEQ_WIN;
/*
* Set the appropriate wrap/unwrap routine for RPCSEC_GSS.
*/
svcauth = __svc_get_svcauth(rqst->rq_xprt);
svcauth->svc_ah_ops = svc_rpc_gss_ops;
svcauth->svc_ah_private = (caddr_t)client_data;
/*
* 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.
*/
gss_parms = &svcauth->svc_gss_parms;
gss_parms->established = client_data->established;
gss_parms->service = creds.service;
gss_parms->qop_rcvd = (uint_t)client_data->qop;
gss_parms->context = (void *)client_data->context;
gss_parms->seq_num = creds.seq_num;
if (!client_data->established) {
if (creds.gss_proc == RPCSEC_GSS_DATA) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
goto error2;
}
/*
* If the context is not established, then only GSSAPI_INIT
* and _CONTINUE requests are valid.
*/
if (creds.gss_proc != RPCSEC_GSS_INIT && creds.gss_proc !=
RPCSEC_GSS_CONTINUE_INIT) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
goto error2;
}
/*
* call is for us, deserialize arguments
*/
memset(&call_arg, 0, sizeof (call_arg));
if (!svc_getargs(rqst->rq_xprt, __xdr_rpc_gss_init_arg,
(caddr_t)&call_arg)) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
goto error2;
}
gssstat = GSS_S_FAILURE;
minor_stat = 0;
rw_rdlock(&cred_lock);
/*
* set next sc to point to the server cred
* if the client_data contains server_creds
*/
for (sc = svc_creds_list; sc != NULL; sc = sc->next) {
if (rqst->rq_prog != sc->program ||
rqst->rq_vers != sc->version)
continue;
mutex_lock(&sc->refresh_mutex);
gssstat = gss_accept_sec_context(&minor_stat,
&client_data->context,
sc->cred,
&call_arg,
GSS_C_NO_CHANNEL_BINDINGS,
&client_data->client_name,
&mech_type,
&output_token,
&ret_flags,
&time_rec,
NULL);
if (gssstat == GSS_S_CREDENTIALS_EXPIRED) {
if (rpc_gss_refresh_svc_cred(sc)) {
gssstat = gss_accept_sec_context(
&minor_stat,
&client_data->context,
sc->cred,
&call_arg,
GSS_C_NO_CHANNEL_BINDINGS,
&client_data->client_name,
&mech_type,
&output_token,
&ret_flags,
&time_rec,
NULL);
mutex_unlock(&sc->refresh_mutex);
} else {
mutex_unlock(&sc->refresh_mutex);
gssstat = GSS_S_NO_CRED;
break;
}
} else
mutex_unlock(&sc->refresh_mutex);
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.
*/
/*
* XXX server_creds will prob be stale
* after rpc_gss_refresh_svc_cred(), but
* it appears not to ever be referenced
* anyways.
*/
mutex_lock(&sc->refresh_mutex);
client_data->server_creds = sc->cred;
client_data->raw_cred.version = creds.version;
client_data->raw_cred.service = creds.service;
client_data->raw_cred.svc_principal =
sc->server_name;
mutex_unlock(&sc->refresh_mutex);
if ((client_data->raw_cred.mechanism
= __rpc_gss_oid_to_mech(mech_type))
== NULL) {
gssstat = GSS_S_FAILURE;
(void) gss_release_buffer(&minor_stat,
&output_token);
} else if (!set_client_principal(client_data->
client_name, &client_data->
raw_cred.client_principal)) {
gssstat = GSS_S_FAILURE;
(void) gss_release_buffer(&minor_stat,
&output_token);
}
break;
}
if (gssstat == GSS_S_CONTINUE_NEEDED) {
/*
* XXX server_creds will prob be stale
* after rpc_gss_refresh_svc_cred(), but
* it appears not to ever be referenced
* anyways.
*/
mutex_lock(&sc->refresh_mutex);
client_data->server_creds = sc->cred;
mutex_unlock(&sc->refresh_mutex);
break;
}
}
rw_unlock(&cred_lock);
call_res.gss_major = gssstat;
call_res.gss_minor = minor_stat;
xdr_free(__xdr_rpc_gss_init_arg, (caddr_t)&call_arg);
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.ctx_handle.length = 0;
call_res.ctx_handle.value = NULL;
call_res.seq_window = 0;
svc_sendreply(rqst->rq_xprt, __xdr_rpc_gss_init_res,
(caddr_t)&call_res);
*no_dispatch = TRUE;
ret = AUTH_OK;
client_data->stale = TRUE;
goto error2;
}
/*
* This step succeeded. Send a response, along with
* a token if there's one. Don't dispatch.
*/
if (output_token.length != 0) {
GSS_COPY_BUFFER(call_res.token, output_token);
}
/*
* set response verifier: checksum of SEQ_WIN
*/
if (gssstat == GSS_S_COMPLETE) {
if (!set_response_verf(rqst, msg, client_data,
(uint_t)SEQ_WIN)) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
(void) gss_release_buffer(&minor_stat,
&output_token);
goto error2;
}
}
svc_sendreply(rqst->rq_xprt, __xdr_rpc_gss_init_res,
(caddr_t)&call_res);
/*
* Cache last response in case it is lost and the client
* retries on an established context.
*/
(void) retrans_add(client_data, msg->rm_xid, &call_res);
*no_dispatch = TRUE;
(void) gss_release_buffer(&minor_stat, &output_token);
/*
* 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 expiry time for
* context (the minimum of time_rec and max_lifetime).
*/
client_data->seq_num = 1;
if (time_rec == GSS_C_INDEFINITE) {
if (max_lifetime != GSS_C_INDEFINITE)
client_data->expiration =
max_lifetime + time(0);
else
client_data->expiration =
GSS_C_INDEFINITE;
} else if (max_lifetime == GSS_C_INDEFINITE ||
max_lifetime > time_rec)
client_data->expiration = time_rec + time(0);
else
client_data->expiration = max_lifetime +
time(0);
client_data->established = TRUE;
}
} else {
if ((creds.gss_proc != RPCSEC_GSS_DATA) &&
(creds.gss_proc != RPCSEC_GSS_DESTROY)) {
switch (creds.gss_proc) {
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 &&
(client_data->retrans_data->xid == msg->rm_xid)) {
retrans_result = &client_data->retrans_data->result;
if (set_response_verf(rqst, msg, client_data,
(uint_t)retrans_result->seq_window)) {
gss_parms->established = FALSE;
svc_sendreply(rqst->rq_xprt,
__xdr_rpc_gss_init_res,
(caddr_t)retrans_result);
*no_dispatch = TRUE;
goto success;
}
}
/* fall thru to default */
default:
syslog(LOG_ERR, "_svcrpcsec_gss: non-data request "
"on an established context");
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)
retrans_del(client_data);
/*
* Context is already established. Check verifier, and
* note parameters we will need for response in gss_parms.
*/
if (!check_verf(msg, client_data->context,
&gss_parms->qop_rcvd)) {
ret = RPCSEC_GSS_NOCRED;
goto error2;
}
/*
* Check and invoke callback if necessary.
*/
if (!client_data->done_docallback) {
client_data->done_docallback = TRUE;
client_data->qop = gss_parms->qop_rcvd;
client_data->raw_cred.qop = __rpc_gss_num_to_qop(
client_data->raw_cred.mechanism,
gss_parms->qop_rcvd);
client_data->raw_cred.service = creds.service;
if (!do_callback(rqst, client_data)) {
ret = AUTH_FAILED;
client_data->stale = TRUE;
goto error2;
}
}
/*
* If the context was locked, make sure that the client
* has not changed QOP.
*/
if (client_data->locked &&
gss_parms->qop_rcvd != client_data->qop) {
ret = AUTH_BADVERF;
goto error2;
}
/*
* Validate sequence number.
*/
if (!check_seq(client_data, creds.seq_num,
&client_data->stale)) {
if (client_data->stale)
ret = RPCSEC_GSS_FAILED;
else {
/*
* 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.
*/
ret = AUTH_OK;
*no_dispatch = TRUE;
}
goto error2;
}
/*
* set response verifier
*/
if (!set_response_verf(rqst, msg, client_data, creds.seq_num)) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
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.
*/
if (creds.gss_proc == RPCSEC_GSS_DESTROY) {
svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
*no_dispatch = TRUE;
client_data->stale = TRUE;
} 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 &&
client_data->raw_cred.service != creds.service) {
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).
*/
rqst->rq_clntcred = (char *)&client_data->raw_cred;
}
}
success:
/*
* Success.
*/
if (creds.ctx_handle.length != 0)
xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
mutex_unlock(&client_data->clm);
return (AUTH_OK);
error2:
mutex_unlock(&client_data->clm);
error:
/*
* Failure.
*/
if (creds.ctx_handle.length != 0)
xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
return (ret);
}
/*
* Check verifier. The verifier is the checksum of the RPC header
* upto and including the credentials field.
*/
static bool_t
check_verf(msg, context, qop_state)
struct rpc_msg *msg;
gss_ctx_id_t context;
int *qop_state;
{
int *buf, *tmp;
int hdr[32];
struct opaque_auth *oa;
int len;
gss_buffer_desc msg_buf;
gss_buffer_desc tok_buf;
OM_uint32 gssstat, minor_stat;
/*
* We have to reconstruct the RPC header from the previously
* parsed information, since we haven't kept the header intact.
*/
buf = hdr;
IXDR_PUT_U_INT32(buf, msg->rm_xid);
IXDR_PUT_ENUM(buf, msg->rm_direction);
IXDR_PUT_U_INT32(buf, msg->rm_call.cb_rpcvers);
IXDR_PUT_U_INT32(buf, msg->rm_call.cb_prog);
IXDR_PUT_U_INT32(buf, msg->rm_call.cb_vers);
IXDR_PUT_U_INT32(buf, msg->rm_call.cb_proc);
oa = &msg->rm_call.cb_cred;
IXDR_PUT_ENUM(buf, oa->oa_flavor);
IXDR_PUT_U_INT32(buf, oa->oa_length);
if (oa->oa_length) {
len = RNDUP(oa->oa_length);
tmp = buf;
buf += len / sizeof (int);
*(buf - 1) = 0;
(void) memcpy((caddr_t)tmp, oa->oa_base, oa->oa_length);
}
len = ((char *)buf) - (char *)hdr;
msg_buf.length = len;
msg_buf.value = (char *)hdr;
oa = &msg->rm_call.cb_verf;
tok_buf.length = oa->oa_length;
tok_buf.value = oa->oa_base;
gssstat = gss_verify(&minor_stat, context, &msg_buf, &tok_buf,
qop_state);
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
set_response_verf(rqst, msg, cl, num)
struct svc_req *rqst;
struct rpc_msg *msg;
svc_rpc_gss_data *cl;
uint_t num;
{
OM_uint32 minor;
gss_buffer_desc in_buf, out_buf;
uint_t num_net;
num_net = (uint_t)htonl(num);
in_buf.length = sizeof (num);
in_buf.value = (char *)&num_net;
if (gss_sign(&minor, cl->context, cl->qop, &in_buf,
&out_buf) != GSS_S_COMPLETE)
return (FALSE);
rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
rqst->rq_xprt->xp_verf.oa_length = out_buf.length;
memcpy(rqst->rq_xprt->xp_verf.oa_base, out_buf.value,
out_buf.length);
(void) gss_release_buffer(&minor, &out_buf);
return (TRUE);
}
/*
* Create client context.
*/
static svc_rpc_gss_data *
create_client()
{
svc_rpc_gss_data *client_data;
static uint_t key = 1;
client_data = (svc_rpc_gss_data *) malloc(sizeof (*client_data));
if (client_data == NULL)
return (NULL);
memset((char *)client_data, 0, sizeof (*client_data));
/*
* set up client data structure
*/
client_data->established = FALSE;
client_data->locked = FALSE;
client_data->u_cred_set = FALSE;
client_data->context = GSS_C_NO_CONTEXT;
client_data->expiration = init_lifetime + time(0);
client_data->ref_cnt = 1;
client_data->qop = GSS_C_QOP_DEFAULT;
client_data->done_docallback = FALSE;
client_data->stale = FALSE;
client_data->time_secs_set = 0;
client_data->retrans_data = NULL;
mutex_init(&client_data->clm, USYNC_THREAD, NULL);
/*
* Check totals. If we've hit the limit, we destroy a context
* based on LRU method.
*/
mutex_lock(&ctx_mutex);
if (num_gss_contexts >= max_gss_contexts) {
/*
* now try on LRU basis
*/
drop_lru_client();
if (num_gss_contexts >= max_gss_contexts) {
mutex_unlock(&ctx_mutex);
free((char *)client_data);
return (NULL);
}
}
/*
* The client context handle is a 32-bit key (unsigned int).
* The key is incremented until there is no duplicate for it.
*/
for (;;) {
client_data->key = key++;
if (find_client(client_data->key) == NULL) {
insert_client(client_data);
/*
* Set cleanup callback if we haven't.
*/
if (!cleanup_cb_set) {
old_cleanup_cb =
(void (*)()) __svc_set_proc_cleanup_cb(
(void *)ctx_cleanup);
cleanup_cb_set = TRUE;
}
mutex_unlock(&ctx_mutex);
return (client_data);
}
}
/*NOTREACHED*/
}
/*
* Insert client context into hash list and LRU list.
*/
static void
insert_client(client_data)
svc_rpc_gss_data *client_data;
{
svc_rpc_gss_data *cl;
int index = (client_data->key & HASHMASK);
client_data->prev = NULL;
cl = clients[index];
if ((client_data->next = cl) != NULL)
cl->prev = client_data;
clients[index] = client_data;
client_data->lru_prev = NULL;
if ((client_data->lru_next = lru_first) != NULL)
lru_first->lru_prev = client_data;
else
lru_last = client_data;
lru_first = client_data;
num_gss_contexts++;
}
/*
* 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 *
get_client(ctx_handle)
gss_buffer_t ctx_handle;
{
uint_t key = *(uint_t *)ctx_handle->value;
svc_rpc_gss_data *cl;
mutex_lock(&ctx_mutex);
if ((cl = find_client(key)) != NULL) {
mutex_lock(&cl->clm);
if (cl->stale) {
mutex_unlock(&cl->clm);
mutex_unlock(&ctx_mutex);
return (NULL);
}
cl->ref_cnt++;
mutex_unlock(&cl->clm);
if (cl != lru_first) {
cl->lru_prev->lru_next = cl->lru_next;
if (cl->lru_next != NULL)
cl->lru_next->lru_prev = cl->lru_prev;
else
lru_last = cl->lru_prev;
cl->lru_prev = NULL;
cl->lru_next = lru_first;
lru_first->lru_prev = cl;
lru_first = cl;
}
}
mutex_unlock(&ctx_mutex);
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 *
find_client(key)
uint_t key;
{
int index = (key & HASHMASK);
svc_rpc_gss_data *cl;
for (cl = clients[index]; cl != NULL; cl = cl->next) {
if (cl->key == key)
break;
}
return (cl);
}
/*
* Destroy a client context.
*/
static void
destroy_client(client_data)
svc_rpc_gss_data *client_data;
{
OM_uint32 minor;
int index = (client_data->key & HASHMASK);
/*
* remove from hash list
*/
if (client_data->prev == NULL)
clients[index] = client_data->next;
else
client_data->prev->next = client_data->next;
if (client_data->next != NULL)
client_data->next->prev = client_data->prev;
/*
* remove from LRU list
*/
if (client_data->lru_prev == NULL)
lru_first = client_data->lru_next;
else
client_data->lru_prev->lru_next = client_data->lru_next;
if (client_data->lru_next != NULL)
client_data->lru_next->lru_prev = client_data->lru_prev;
else
lru_last = client_data->lru_prev;
/*
* If there is a GSS context, clean up GSS state.
*/
if (client_data->context != GSS_C_NO_CONTEXT) {
(void) gss_delete_sec_context(&minor, &client_data->context,
NULL);
if (client_data->client_name)
(void) gss_release_name(&minor, &client_data->client_name);
if (client_data->raw_cred.client_principal)
free((char *)client_data->raw_cred.client_principal);
if (client_data->u_cred.gidlist != NULL)
free((char *)client_data->u_cred.gidlist);
if (client_data->deleg != GSS_C_NO_CREDENTIAL)
(void) gss_release_cred(&minor, &client_data->deleg);
}
if (client_data->retrans_data != NULL)
retrans_del(client_data);
free(client_data);
num_gss_contexts--;
}
/*
* Check for expired client contexts.
*/
static void
sweep_clients()
{
svc_rpc_gss_data *cl, *next;
int index;
for (index = 0; index < HASHMOD; index++) {
cl = clients[index];
while (cl) {
next = cl->next;
mutex_lock(&cl->clm);
if ((cl->expiration != GSS_C_INDEFINITE &&
cl->expiration <= time(0)) || cl->stale) {
cl->stale = TRUE;
if (cl->ref_cnt == 0) {
mutex_unlock(&cl->clm);
destroy_client(cl);
} else
mutex_unlock(&cl->clm);
} else
mutex_unlock(&cl->clm);
cl = next;
}
}
last_swept = time(0);
}
/*
* Drop the least recently used client context, if possible.
*/
static void
drop_lru_client()
{
mutex_lock(&lru_last->clm);
lru_last->stale = TRUE;
mutex_unlock(&lru_last->clm);
if (lru_last->ref_cnt == 0)
destroy_client(lru_last);
else
sweep_clients();
}
/*
* find service credentials
* return cred if found,
* other wise, NULL
*/
svc_creds_list_t *
find_svc_cred(char *service_name, uint_t program, uint_t version) {
svc_creds_list_t *sc;
if (!svc_creds_list)
return (NULL);
for (sc = svc_creds_list; sc != NULL; sc = sc->next) {
if (program != sc->program || version != sc->version)
continue;
if (strcmp(service_name, sc->server_name) != 0)
continue;
return (sc);
}
return (NULL);
}
/*
* Set the server principal name.
*/
bool_t
__rpc_gss_set_svc_name(server_name, mech, req_time, program, version)
char *server_name;
char *mech;
OM_uint32 req_time;
uint_t program;
uint_t version;
{
gss_name_t name;
svc_creds_list_t *svc_cred;
gss_OID mechanism;
gss_OID_set_desc oid_set_desc;
gss_OID_set oid_set;
OM_uint32 ret_time;
OM_uint32 major, minor;
gss_buffer_desc name_buf;
if (!__rpc_gss_mech_to_oid(mech, &mechanism)) {
return (FALSE);
}
name_buf.value = server_name;
name_buf.length = strlen(server_name);
major = gss_import_name(&minor, &name_buf,
(gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &name);
if (major != GSS_S_COMPLETE) {
return (FALSE);
}
/* Check if there is already an entry in the svc_creds_list. */
rw_wrlock(&cred_lock);
if (svc_cred = find_svc_cred(server_name, program, version)) {
major = gss_add_cred(&minor, svc_cred->cred, name,
mechanism, GSS_C_ACCEPT,
0, req_time, NULL,
&oid_set, NULL,
&ret_time);
(void) gss_release_name(&minor, &name);
if (major == GSS_S_COMPLETE) {
/*
* Successfully added the mech to the cred handle
* free the existing oid_set in svc_cred
*/
gss_release_oid_set(&minor, &svc_cred->oid_set);
svc_cred->oid_set = oid_set;
rw_unlock(&cred_lock);
return (TRUE);
} else if (major == GSS_S_DUPLICATE_ELEMENT) {
rw_unlock(&cred_lock);
return (TRUE);
} else if (major == GSS_S_CREDENTIALS_EXPIRED) {
if (rpc_gss_refresh_svc_cred(svc_cred)) {
rw_unlock(&cred_lock);
return (TRUE);
} else {
rw_unlock(&cred_lock);
return (FALSE);
}
} else {
rw_unlock(&cred_lock);
return (FALSE);
}
} else {
svc_cred = (svc_creds_list_t *)malloc(sizeof (*svc_cred));
if (svc_cred == NULL) {
(void) gss_release_name(&minor, &name);
rw_unlock(&cred_lock);
return (FALSE);
}
oid_set_desc.count = 1;
oid_set_desc.elements = mechanism;
major = gss_acquire_cred(&minor, name, req_time,
&oid_set_desc,
GSS_C_ACCEPT,
&svc_cred->cred,
&oid_set, &ret_time);
if (major != GSS_S_COMPLETE) {
(void) gss_release_name(&minor, &name);
free(svc_cred);
rw_unlock(&cred_lock);
return (FALSE);
}
svc_cred->name = name;
svc_cred->program = program;
svc_cred->version = version;
svc_cred->req_time = req_time;
svc_cred->oid_set = oid_set;
svc_cred->server_name = strdup(server_name);
if (svc_cred->server_name == NULL) {
(void) gss_release_name(&minor, &name);
free((char *)svc_cred);
rw_unlock(&cred_lock);
return (FALSE);
}
mutex_init(&svc_cred->refresh_mutex, USYNC_THREAD, NULL);
svc_cred->next = svc_creds_list;
svc_creds_list = svc_cred;
svc_creds_count++;
rw_unlock(&cred_lock);
return (TRUE);
}
}
/*
* Refresh server credentials.
*/
static bool_t
rpc_gss_refresh_svc_cred(svc_cred)
svc_creds_list_t *svc_cred;
{
OM_uint32 major, minor;
gss_OID_set oid_set;
OM_uint32 ret_time;
(void) gss_release_cred(&minor, &svc_cred->cred);
svc_cred->cred = GSS_C_NO_CREDENTIAL;
major = gss_acquire_cred(&minor, svc_cred->name, svc_cred->req_time,
svc_cred->oid_set, GSS_C_ACCEPT, &svc_cred->cred, &oid_set,
&ret_time);
if (major != GSS_S_COMPLETE) {
return (FALSE);
}
gss_release_oid_set(&minor, &svc_cred->oid_set);
svc_cred->oid_set = oid_set;
return (TRUE);
}
/*
* Encrypt the serialized arguments from xdr_func applied to xdr_ptr
* and write the result to xdrs.
*/
static bool_t
svc_rpc_gss_wrap(auth, out_xdrs, xdr_func, xdr_ptr)
SVCAUTH *auth;
XDR *out_xdrs;
bool_t (*xdr_func)();
caddr_t xdr_ptr;
{
svc_rpc_gss_parms_t *gss_parms = &auth->svc_gss_parms;
/*
* 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 ||
gss_parms->service == rpc_gss_svc_none)
return ((*xdr_func)(out_xdrs, xdr_ptr));
return (__rpc_gss_wrap_data(gss_parms->service,
(OM_uint32)gss_parms->qop_rcvd,
(gss_ctx_id_t)gss_parms->context,
gss_parms->seq_num,
out_xdrs, xdr_func, xdr_ptr));
}
/*
* Decrypt the serialized arguments and XDR decode them.
*/
static bool_t
svc_rpc_gss_unwrap(auth, in_xdrs, xdr_func, xdr_ptr)
SVCAUTH *auth;
XDR *in_xdrs;
bool_t (*xdr_func)();
caddr_t xdr_ptr;
{
svc_rpc_gss_parms_t *gss_parms = &auth->svc_gss_parms;
/*
* 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 ||
gss_parms->service == rpc_gss_svc_none)
return ((*xdr_func)(in_xdrs, xdr_ptr));
return (__rpc_gss_unwrap_data(gss_parms->service,
(gss_ctx_id_t)gss_parms->context,
gss_parms->seq_num,
gss_parms->qop_rcvd,
in_xdrs, xdr_func, xdr_ptr));
}
int
__rpc_gss_svc_max_data_length(req, max_tp_unit_len)
struct svc_req *req;
int max_tp_unit_len;
{
SVCAUTH *svcauth;
svc_rpc_gss_parms_t *gss_parms;
svcauth = __svc_get_svcauth(req->rq_xprt);
gss_parms = &svcauth->svc_gss_parms;
if (!gss_parms->established || max_tp_unit_len <= 0)
return (0);
return (__find_max_data_length(gss_parms->service,
(gss_ctx_id_t)gss_parms->context,
gss_parms->qop_rcvd, max_tp_unit_len));
}
/*
* 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.
*/
static void retrans_add(client, xid, result)
svc_rpc_gss_data *client;
uint32_t xid;
rpc_gss_init_res *result;
{
retrans_entry *rdata;
if (client->retrans_data && client->retrans_data->xid == xid)
return;
rdata = (retrans_entry *) malloc(sizeof (*rdata));
if (rdata == NULL)
return;
rdata->xid = xid;
rdata->result = *result;
if (result->token.length != 0) {
GSS_DUP_BUFFER(rdata->result.token, result->token);
}
if (client->retrans_data)
retrans_del(client);
client->retrans_data = rdata;
}
/*
* Delete the retransmit data from the context cache entry.
*/
static void retrans_del(client)
svc_rpc_gss_data *client;
{
retrans_entry *rdata;
OM_uint32 minor_stat;
if (client->retrans_data == NULL)
return;
rdata = client->retrans_data;
if (rdata->result.token.length != 0) {
(void) gss_release_buffer(&minor_stat, &rdata->result.token);
}
free((caddr_t)rdata);
client->retrans_data = NULL;
}