2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 1986, 2010, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A/*
2N/A * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved.
2N/A *
2N/A * $Id: svc_auth_gssapi.c,v 1.19 1994/10/27 12:38:51 jik Exp $
2N/A */
2N/A
2N/A/*
2N/A * Server side handling of RPCSEC_GSS flavor.
2N/A */
2N/A
2N/A#include <stdio.h>
2N/A#include <stdlib.h>
2N/A#include <strings.h>
2N/A#include <sys/stat.h>
2N/A#include <sys/time.h>
2N/A#include <gssapi/gssapi.h>
2N/A#include <gssapi/gssapi_ext.h>
2N/A#include <rpc/rpc.h>
2N/A#include <rpc/rpcsec_defs.h>
2N/A#include <sys/file.h>
2N/A#include <fcntl.h>
2N/A#include <pwd.h>
2N/A#include <stdio.h>
2N/A#include <syslog.h>
2N/A
2N/A/*
2N/A * Sequence window definitions.
2N/A */
2N/A#define SEQ_ARR_SIZE 4
2N/A#define SEQ_WIN (SEQ_ARR_SIZE*32)
2N/A#define SEQ_HI_BIT 0x80000000
2N/A#define SEQ_LO_BIT 1
2N/A#define DIV_BY_32 5
2N/A#define SEQ_MASK 0x1f
2N/A#define SEQ_MAX 0x80000000
2N/A
2N/A
2N/A/* cache retransmit data */
2N/Atypedef struct _retrans_entry {
2N/A uint32_t xid;
2N/A rpc_gss_init_res result;
2N/A struct _retrans_entry *next, *prev;
2N/A} retrans_entry;
2N/A
2N/A/*
2N/A * Server side RPCSEC_GSS context information.
2N/A */
2N/Atypedef struct _svc_rpc_gss_data {
2N/A struct _svc_rpc_gss_data *next, *prev;
2N/A struct _svc_rpc_gss_data *lru_next, *lru_prev;
2N/A bool_t established;
2N/A gss_ctx_id_t context;
2N/A gss_name_t client_name;
2N/A gss_cred_id_t server_creds;
2N/A uint_t expiration;
2N/A uint_t seq_num;
2N/A uint_t seq_bits[SEQ_ARR_SIZE];
2N/A uint_t key;
2N/A OM_uint32 qop;
2N/A bool_t done_docallback;
2N/A bool_t locked;
2N/A rpc_gss_rawcred_t raw_cred;
2N/A rpc_gss_ucred_t u_cred;
2N/A bool_t u_cred_set;
2N/A void *cookie;
2N/A gss_cred_id_t deleg;
2N/A mutex_t clm;
2N/A int ref_cnt;
2N/A bool_t stale;
2N/A time_t time_secs_set;
2N/A retrans_entry *retrans_data;
2N/A} svc_rpc_gss_data;
2N/A
2N/A/*
2N/A * Data structures used for LRU based context management.
2N/A */
2N/A#define HASHMOD 256
2N/A#define HASHMASK 255
2N/A
2N/Astatic svc_rpc_gss_data *clients[HASHMOD];
2N/Astatic svc_rpc_gss_data *lru_first, *lru_last;
2N/Astatic int num_gss_contexts = 0;
2N/Astatic int max_gss_contexts = 128;
2N/Astatic int sweep_interval = 10;
2N/Astatic int last_swept = 0;
2N/Astatic uint_t max_lifetime = GSS_C_INDEFINITE;
2N/Astatic int init_lifetime = 0;
2N/Astatic uint_t gid_timeout = 43200; /* 43200 secs = 12 hours */
2N/A
2N/A/*
2N/A * lock used with context/lru variables
2N/A */
2N/Astatic mutex_t ctx_mutex = DEFAULTMUTEX;
2N/A
2N/A/*
2N/A * server credential management data and structures
2N/A */
2N/Atypedef struct svc_creds_list_s {
2N/A struct svc_creds_list_s *next;
2N/A gss_cred_id_t cred;
2N/A gss_name_t name;
2N/A rpcprog_t program;
2N/A rpcvers_t version;
2N/A gss_OID_set oid_set;
2N/A OM_uint32 req_time;
2N/A char *server_name;
2N/A mutex_t refresh_mutex;
2N/A} svc_creds_list_t;
2N/A
2N/A
2N/Astatic svc_creds_list_t *svc_creds_list;
2N/Astatic int svc_creds_count = 0;
2N/A
2N/A/*
2N/A * lock used with server credential variables list
2N/A *
2N/A * server cred list locking guidelines:
2N/A * - Writer's lock holder has exclusive access to the list
2N/A * - Reader's lock holder(s) must also lock (refresh_mutex) each node
2N/A * before accessing that node's elements (ie. cred)
2N/A */
2N/Astatic rwlock_t cred_lock = DEFAULTRWLOCK;
2N/A
2N/A/*
2N/A * server callback list
2N/A */
2N/Atypedef struct cblist_s {
2N/A struct cblist_s *next;
2N/A rpc_gss_callback_t cb;
2N/A} cblist_t;
2N/A
2N/Acblist_t *cblist = NULL;
2N/A
2N/A/*
2N/A * lock used with callback variables
2N/A */
2N/Astatic mutex_t cb_mutex = DEFAULTMUTEX;
2N/A
2N/A/*
2N/A * forward declarations
2N/A */
2N/Astatic bool_t svc_rpc_gss_wrap();
2N/Astatic bool_t svc_rpc_gss_unwrap();
2N/Astatic svc_rpc_gss_data *create_client();
2N/Astatic svc_rpc_gss_data *get_client();
2N/Astatic svc_rpc_gss_data *find_client();
2N/Astatic void destroy_client();
2N/Astatic void sweep_clients();
2N/Astatic void drop_lru_client();
2N/Astatic void insert_client();
2N/Astatic bool_t check_verf();
2N/Astatic bool_t rpc_gss_refresh_svc_cred();
2N/Astatic bool_t set_response_verf();
2N/Astatic void retrans_add(svc_rpc_gss_data *, uint32_t,
2N/A rpc_gss_init_res *);
2N/Astatic void retrans_del(struct _svc_rpc_gss_data *);
2N/A
2N/A
2N/A/*
2N/A * server side wrap/unwrap routines
2N/A */
2N/Astruct svc_auth_ops svc_rpc_gss_ops = {
2N/A svc_rpc_gss_wrap,
2N/A svc_rpc_gss_unwrap,
2N/A};
2N/A
2N/A/*
2N/A * Fetch server side authentication structure.
2N/A */
2N/Aextern SVCAUTH *__svc_get_svcauth();
2N/A
2N/A/*
2N/A * Cleanup routine for destroying context, called after service
2N/A * procedure is executed, for MT safeness.
2N/A */
2N/Aextern void *__svc_set_proc_cleanup_cb();
2N/Astatic void (*old_cleanup_cb)() = NULL;
2N/Astatic bool_t cleanup_cb_set = FALSE;
2N/A
2N/Astatic void
2N/Actx_cleanup(xprt)
2N/A SVCXPRT *xprt;
2N/A{
2N/A svc_rpc_gss_data *cl;
2N/A SVCAUTH *svcauth;
2N/A
2N/A if (old_cleanup_cb != NULL)
2N/A (*old_cleanup_cb)(xprt);
2N/A
2N/A /*
2N/A * First check if current context needs to be cleaned up.
2N/A */
2N/A svcauth = __svc_get_svcauth(xprt);
2N/A /*LINTED*/
2N/A if ((cl = (svc_rpc_gss_data *)svcauth->svc_ah_private) != NULL) {
2N/A mutex_lock(&cl->clm);
2N/A if (--cl->ref_cnt == 0 && cl->stale) {
2N/A mutex_unlock(&cl->clm);
2N/A mutex_lock(&ctx_mutex);
2N/A destroy_client(cl);
2N/A mutex_unlock(&ctx_mutex);
2N/A } else
2N/A mutex_unlock(&cl->clm);
2N/A }
2N/A
2N/A /*
2N/A * Check for other expired contexts.
2N/A */
2N/A if ((time(0) - last_swept) > sweep_interval) {
2N/A mutex_lock(&ctx_mutex);
2N/A /*
2N/A * Check again, in case some other thread got in.
2N/A */
2N/A if ((time(0) - last_swept) > sweep_interval)
2N/A sweep_clients();
2N/A mutex_unlock(&ctx_mutex);
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Set server parameters.
2N/A */
2N/Avoid
2N/A__rpc_gss_set_server_parms(init_cred_lifetime, max_cred_lifetime, cache_size)
2N/A int init_cred_lifetime;
2N/A int max_cred_lifetime;
2N/A int cache_size;
2N/A{
2N/A /*
2N/A * Ignore parameters unless greater than zero.
2N/A */
2N/A mutex_lock(&ctx_mutex);
2N/A if (cache_size > 0)
2N/A max_gss_contexts = cache_size;
2N/A if (max_cred_lifetime > 0)
2N/A max_lifetime = (uint_t)max_cred_lifetime;
2N/A if (init_cred_lifetime > 0)
2N/A init_lifetime = init_cred_lifetime;
2N/A mutex_unlock(&ctx_mutex);
2N/A}
2N/A
2N/A/*
2N/A * Shift the array arr of length arrlen right by nbits bits.
2N/A */
2N/Astatic void
2N/Ashift_bits(arr, arrlen, nbits)
2N/A uint_t *arr;
2N/A int arrlen;
2N/A int nbits;
2N/A{
2N/A int i, j;
2N/A uint_t lo, hi;
2N/A
2N/A /*
2N/A * If the number of bits to be shifted exceeds SEQ_WIN, just
2N/A * zero out the array.
2N/A */
2N/A if (nbits < SEQ_WIN) {
2N/A for (i = 0; i < nbits; i++) {
2N/A hi = 0;
2N/A for (j = 0; j < arrlen; j++) {
2N/A lo = arr[j] & SEQ_LO_BIT;
2N/A arr[j] >>= 1;
2N/A if (hi)
2N/A arr[j] |= SEQ_HI_BIT;
2N/A hi = lo;
2N/A }
2N/A }
2N/A } else {
2N/A for (j = 0; j < arrlen; j++)
2N/A arr[j] = 0;
2N/A }
2N/A}
2N/A
2N/A/*
2N/A * Check that the received sequence number seq_num is valid.
2N/A */
2N/Astatic bool_t
2N/Acheck_seq(cl, seq_num, kill_context)
2N/A svc_rpc_gss_data *cl;
2N/A uint_t seq_num;
2N/A bool_t *kill_context;
2N/A{
2N/A int i, j;
2N/A uint_t bit;
2N/A
2N/A /*
2N/A * If it exceeds the maximum, kill context.
2N/A */
2N/A if (seq_num >= SEQ_MAX) {
2N/A *kill_context = TRUE;
2N/A return (FALSE);
2N/A }
2N/A
2N/A /*
2N/A * If greater than the last seen sequence number, just shift
2N/A * the sequence window so that it starts at the new sequence
2N/A * number and extends downwards by SEQ_WIN.
2N/A */
2N/A if (seq_num > cl->seq_num) {
2N/A shift_bits(cl->seq_bits, SEQ_ARR_SIZE, seq_num - cl->seq_num);
2N/A cl->seq_bits[0] |= SEQ_HI_BIT;
2N/A cl->seq_num = seq_num;
2N/A return (TRUE);
2N/A }
2N/A
2N/A /*
2N/A * If it is outside the sequence window, return failure.
2N/A */
2N/A i = cl->seq_num - seq_num;
2N/A if (i >= SEQ_WIN)
2N/A return (FALSE);
2N/A
2N/A /*
2N/A * If within sequence window, set the bit corresponding to it
2N/A * if not already seen; if already seen, return failure.
2N/A */
2N/A j = SEQ_MASK - (i & SEQ_MASK);
2N/A bit = j > 0 ? (1 << j) : 1;
2N/A i >>= DIV_BY_32;
2N/A if (cl->seq_bits[i] & bit)
2N/A return (FALSE);
2N/A cl->seq_bits[i] |= bit;
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Convert a name in gss exported type to rpc_gss_principal_t type.
2N/A */
2N/Astatic bool_t
2N/A__rpc_gss_make_principal(principal, name)
2N/A rpc_gss_principal_t *principal;
2N/A gss_buffer_desc *name;
2N/A{
2N/A int plen;
2N/A char *s;
2N/A
2N/A plen = RNDUP(name->length) + sizeof (int);
2N/A (*principal) = (rpc_gss_principal_t)malloc(plen);
2N/A if ((*principal) == NULL)
2N/A return (FALSE);
2N/A bzero((caddr_t)(*principal), plen);
2N/A (*principal)->len = RNDUP(name->length);
2N/A s = (*principal)->name;
2N/A memcpy(s, name->value, name->length);
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Convert a name in internal form to the exported type.
2N/A */
2N/Astatic bool_t
2N/Aset_client_principal(g_name, r_name)
2N/A gss_name_t g_name;
2N/A rpc_gss_principal_t *r_name;
2N/A{
2N/A gss_buffer_desc name;
2N/A OM_uint32 major, minor;
2N/A bool_t ret = FALSE;
2N/A
2N/A major = gss_export_name(&minor, g_name, &name);
2N/A if (major != GSS_S_COMPLETE)
2N/A return (FALSE);
2N/A ret = __rpc_gss_make_principal(r_name, &name);
2N/A (void) gss_release_buffer(&minor, &name);
2N/A return (ret);
2N/A}
2N/A
2N/A/*
2N/A * Set server callback.
2N/A */
2N/Abool_t
2N/A__rpc_gss_set_callback(cb)
2N/A rpc_gss_callback_t *cb;
2N/A{
2N/A cblist_t *cbl;
2N/A
2N/A if (cb->callback == NULL)
2N/A return (FALSE);
2N/A if ((cbl = (cblist_t *)malloc(sizeof (*cbl))) == NULL)
2N/A return (FALSE);
2N/A cbl->cb = *cb;
2N/A mutex_lock(&cb_mutex);
2N/A cbl->next = cblist;
2N/A cblist = cbl;
2N/A mutex_unlock(&cb_mutex);
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Locate callback (if specified) and call server. Release any
2N/A * delegated credentials unless passed to server and the server
2N/A * accepts the context. If a callback is not specified, accept
2N/A * the incoming context.
2N/A */
2N/Astatic bool_t
2N/Ado_callback(req, client_data)
2N/A struct svc_req *req;
2N/A svc_rpc_gss_data *client_data;
2N/A{
2N/A cblist_t *cbl;
2N/A bool_t ret = TRUE, found = FALSE;
2N/A rpc_gss_lock_t lock;
2N/A OM_uint32 minor;
2N/A
2N/A mutex_lock(&cb_mutex);
2N/A for (cbl = cblist; cbl != NULL; cbl = cbl->next) {
2N/A if (req->rq_prog != cbl->cb.program ||
2N/A req->rq_vers != cbl->cb.version)
2N/A continue;
2N/A found = TRUE;
2N/A lock.locked = FALSE;
2N/A lock.raw_cred = &client_data->raw_cred;
2N/A ret = (*cbl->cb.callback)(req, client_data->deleg,
2N/A client_data->context, &lock, &client_data->cookie);
2N/A if (ret) {
2N/A client_data->locked = lock.locked;
2N/A client_data->deleg = GSS_C_NO_CREDENTIAL;
2N/A }
2N/A break;
2N/A }
2N/A if (!found) {
2N/A if (client_data->deleg != GSS_C_NO_CREDENTIAL) {
2N/A (void) gss_release_cred(&minor, &client_data->deleg);
2N/A client_data->deleg = GSS_C_NO_CREDENTIAL;
2N/A }
2N/A }
2N/A mutex_unlock(&cb_mutex);
2N/A return (ret);
2N/A}
2N/A
2N/A/*
2N/A * Return caller credentials.
2N/A */
2N/Abool_t
2N/A__rpc_gss_getcred(req, rcred, ucred, cookie)
2N/A struct svc_req *req;
2N/A rpc_gss_rawcred_t **rcred;
2N/A rpc_gss_ucred_t **ucred;
2N/A void **cookie;
2N/A{
2N/A SVCAUTH *svcauth;
2N/A svc_rpc_gss_data *client_data;
2N/A svc_rpc_gss_parms_t *gss_parms;
2N/A gss_OID oid;
2N/A OM_uint32 status;
2N/A int len = 0;
2N/A struct timeval now;
2N/A
2N/A svcauth = __svc_get_svcauth(req->rq_xprt);
2N/A /*LINTED*/
2N/A client_data = (svc_rpc_gss_data *)svcauth->svc_ah_private;
2N/A gss_parms = &svcauth->svc_gss_parms;
2N/A
2N/A mutex_lock(&client_data->clm);
2N/A
2N/A if (rcred != NULL) {
2N/A svcauth->raw_cred = client_data->raw_cred;
2N/A svcauth->raw_cred.service = gss_parms->service;
2N/A svcauth->raw_cred.qop = __rpc_gss_num_to_qop(
2N/A svcauth->raw_cred.mechanism, gss_parms->qop_rcvd);
2N/A *rcred = &svcauth->raw_cred;
2N/A }
2N/A if (ucred != NULL) {
2N/A if (!client_data->u_cred_set) {
2N/A /*
2N/A * Double check making sure ucred is not set
2N/A * after acquiring the lock.
2N/A */
2N/A if (!client_data->u_cred_set) {
2N/A if (!__rpc_gss_mech_to_oid(
2N/A (*rcred)->mechanism, &oid)) {
2N/A fprintf(stderr, dgettext(TEXT_DOMAIN,
2N/A "mech_to_oid failed in getcred.\n"));
2N/A *ucred = NULL;
2N/A } else {
2N/A status = gsscred_name_to_unix_cred(
2N/A client_data->client_name, oid,
2N/A &client_data->u_cred.uid,
2N/A &client_data->u_cred.gid,
2N/A &client_data->u_cred.gidlist,
2N/A &len);
2N/A if (status == GSS_S_COMPLETE) {
2N/A client_data->u_cred_set = TRUE;
2N/A client_data->u_cred.gidlen =
2N/A (short)len;
2N/A gettimeofday(&now,
2N/A (struct timezone *)NULL);
2N/A client_data->time_secs_set =
2N/A now.tv_sec;
2N/A *ucred = &client_data->u_cred;
2N/A } else
2N/A *ucred = NULL;
2N/A }
2N/A }
2N/A } else {
2N/A /*
2N/A * gid's already set;
2N/A * check if they have expired.
2N/A */
2N/A gettimeofday(&now, (struct timezone *)NULL);
2N/A if ((now.tv_sec - client_data->time_secs_set)
2N/A > gid_timeout) {
2N/A /* Refresh gid's */
2N/A status = gss_get_group_info(
2N/A client_data->u_cred.uid,
2N/A &client_data->u_cred.gid,
2N/A &client_data->u_cred.gidlist,
2N/A &len);
2N/A if (status == GSS_S_COMPLETE) {
2N/A client_data->u_cred.gidlen =
2N/A (short)len;
2N/A gettimeofday(&now,
2N/A (struct timezone *)NULL);
2N/A client_data->time_secs_set = now.tv_sec;
2N/A *ucred = &client_data->u_cred;
2N/A } else {
2N/A client_data->u_cred_set = FALSE;
2N/A *ucred = NULL;
2N/A }
2N/A }
2N/A else
2N/A *ucred = &client_data->u_cred;
2N/A }
2N/A }
2N/A if (cookie != NULL)
2N/A *cookie = client_data->cookie;
2N/A
2N/A mutex_unlock(&client_data->clm);
2N/A
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Server side authentication for RPCSEC_GSS.
2N/A */
2N/A
2N/Aenum auth_stat
2N/A__svcrpcsec_gss(rqst, msg, no_dispatch)
2N/A struct svc_req *rqst;
2N/A struct rpc_msg *msg;
2N/A bool_t *no_dispatch;
2N/A{
2N/A XDR xdrs;
2N/A rpc_gss_creds creds;
2N/A rpc_gss_init_arg call_arg;
2N/A rpc_gss_init_res call_res, *retrans_result;
2N/A gss_buffer_desc output_token;
2N/A OM_uint32 gssstat, minor_stat, time_rec, ret_flags;
2N/A struct opaque_auth *cred;
2N/A svc_rpc_gss_data *client_data;
2N/A int ret;
2N/A svc_creds_list_t *sc;
2N/A SVCAUTH *svcauth;
2N/A svc_rpc_gss_parms_t *gss_parms;
2N/A gss_OID mech_type = GSS_C_NULL_OID;
2N/A
2N/A /*
2N/A * Initialize response verifier to NULL verifier. If
2N/A * necessary, this will be changed later.
2N/A */
2N/A rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NONE;
2N/A rqst->rq_xprt->xp_verf.oa_base = NULL;
2N/A rqst->rq_xprt->xp_verf.oa_length = 0;
2N/A /*
2N/A * Need to null out results to start with.
2N/A */
2N/A memset((char *)&call_res, 0, sizeof (call_res));
2N/A
2N/A /*
2N/A * Pull out and check credential and verifier.
2N/A */
2N/A cred = &msg->rm_call.cb_cred;
2N/A if (cred->oa_length == 0) {
2N/A return (AUTH_BADCRED);
2N/A }
2N/A
2N/A xdrmem_create(&xdrs, cred->oa_base, cred->oa_length, XDR_DECODE);
2N/A
2N/A memset((char *)&creds, 0, sizeof (creds));
2N/A if (!__xdr_rpc_gss_creds(&xdrs, &creds)) {
2N/A XDR_DESTROY(&xdrs);
2N/A ret = AUTH_BADCRED;
2N/A goto error;
2N/A }
2N/A XDR_DESTROY(&xdrs);
2N/A
2N/A /*
2N/A * If this is a control message and proc is GSSAPI_INIT, then
2N/A * create a client handle for this client. Otherwise, look up
2N/A * the existing handle.
2N/A */
2N/A if (creds.gss_proc == RPCSEC_GSS_INIT) {
2N/A if (creds.ctx_handle.length != 0) {
2N/A ret = AUTH_BADCRED;
2N/A goto error;
2N/A }
2N/A if ((client_data = create_client()) == NULL) {
2N/A ret = AUTH_FAILED;
2N/A goto error;
2N/A }
2N/A } else {
2N/A /*
2N/A * Only verify values for service parameter when proc
2N/A * not RPCSEC_GSS_INIT or RPCSEC_GSS_CONTINUE_INIT.
2N/A * RFC2203 says contents for sequence and service args
2N/A * are undefined for creation procs.
2N/A *
2N/A * Note: only need to check for *CONTINUE_INIT here because
2N/A * if() clause already checked for RPCSEC_GSS_INIT
2N/A */
2N/A if (creds.gss_proc != RPCSEC_GSS_CONTINUE_INIT) {
2N/A switch (creds.service) {
2N/A case rpc_gss_svc_none:
2N/A case rpc_gss_svc_integrity:
2N/A case rpc_gss_svc_privacy:
2N/A break;
2N/A default:
2N/A ret = AUTH_BADCRED;
2N/A goto error;
2N/A }
2N/A }
2N/A if (creds.ctx_handle.length == 0) {
2N/A ret = AUTH_BADCRED;
2N/A goto error;
2N/A }
2N/A if ((client_data = get_client(&creds.ctx_handle)) == NULL) {
2N/A ret = RPCSEC_GSS_NOCRED;
2N/A goto error;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * lock the client data until it's safe; if it's already stale,
2N/A * no more processing is possible
2N/A */
2N/A mutex_lock(&client_data->clm);
2N/A if (client_data->stale) {
2N/A ret = RPCSEC_GSS_NOCRED;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * Any response we send will use ctx_handle, so set it now;
2N/A * also set seq_window since this won't change.
2N/A */
2N/A call_res.ctx_handle.length = sizeof (client_data->key);
2N/A call_res.ctx_handle.value = (char *)&client_data->key;
2N/A call_res.seq_window = SEQ_WIN;
2N/A
2N/A /*
2N/A * Set the appropriate wrap/unwrap routine for RPCSEC_GSS.
2N/A */
2N/A svcauth = __svc_get_svcauth(rqst->rq_xprt);
2N/A svcauth->svc_ah_ops = svc_rpc_gss_ops;
2N/A svcauth->svc_ah_private = (caddr_t)client_data;
2N/A
2N/A /*
2N/A * Keep copy of parameters we'll need for response, for the
2N/A * sake of reentrancy (we don't want to look in the context
2N/A * data because when we are sending a response, another
2N/A * request may have come in.
2N/A */
2N/A gss_parms = &svcauth->svc_gss_parms;
2N/A gss_parms->established = client_data->established;
2N/A gss_parms->service = creds.service;
2N/A gss_parms->qop_rcvd = (uint_t)client_data->qop;
2N/A gss_parms->context = (void *)client_data->context;
2N/A gss_parms->seq_num = creds.seq_num;
2N/A
2N/A if (!client_data->established) {
2N/A if (creds.gss_proc == RPCSEC_GSS_DATA) {
2N/A ret = RPCSEC_GSS_FAILED;
2N/A client_data->stale = TRUE;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * If the context is not established, then only GSSAPI_INIT
2N/A * and _CONTINUE requests are valid.
2N/A */
2N/A if (creds.gss_proc != RPCSEC_GSS_INIT && creds.gss_proc !=
2N/A RPCSEC_GSS_CONTINUE_INIT) {
2N/A ret = RPCSEC_GSS_FAILED;
2N/A client_data->stale = TRUE;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * call is for us, deserialize arguments
2N/A */
2N/A memset(&call_arg, 0, sizeof (call_arg));
2N/A if (!svc_getargs(rqst->rq_xprt, __xdr_rpc_gss_init_arg,
2N/A (caddr_t)&call_arg)) {
2N/A ret = RPCSEC_GSS_FAILED;
2N/A client_data->stale = TRUE;
2N/A goto error2;
2N/A }
2N/A
2N/A gssstat = GSS_S_FAILURE;
2N/A minor_stat = 0;
2N/A rw_rdlock(&cred_lock);
2N/A /*
2N/A * set next sc to point to the server cred
2N/A * if the client_data contains server_creds
2N/A */
2N/A for (sc = svc_creds_list; sc != NULL; sc = sc->next) {
2N/A if (rqst->rq_prog != sc->program ||
2N/A rqst->rq_vers != sc->version)
2N/A continue;
2N/A
2N/A mutex_lock(&sc->refresh_mutex);
2N/A gssstat = gss_accept_sec_context(&minor_stat,
2N/A &client_data->context,
2N/A sc->cred,
2N/A &call_arg,
2N/A GSS_C_NO_CHANNEL_BINDINGS,
2N/A &client_data->client_name,
2N/A &mech_type,
2N/A &output_token,
2N/A &ret_flags,
2N/A &time_rec,
2N/A NULL);
2N/A
2N/A if (gssstat == GSS_S_CREDENTIALS_EXPIRED) {
2N/A if (rpc_gss_refresh_svc_cred(sc)) {
2N/A gssstat = gss_accept_sec_context(
2N/A &minor_stat,
2N/A &client_data->context,
2N/A sc->cred,
2N/A &call_arg,
2N/A GSS_C_NO_CHANNEL_BINDINGS,
2N/A &client_data->client_name,
2N/A &mech_type,
2N/A &output_token,
2N/A &ret_flags,
2N/A &time_rec,
2N/A NULL);
2N/A mutex_unlock(&sc->refresh_mutex);
2N/A
2N/A } else {
2N/A mutex_unlock(&sc->refresh_mutex);
2N/A gssstat = GSS_S_NO_CRED;
2N/A break;
2N/A }
2N/A
2N/A } else
2N/A mutex_unlock(&sc->refresh_mutex);
2N/A
2N/A if (gssstat == GSS_S_COMPLETE) {
2N/A /*
2N/A * Server_creds was right - set it. Also
2N/A * set the raw and unix credentials at this
2N/A * point. This saves a lot of computation
2N/A * later when credentials are retrieved.
2N/A */
2N/A /*
2N/A * XXX server_creds will prob be stale
2N/A * after rpc_gss_refresh_svc_cred(), but
2N/A * it appears not to ever be referenced
2N/A * anyways.
2N/A */
2N/A mutex_lock(&sc->refresh_mutex);
2N/A client_data->server_creds = sc->cred;
2N/A client_data->raw_cred.version = creds.version;
2N/A client_data->raw_cred.service = creds.service;
2N/A client_data->raw_cred.svc_principal =
2N/A sc->server_name;
2N/A mutex_unlock(&sc->refresh_mutex);
2N/A
2N/A if ((client_data->raw_cred.mechanism
2N/A = __rpc_gss_oid_to_mech(mech_type))
2N/A == NULL) {
2N/A gssstat = GSS_S_FAILURE;
2N/A (void) gss_release_buffer(&minor_stat,
2N/A &output_token);
2N/A } else if (!set_client_principal(client_data->
2N/A client_name, &client_data->
2N/A raw_cred.client_principal)) {
2N/A gssstat = GSS_S_FAILURE;
2N/A (void) gss_release_buffer(&minor_stat,
2N/A &output_token);
2N/A }
2N/A break;
2N/A }
2N/A
2N/A if (gssstat == GSS_S_CONTINUE_NEEDED) {
2N/A /*
2N/A * XXX server_creds will prob be stale
2N/A * after rpc_gss_refresh_svc_cred(), but
2N/A * it appears not to ever be referenced
2N/A * anyways.
2N/A */
2N/A mutex_lock(&sc->refresh_mutex);
2N/A client_data->server_creds = sc->cred;
2N/A mutex_unlock(&sc->refresh_mutex);
2N/A break;
2N/A }
2N/A
2N/A /* Make sure to free output_token in case of failure. */
2N/A if (output_token.length != 0) {
2N/A (void) gss_release_buffer(&minor_stat,
2N/A &output_token);
2N/A }
2N/A
2N/A }
2N/A rw_unlock(&cred_lock);
2N/A
2N/A call_res.gss_major = gssstat;
2N/A call_res.gss_minor = minor_stat;
2N/A
2N/A xdr_free(__xdr_rpc_gss_init_arg, (caddr_t)&call_arg);
2N/A
2N/A if (gssstat != GSS_S_COMPLETE &&
2N/A gssstat != GSS_S_CONTINUE_NEEDED) {
2N/A /*
2N/A * We have a failure - send response and delete
2N/A * the context. Don't dispatch. Set ctx_handle
2N/A * to NULL and seq_window to 0.
2N/A */
2N/A call_res.ctx_handle.length = 0;
2N/A call_res.ctx_handle.value = NULL;
2N/A call_res.seq_window = 0;
2N/A
2N/A svc_sendreply(rqst->rq_xprt, __xdr_rpc_gss_init_res,
2N/A (caddr_t)&call_res);
2N/A *no_dispatch = TRUE;
2N/A ret = AUTH_OK;
2N/A client_data->stale = TRUE;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * This step succeeded. Send a response, along with
2N/A * a token if there's one. Don't dispatch.
2N/A */
2N/A if (output_token.length != 0) {
2N/A GSS_COPY_BUFFER(call_res.token, output_token);
2N/A }
2N/A
2N/A /*
2N/A * set response verifier: checksum of SEQ_WIN
2N/A */
2N/A if (gssstat == GSS_S_COMPLETE) {
2N/A if (!set_response_verf(rqst, msg, client_data,
2N/A (uint_t)SEQ_WIN)) {
2N/A ret = RPCSEC_GSS_FAILED;
2N/A client_data->stale = TRUE;
2N/A (void) gss_release_buffer(&minor_stat,
2N/A &output_token);
2N/A goto error2;
2N/A }
2N/A }
2N/A
2N/A svc_sendreply(rqst->rq_xprt, __xdr_rpc_gss_init_res,
2N/A (caddr_t)&call_res);
2N/A /*
2N/A * Cache last response in case it is lost and the client
2N/A * retries on an established context.
2N/A */
2N/A (void) retrans_add(client_data, msg->rm_xid, &call_res);
2N/A *no_dispatch = TRUE;
2N/A (void) gss_release_buffer(&minor_stat, &output_token);
2N/A
2N/A /*
2N/A * If appropriate, set established to TRUE *after* sending
2N/A * response (otherwise, the client will receive the final
2N/A * token encrypted)
2N/A */
2N/A if (gssstat == GSS_S_COMPLETE) {
2N/A /*
2N/A * Context is established. Set expiry time for
2N/A * context (the minimum of time_rec and max_lifetime).
2N/A */
2N/A client_data->seq_num = 1;
2N/A if (time_rec == GSS_C_INDEFINITE) {
2N/A if (max_lifetime != GSS_C_INDEFINITE)
2N/A client_data->expiration =
2N/A max_lifetime + time(0);
2N/A else
2N/A client_data->expiration =
2N/A GSS_C_INDEFINITE;
2N/A } else if (max_lifetime == GSS_C_INDEFINITE ||
2N/A max_lifetime > time_rec)
2N/A client_data->expiration = time_rec + time(0);
2N/A else
2N/A client_data->expiration = max_lifetime +
2N/A time(0);
2N/A client_data->established = TRUE;
2N/A }
2N/A
2N/A } else {
2N/A if ((creds.gss_proc != RPCSEC_GSS_DATA) &&
2N/A (creds.gss_proc != RPCSEC_GSS_DESTROY)) {
2N/A
2N/A switch (creds.gss_proc) {
2N/A
2N/A case RPCSEC_GSS_CONTINUE_INIT:
2N/A /*
2N/A * This is an established context. Continue to
2N/A * satisfy retried continue init requests out of
2N/A * the retransmit cache. Throw away any that don't
2N/A * have a matching xid or the cach is empty.
2N/A * Delete the retransmit cache once the client sends
2N/A * a data request.
2N/A */
2N/A if (client_data->retrans_data &&
2N/A (client_data->retrans_data->xid == msg->rm_xid)) {
2N/A
2N/A retrans_result = &client_data->retrans_data->result;
2N/A if (set_response_verf(rqst, msg, client_data,
2N/A (uint_t)retrans_result->seq_window)) {
2N/A
2N/A gss_parms->established = FALSE;
2N/A svc_sendreply(rqst->rq_xprt,
2N/A __xdr_rpc_gss_init_res,
2N/A (caddr_t)retrans_result);
2N/A *no_dispatch = TRUE;
2N/A goto success;
2N/A }
2N/A }
2N/A /* fall thru to default */
2N/A
2N/A default:
2N/A syslog(LOG_ERR, "_svcrpcsec_gss: non-data request "
2N/A "on an established context");
2N/A ret = AUTH_FAILED;
2N/A goto error2;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * Once the context is established and there is no more
2N/A * retransmission of last continue init request, it is safe
2N/A * to delete the retransmit cache entry.
2N/A */
2N/A if (client_data->retrans_data)
2N/A retrans_del(client_data);
2N/A
2N/A /*
2N/A * Context is already established. Check verifier, and
2N/A * note parameters we will need for response in gss_parms.
2N/A */
2N/A if (!check_verf(msg, client_data->context,
2N/A &gss_parms->qop_rcvd)) {
2N/A ret = RPCSEC_GSS_NOCRED;
2N/A goto error2;
2N/A }
2N/A /*
2N/A * Check and invoke callback if necessary.
2N/A */
2N/A if (!client_data->done_docallback) {
2N/A client_data->done_docallback = TRUE;
2N/A client_data->qop = gss_parms->qop_rcvd;
2N/A client_data->raw_cred.qop = __rpc_gss_num_to_qop(
2N/A client_data->raw_cred.mechanism,
2N/A gss_parms->qop_rcvd);
2N/A client_data->raw_cred.service = creds.service;
2N/A if (!do_callback(rqst, client_data)) {
2N/A ret = AUTH_FAILED;
2N/A client_data->stale = TRUE;
2N/A goto error2;
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * If the context was locked, make sure that the client
2N/A * has not changed QOP.
2N/A */
2N/A if (client_data->locked &&
2N/A gss_parms->qop_rcvd != client_data->qop) {
2N/A ret = AUTH_BADVERF;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * Validate sequence number.
2N/A */
2N/A if (!check_seq(client_data, creds.seq_num,
2N/A &client_data->stale)) {
2N/A if (client_data->stale)
2N/A ret = RPCSEC_GSS_FAILED;
2N/A else {
2N/A /*
2N/A * Operational error, drop packet silently.
2N/A * The client will recover after timing out,
2N/A * assuming this is a client error and not
2N/A * a relpay attack. Don't dispatch.
2N/A */
2N/A ret = AUTH_OK;
2N/A *no_dispatch = TRUE;
2N/A }
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * set response verifier
2N/A */
2N/A if (!set_response_verf(rqst, msg, client_data, creds.seq_num)) {
2N/A ret = RPCSEC_GSS_FAILED;
2N/A client_data->stale = TRUE;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * If this is a control message RPCSEC_GSS_DESTROY, process
2N/A * the call; otherwise, return AUTH_OK so it will be
2N/A * dispatched to the application server.
2N/A */
2N/A if (creds.gss_proc == RPCSEC_GSS_DESTROY) {
2N/A svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
2N/A *no_dispatch = TRUE;
2N/A client_data->stale = TRUE;
2N/A
2N/A } else {
2N/A /*
2N/A * This should be an RPCSEC_GSS_DATA request.
2N/A * If context is locked, make sure that the client
2N/A * has not changed the security service.
2N/A */
2N/A if (client_data->locked &&
2N/A client_data->raw_cred.service != creds.service) {
2N/A ret = AUTH_FAILED;
2N/A goto error2;
2N/A }
2N/A
2N/A /*
2N/A * Set client credentials to raw credential
2N/A * structure in context. This is okay, since
2N/A * this will not change during the lifetime of
2N/A * the context (so it's MT safe).
2N/A */
2N/A rqst->rq_clntcred = (char *)&client_data->raw_cred;
2N/A }
2N/A }
2N/A
2N/Asuccess:
2N/A /*
2N/A * Success.
2N/A */
2N/A if (creds.ctx_handle.length != 0)
2N/A xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
2N/A mutex_unlock(&client_data->clm);
2N/A return (AUTH_OK);
2N/Aerror2:
2N/A mutex_unlock(&client_data->clm);
2N/Aerror:
2N/A /*
2N/A * Failure.
2N/A */
2N/A if (creds.ctx_handle.length != 0)
2N/A xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
2N/A return (ret);
2N/A}
2N/A
2N/A/*
2N/A * Check verifier. The verifier is the checksum of the RPC header
2N/A * upto and including the credentials field.
2N/A */
2N/Astatic bool_t
2N/Acheck_verf(msg, context, qop_state)
2N/A struct rpc_msg *msg;
2N/A gss_ctx_id_t context;
2N/A int *qop_state;
2N/A{
2N/A int *buf, *tmp;
2N/A int hdr[32];
2N/A struct opaque_auth *oa;
2N/A int len;
2N/A gss_buffer_desc msg_buf;
2N/A gss_buffer_desc tok_buf;
2N/A OM_uint32 gssstat, minor_stat;
2N/A
2N/A /*
2N/A * We have to reconstruct the RPC header from the previously
2N/A * parsed information, since we haven't kept the header intact.
2N/A */
2N/A
2N/A oa = &msg->rm_call.cb_cred;
2N/A if (oa->oa_length > MAX_AUTH_BYTES)
2N/A return (FALSE);
2N/A
2N/A /* 8 XDR units from the IXDR macro calls. */
2N/A if (sizeof (hdr) < (8 * BYTES_PER_XDR_UNIT +
2N/A RNDUP(oa->oa_length)))
2N/A return (FALSE);
2N/A buf = hdr;
2N/A
2N/A IXDR_PUT_U_INT32(buf, msg->rm_xid);
2N/A IXDR_PUT_ENUM(buf, msg->rm_direction);
2N/A IXDR_PUT_U_INT32(buf, msg->rm_call.cb_rpcvers);
2N/A IXDR_PUT_U_INT32(buf, msg->rm_call.cb_prog);
2N/A IXDR_PUT_U_INT32(buf, msg->rm_call.cb_vers);
2N/A IXDR_PUT_U_INT32(buf, msg->rm_call.cb_proc);
2N/A IXDR_PUT_ENUM(buf, oa->oa_flavor);
2N/A IXDR_PUT_U_INT32(buf, oa->oa_length);
2N/A if (oa->oa_length) {
2N/A len = RNDUP(oa->oa_length);
2N/A tmp = buf;
2N/A buf += len / sizeof (int);
2N/A *(buf - 1) = 0;
2N/A (void) memcpy((caddr_t)tmp, oa->oa_base, oa->oa_length);
2N/A }
2N/A len = ((char *)buf) - (char *)hdr;
2N/A msg_buf.length = len;
2N/A msg_buf.value = (char *)hdr;
2N/A oa = &msg->rm_call.cb_verf;
2N/A tok_buf.length = oa->oa_length;
2N/A tok_buf.value = oa->oa_base;
2N/A
2N/A gssstat = gss_verify(&minor_stat, context, &msg_buf, &tok_buf,
2N/A qop_state);
2N/A if (gssstat != GSS_S_COMPLETE)
2N/A return (FALSE);
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Set response verifier. This is the checksum of the given number.
2N/A * (e.g. sequence number or sequence window)
2N/A */
2N/Astatic bool_t
2N/Aset_response_verf(rqst, msg, cl, num)
2N/A struct svc_req *rqst;
2N/A struct rpc_msg *msg;
2N/A svc_rpc_gss_data *cl;
2N/A uint_t num;
2N/A{
2N/A OM_uint32 minor;
2N/A gss_buffer_desc in_buf, out_buf;
2N/A uint_t num_net;
2N/A
2N/A num_net = (uint_t)htonl(num);
2N/A in_buf.length = sizeof (num);
2N/A in_buf.value = (char *)&num_net;
2N/A if (gss_sign(&minor, cl->context, cl->qop, &in_buf,
2N/A &out_buf) != GSS_S_COMPLETE)
2N/A return (FALSE);
2N/A rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
2N/A rqst->rq_xprt->xp_verf.oa_base = msg->rm_call.cb_verf.oa_base;
2N/A rqst->rq_xprt->xp_verf.oa_length = out_buf.length;
2N/A memcpy(rqst->rq_xprt->xp_verf.oa_base, out_buf.value,
2N/A out_buf.length);
2N/A (void) gss_release_buffer(&minor, &out_buf);
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Create client context.
2N/A */
2N/Astatic svc_rpc_gss_data *
2N/Acreate_client()
2N/A{
2N/A svc_rpc_gss_data *client_data;
2N/A static uint_t key = 1;
2N/A
2N/A client_data = (svc_rpc_gss_data *) malloc(sizeof (*client_data));
2N/A if (client_data == NULL)
2N/A return (NULL);
2N/A memset((char *)client_data, 0, sizeof (*client_data));
2N/A
2N/A /*
2N/A * set up client data structure
2N/A */
2N/A client_data->established = FALSE;
2N/A client_data->locked = FALSE;
2N/A client_data->u_cred_set = FALSE;
2N/A client_data->context = GSS_C_NO_CONTEXT;
2N/A client_data->expiration = init_lifetime + time(0);
2N/A client_data->ref_cnt = 1;
2N/A client_data->qop = GSS_C_QOP_DEFAULT;
2N/A client_data->done_docallback = FALSE;
2N/A client_data->stale = FALSE;
2N/A client_data->time_secs_set = 0;
2N/A client_data->retrans_data = NULL;
2N/A mutex_init(&client_data->clm, USYNC_THREAD, NULL);
2N/A /*
2N/A * Check totals. If we've hit the limit, we destroy a context
2N/A * based on LRU method.
2N/A */
2N/A mutex_lock(&ctx_mutex);
2N/A if (num_gss_contexts >= max_gss_contexts) {
2N/A /*
2N/A * now try on LRU basis
2N/A */
2N/A drop_lru_client();
2N/A if (num_gss_contexts >= max_gss_contexts) {
2N/A mutex_unlock(&ctx_mutex);
2N/A free((char *)client_data);
2N/A return (NULL);
2N/A }
2N/A }
2N/A
2N/A /*
2N/A * The client context handle is a 32-bit key (unsigned int).
2N/A * The key is incremented until there is no duplicate for it.
2N/A */
2N/A for (;;) {
2N/A client_data->key = key++;
2N/A if (find_client(client_data->key) == NULL) {
2N/A insert_client(client_data);
2N/A /*
2N/A * Set cleanup callback if we haven't.
2N/A */
2N/A if (!cleanup_cb_set) {
2N/A old_cleanup_cb =
2N/A (void (*)()) __svc_set_proc_cleanup_cb(
2N/A (void *)ctx_cleanup);
2N/A cleanup_cb_set = TRUE;
2N/A }
2N/A mutex_unlock(&ctx_mutex);
2N/A return (client_data);
2N/A }
2N/A }
2N/A /*NOTREACHED*/
2N/A}
2N/A
2N/A/*
2N/A * Insert client context into hash list and LRU list.
2N/A */
2N/Astatic void
2N/Ainsert_client(client_data)
2N/A svc_rpc_gss_data *client_data;
2N/A{
2N/A svc_rpc_gss_data *cl;
2N/A int index = (client_data->key & HASHMASK);
2N/A
2N/A client_data->prev = NULL;
2N/A cl = clients[index];
2N/A if ((client_data->next = cl) != NULL)
2N/A cl->prev = client_data;
2N/A clients[index] = client_data;
2N/A
2N/A client_data->lru_prev = NULL;
2N/A if ((client_data->lru_next = lru_first) != NULL)
2N/A lru_first->lru_prev = client_data;
2N/A else
2N/A lru_last = client_data;
2N/A lru_first = client_data;
2N/A
2N/A num_gss_contexts++;
2N/A}
2N/A
2N/A/*
2N/A * Fetch a client, given the client context handle. Move it to the
2N/A * top of the LRU list since this is the most recently used context.
2N/A */
2N/Astatic svc_rpc_gss_data *
2N/Aget_client(ctx_handle)
2N/A gss_buffer_t ctx_handle;
2N/A{
2N/A uint_t key = *(uint_t *)ctx_handle->value;
2N/A svc_rpc_gss_data *cl;
2N/A
2N/A mutex_lock(&ctx_mutex);
2N/A if ((cl = find_client(key)) != NULL) {
2N/A mutex_lock(&cl->clm);
2N/A if (cl->stale) {
2N/A mutex_unlock(&cl->clm);
2N/A mutex_unlock(&ctx_mutex);
2N/A return (NULL);
2N/A }
2N/A cl->ref_cnt++;
2N/A mutex_unlock(&cl->clm);
2N/A if (cl != lru_first) {
2N/A cl->lru_prev->lru_next = cl->lru_next;
2N/A if (cl->lru_next != NULL)
2N/A cl->lru_next->lru_prev = cl->lru_prev;
2N/A else
2N/A lru_last = cl->lru_prev;
2N/A cl->lru_prev = NULL;
2N/A cl->lru_next = lru_first;
2N/A lru_first->lru_prev = cl;
2N/A lru_first = cl;
2N/A }
2N/A }
2N/A mutex_unlock(&ctx_mutex);
2N/A return (cl);
2N/A}
2N/A
2N/A/*
2N/A * Given the client context handle, find the context corresponding to it.
2N/A * Don't change its LRU state since it may not be used.
2N/A */
2N/Astatic svc_rpc_gss_data *
2N/Afind_client(key)
2N/A uint_t key;
2N/A{
2N/A int index = (key & HASHMASK);
2N/A svc_rpc_gss_data *cl;
2N/A
2N/A for (cl = clients[index]; cl != NULL; cl = cl->next) {
2N/A if (cl->key == key)
2N/A break;
2N/A }
2N/A return (cl);
2N/A}
2N/A
2N/A/*
2N/A * Destroy a client context.
2N/A */
2N/Astatic void
2N/Adestroy_client(client_data)
2N/A svc_rpc_gss_data *client_data;
2N/A{
2N/A OM_uint32 minor;
2N/A int index = (client_data->key & HASHMASK);
2N/A
2N/A /*
2N/A * remove from hash list
2N/A */
2N/A if (client_data->prev == NULL)
2N/A clients[index] = client_data->next;
2N/A else
2N/A client_data->prev->next = client_data->next;
2N/A if (client_data->next != NULL)
2N/A client_data->next->prev = client_data->prev;
2N/A
2N/A /*
2N/A * remove from LRU list
2N/A */
2N/A if (client_data->lru_prev == NULL)
2N/A lru_first = client_data->lru_next;
2N/A else
2N/A client_data->lru_prev->lru_next = client_data->lru_next;
2N/A if (client_data->lru_next != NULL)
2N/A client_data->lru_next->lru_prev = client_data->lru_prev;
2N/A else
2N/A lru_last = client_data->lru_prev;
2N/A
2N/A /*
2N/A * If there is a GSS context, clean up GSS state.
2N/A */
2N/A if (client_data->context != GSS_C_NO_CONTEXT) {
2N/A (void) gss_delete_sec_context(&minor, &client_data->context,
2N/A NULL);
2N/A if (client_data->client_name)
2N/A (void) gss_release_name(&minor, &client_data->client_name);
2N/A if (client_data->raw_cred.client_principal)
2N/A free((char *)client_data->raw_cred.client_principal);
2N/A if (client_data->u_cred.gidlist != NULL)
2N/A free((char *)client_data->u_cred.gidlist);
2N/A if (client_data->deleg != GSS_C_NO_CREDENTIAL)
2N/A (void) gss_release_cred(&minor, &client_data->deleg);
2N/A }
2N/A
2N/A if (client_data->retrans_data != NULL)
2N/A retrans_del(client_data);
2N/A
2N/A free(client_data);
2N/A num_gss_contexts--;
2N/A}
2N/A
2N/A/*
2N/A * Check for expired client contexts.
2N/A */
2N/Astatic void
2N/Asweep_clients()
2N/A{
2N/A svc_rpc_gss_data *cl, *next;
2N/A int index;
2N/A
2N/A for (index = 0; index < HASHMOD; index++) {
2N/A cl = clients[index];
2N/A while (cl) {
2N/A next = cl->next;
2N/A mutex_lock(&cl->clm);
2N/A if ((cl->expiration != GSS_C_INDEFINITE &&
2N/A cl->expiration <= time(0)) || cl->stale) {
2N/A cl->stale = TRUE;
2N/A if (cl->ref_cnt == 0) {
2N/A mutex_unlock(&cl->clm);
2N/A destroy_client(cl);
2N/A } else
2N/A mutex_unlock(&cl->clm);
2N/A } else
2N/A mutex_unlock(&cl->clm);
2N/A cl = next;
2N/A }
2N/A }
2N/A last_swept = time(0);
2N/A}
2N/A
2N/A/*
2N/A * Drop the least recently used client context, if possible.
2N/A */
2N/Astatic void
2N/Adrop_lru_client()
2N/A{
2N/A mutex_lock(&lru_last->clm);
2N/A lru_last->stale = TRUE;
2N/A mutex_unlock(&lru_last->clm);
2N/A if (lru_last->ref_cnt == 0)
2N/A destroy_client(lru_last);
2N/A else
2N/A sweep_clients();
2N/A}
2N/A
2N/A/*
2N/A * find service credentials
2N/A * return cred if found,
2N/A * other wise, NULL
2N/A */
2N/A
2N/Asvc_creds_list_t *
2N/Afind_svc_cred(char *service_name, uint_t program, uint_t version) {
2N/A
2N/A svc_creds_list_t *sc;
2N/A
2N/A if (!svc_creds_list)
2N/A return (NULL);
2N/A
2N/A for (sc = svc_creds_list; sc != NULL; sc = sc->next) {
2N/A if (program != sc->program || version != sc->version)
2N/A continue;
2N/A
2N/A if (strcmp(service_name, sc->server_name) != 0)
2N/A continue;
2N/A return (sc);
2N/A }
2N/A return (NULL);
2N/A}
2N/A
2N/A/*
2N/A * Set the server principal name.
2N/A */
2N/Abool_t
2N/A__rpc_gss_set_svc_name(server_name, mech, req_time, program, version)
2N/A char *server_name;
2N/A char *mech;
2N/A OM_uint32 req_time;
2N/A uint_t program;
2N/A uint_t version;
2N/A{
2N/A gss_name_t name;
2N/A svc_creds_list_t *svc_cred;
2N/A gss_OID mechanism;
2N/A gss_OID_set_desc oid_set_desc;
2N/A gss_OID_set oid_set;
2N/A OM_uint32 ret_time;
2N/A OM_uint32 major, minor;
2N/A gss_buffer_desc name_buf;
2N/A
2N/A if (!__rpc_gss_mech_to_oid(mech, &mechanism)) {
2N/A return (FALSE);
2N/A }
2N/A
2N/A name_buf.value = server_name;
2N/A name_buf.length = strlen(server_name);
2N/A major = gss_import_name(&minor, &name_buf,
2N/A (gss_OID) GSS_C_NT_HOSTBASED_SERVICE, &name);
2N/A if (major != GSS_S_COMPLETE) {
2N/A return (FALSE);
2N/A }
2N/A
2N/A /* Check if there is already an entry in the svc_creds_list. */
2N/A rw_wrlock(&cred_lock);
2N/A if (svc_cred = find_svc_cred(server_name, program, version)) {
2N/A
2N/A major = gss_add_cred(&minor, svc_cred->cred, name,
2N/A mechanism, GSS_C_ACCEPT,
2N/A 0, req_time, NULL,
2N/A &oid_set, NULL,
2N/A &ret_time);
2N/A (void) gss_release_name(&minor, &name);
2N/A if (major == GSS_S_COMPLETE) {
2N/A /*
2N/A * Successfully added the mech to the cred handle
2N/A * free the existing oid_set in svc_cred
2N/A */
2N/A gss_release_oid_set(&minor, &svc_cred->oid_set);
2N/A svc_cred->oid_set = oid_set;
2N/A rw_unlock(&cred_lock);
2N/A return (TRUE);
2N/A } else if (major == GSS_S_DUPLICATE_ELEMENT) {
2N/A rw_unlock(&cred_lock);
2N/A return (TRUE);
2N/A } else if (major == GSS_S_CREDENTIALS_EXPIRED) {
2N/A if (rpc_gss_refresh_svc_cred(svc_cred)) {
2N/A rw_unlock(&cred_lock);
2N/A return (TRUE);
2N/A } else {
2N/A rw_unlock(&cred_lock);
2N/A return (FALSE);
2N/A }
2N/A } else {
2N/A rw_unlock(&cred_lock);
2N/A return (FALSE);
2N/A }
2N/A } else {
2N/A svc_cred = (svc_creds_list_t *)malloc(sizeof (*svc_cred));
2N/A if (svc_cred == NULL) {
2N/A (void) gss_release_name(&minor, &name);
2N/A rw_unlock(&cred_lock);
2N/A return (FALSE);
2N/A }
2N/A oid_set_desc.count = 1;
2N/A oid_set_desc.elements = mechanism;
2N/A major = gss_acquire_cred(&minor, name, req_time,
2N/A &oid_set_desc,
2N/A GSS_C_ACCEPT,
2N/A &svc_cred->cred,
2N/A &oid_set, &ret_time);
2N/A
2N/A if (major != GSS_S_COMPLETE) {
2N/A (void) gss_release_name(&minor, &name);
2N/A free(svc_cred);
2N/A rw_unlock(&cred_lock);
2N/A return (FALSE);
2N/A }
2N/A
2N/A svc_cred->name = name;
2N/A svc_cred->program = program;
2N/A svc_cred->version = version;
2N/A svc_cred->req_time = req_time;
2N/A svc_cred->oid_set = oid_set;
2N/A svc_cred->server_name = strdup(server_name);
2N/A if (svc_cred->server_name == NULL) {
2N/A (void) gss_release_name(&minor, &name);
2N/A free((char *)svc_cred);
2N/A rw_unlock(&cred_lock);
2N/A return (FALSE);
2N/A }
2N/A mutex_init(&svc_cred->refresh_mutex, USYNC_THREAD, NULL);
2N/A
2N/A svc_cred->next = svc_creds_list;
2N/A svc_creds_list = svc_cred;
2N/A svc_creds_count++;
2N/A rw_unlock(&cred_lock);
2N/A
2N/A return (TRUE);
2N/A }
2N/A}
2N/A/*
2N/A * Refresh server credentials.
2N/A */
2N/Astatic bool_t
2N/Arpc_gss_refresh_svc_cred(svc_cred)
2N/A svc_creds_list_t *svc_cred;
2N/A{
2N/A OM_uint32 major, minor;
2N/A gss_OID_set oid_set;
2N/A OM_uint32 ret_time;
2N/A
2N/A (void) gss_release_cred(&minor, &svc_cred->cred);
2N/A svc_cred->cred = GSS_C_NO_CREDENTIAL;
2N/A major = gss_acquire_cred(&minor, svc_cred->name, svc_cred->req_time,
2N/A svc_cred->oid_set, GSS_C_ACCEPT, &svc_cred->cred, &oid_set,
2N/A &ret_time);
2N/A if (major != GSS_S_COMPLETE) {
2N/A return (FALSE);
2N/A }
2N/A gss_release_oid_set(&minor, &svc_cred->oid_set);
2N/A svc_cred->oid_set = oid_set;
2N/A return (TRUE);
2N/A}
2N/A
2N/A/*
2N/A * Encrypt the serialized arguments from xdr_func applied to xdr_ptr
2N/A * and write the result to xdrs.
2N/A */
2N/Astatic bool_t
2N/Asvc_rpc_gss_wrap(auth, out_xdrs, xdr_func, xdr_ptr)
2N/A SVCAUTH *auth;
2N/A XDR *out_xdrs;
2N/A bool_t (*xdr_func)();
2N/A caddr_t xdr_ptr;
2N/A{
2N/A svc_rpc_gss_parms_t *gss_parms = &auth->svc_gss_parms;
2N/A
2N/A /*
2N/A * If context is not established, or if neither integrity nor
2N/A * privacy service is used, don't wrap - just XDR encode.
2N/A * Otherwise, wrap data using service and QOP parameters.
2N/A */
2N/A if (!gss_parms->established ||
2N/A gss_parms->service == rpc_gss_svc_none)
2N/A return ((*xdr_func)(out_xdrs, xdr_ptr));
2N/A
2N/A return (__rpc_gss_wrap_data(gss_parms->service,
2N/A (OM_uint32)gss_parms->qop_rcvd,
2N/A (gss_ctx_id_t)gss_parms->context,
2N/A gss_parms->seq_num,
2N/A out_xdrs, xdr_func, xdr_ptr));
2N/A}
2N/A
2N/A/*
2N/A * Decrypt the serialized arguments and XDR decode them.
2N/A */
2N/Astatic bool_t
2N/Asvc_rpc_gss_unwrap(auth, in_xdrs, xdr_func, xdr_ptr)
2N/A SVCAUTH *auth;
2N/A XDR *in_xdrs;
2N/A bool_t (*xdr_func)();
2N/A caddr_t xdr_ptr;
2N/A{
2N/A svc_rpc_gss_parms_t *gss_parms = &auth->svc_gss_parms;
2N/A
2N/A /*
2N/A * If context is not established, or if neither integrity nor
2N/A * privacy service is used, don't unwrap - just XDR decode.
2N/A * Otherwise, unwrap data.
2N/A */
2N/A if (!gss_parms->established ||
2N/A gss_parms->service == rpc_gss_svc_none)
2N/A return ((*xdr_func)(in_xdrs, xdr_ptr));
2N/A
2N/A return (__rpc_gss_unwrap_data(gss_parms->service,
2N/A (gss_ctx_id_t)gss_parms->context,
2N/A gss_parms->seq_num,
2N/A gss_parms->qop_rcvd,
2N/A in_xdrs, xdr_func, xdr_ptr));
2N/A}
2N/A
2N/Aint
2N/A__rpc_gss_svc_max_data_length(req, max_tp_unit_len)
2N/A struct svc_req *req;
2N/A int max_tp_unit_len;
2N/A{
2N/A SVCAUTH *svcauth;
2N/A svc_rpc_gss_parms_t *gss_parms;
2N/A
2N/A svcauth = __svc_get_svcauth(req->rq_xprt);
2N/A gss_parms = &svcauth->svc_gss_parms;
2N/A
2N/A if (!gss_parms->established || max_tp_unit_len <= 0)
2N/A return (0);
2N/A
2N/A return (__find_max_data_length(gss_parms->service,
2N/A (gss_ctx_id_t)gss_parms->context,
2N/A gss_parms->qop_rcvd, max_tp_unit_len));
2N/A}
2N/A
2N/A/*
2N/A * Add retransmit entry to the context cache entry for a new xid.
2N/A * If there is already an entry, delete it before adding the new one.
2N/A */
2N/Astatic void retrans_add(client, xid, result)
2N/A svc_rpc_gss_data *client;
2N/A uint32_t xid;
2N/A rpc_gss_init_res *result;
2N/A{
2N/A retrans_entry *rdata;
2N/A
2N/A if (client->retrans_data && client->retrans_data->xid == xid)
2N/A return;
2N/A
2N/A rdata = (retrans_entry *) malloc(sizeof (*rdata));
2N/A if (rdata == NULL)
2N/A return;
2N/A
2N/A rdata->xid = xid;
2N/A rdata->result = *result;
2N/A
2N/A if (result->token.length != 0) {
2N/A GSS_DUP_BUFFER(rdata->result.token, result->token);
2N/A }
2N/A
2N/A if (client->retrans_data)
2N/A retrans_del(client);
2N/A
2N/A client->retrans_data = rdata;
2N/A}
2N/A
2N/A/*
2N/A * Delete the retransmit data from the context cache entry.
2N/A */
2N/Astatic void retrans_del(client)
2N/A svc_rpc_gss_data *client;
2N/A{
2N/A retrans_entry *rdata;
2N/A OM_uint32 minor_stat;
2N/A
2N/A if (client->retrans_data == NULL)
2N/A return;
2N/A
2N/A rdata = client->retrans_data;
2N/A if (rdata->result.token.length != 0) {
2N/A (void) gss_release_buffer(&minor_stat, &rdata->result.token);
2N/A }
2N/A
2N/A free((caddr_t)rdata);
2N/A client->retrans_data = NULL;
2N/A}