#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>
#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
typedef struct _retrans_entry {
uint32_t xid;
rpc_gss_init_res result;
struct _retrans_entry *next, *prev;
} retrans_entry;
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;
#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 = 0;
static uint_t gid_timeout = 43200;
static mutex_t ctx_mutex = DEFAULTMUTEX;
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;
static rwlock_t cred_lock = DEFAULTRWLOCK;
typedef struct cblist_s {
struct cblist_s *next;
rpc_gss_callback_t cb;
} cblist_t;
cblist_t *cblist = NULL;
static mutex_t cb_mutex = DEFAULTMUTEX;
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 *);
struct svc_auth_ops svc_rpc_gss_ops = {
svc_rpc_gss_wrap,
svc_rpc_gss_unwrap,
};
extern SVCAUTH *__svc_get_svcauth();
extern void *__svc_set_proc_cleanup_cb();
static void (*old_cleanup_cb)() = NULL;
static bool_t cleanup_cb_set = FALSE;
static void
ctx_cleanup(SVCXPRT *xprt)
{
svc_rpc_gss_data *cl;
SVCAUTH *svcauth;
if (old_cleanup_cb != NULL)
(*old_cleanup_cb)(xprt);
svcauth = __svc_get_svcauth(xprt);
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);
}
if ((time(0) - last_swept) > sweep_interval) {
mutex_lock(&ctx_mutex);
if ((time(0) - last_swept) > sweep_interval)
sweep_clients();
mutex_unlock(&ctx_mutex);
}
}
void
__rpc_gss_set_server_parms(int init_cred_lifetime, int max_cred_lifetime,
int cache_size)
{
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);
}
static void
shift_bits(uint_t *arr, int arrlen, int nbits)
{
int i, j;
uint_t lo, hi;
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;
}
}
static bool_t
check_seq(svc_rpc_gss_data *cl, uint_t seq_num, bool_t *kill_context)
{
int i, j;
uint_t bit;
if (seq_num >= SEQ_MAX) {
*kill_context = TRUE;
return (FALSE);
}
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);
}
i = cl->seq_num - seq_num;
if (i >= SEQ_WIN)
return (FALSE);
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);
}
static bool_t
__rpc_gss_make_principal(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);
}
static bool_t
set_client_principal(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);
}
bool_t
__rpc_gss_set_callback(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);
}
static bool_t
do_callback(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);
}
bool_t
__rpc_gss_getcred(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);
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) {
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, NULL);
client_data->time_secs_set =
now.tv_sec;
*ucred = &client_data->u_cred;
} else
*ucred = NULL;
}
}
} else {
gettimeofday(&now, NULL);
if ((now.tv_sec - client_data->time_secs_set)
> gid_timeout) {
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, 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);
}
enum auth_stat
__svcrpcsec_gss(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;
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;
memset((char *)&call_res, 0, sizeof (call_res));
cred = &msg->rm_call.cb_cred;
if (cred->oa_length == 0) {
return (AUTH_BADCRED);
}
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 (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 {
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;
}
}
mutex_lock(&client_data->clm);
if (client_data->stale) {
ret = RPCSEC_GSS_NOCRED;
goto error2;
}
call_res.ctx_handle.length = sizeof (client_data->key);
call_res.ctx_handle.value = (char *)&client_data->key;
call_res.seq_window = SEQ_WIN;
svcauth = __svc_get_svcauth(rqst->rq_xprt);
svcauth->svc_ah_ops = svc_rpc_gss_ops;
svcauth->svc_ah_private = (caddr_t)client_data;
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 (creds.gss_proc != RPCSEC_GSS_INIT && creds.gss_proc !=
RPCSEC_GSS_CONTINUE_INIT) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
goto error2;
}
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);
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) {
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) {
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) {
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;
}
if (output_token.length != 0) {
GSS_COPY_BUFFER(call_res.token, output_token);
}
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);
(void) retrans_add(client_data, msg->rm_xid, &call_res);
*no_dispatch = TRUE;
(void) gss_release_buffer(&minor_stat, &output_token);
if (gssstat == GSS_S_COMPLETE) {
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:
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;
}
}
default:
syslog(LOG_ERR, "_svcrpcsec_gss: non-data "
"request on an established context");
ret = AUTH_FAILED;
goto error2;
}
}
if (client_data->retrans_data)
retrans_del(client_data);
if (!check_verf(msg, client_data->context,
&gss_parms->qop_rcvd)) {
ret = RPCSEC_GSS_NOCRED;
goto error2;
}
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 (client_data->locked &&
gss_parms->qop_rcvd != client_data->qop) {
ret = AUTH_BADVERF;
goto error2;
}
if (!check_seq(client_data, creds.seq_num,
&client_data->stale)) {
if (client_data->stale)
ret = RPCSEC_GSS_FAILED;
else {
ret = AUTH_OK;
*no_dispatch = TRUE;
}
goto error2;
}
if (!set_response_verf(rqst, msg, client_data, creds.seq_num)) {
ret = RPCSEC_GSS_FAILED;
client_data->stale = TRUE;
goto error2;
}
if (creds.gss_proc == RPCSEC_GSS_DESTROY) {
svc_sendreply(rqst->rq_xprt, xdr_void, NULL);
*no_dispatch = TRUE;
client_data->stale = TRUE;
} else {
if (client_data->locked &&
client_data->raw_cred.service != creds.service) {
ret = AUTH_FAILED;
goto error2;
}
rqst->rq_clntcred = (char *)&client_data->raw_cred;
}
}
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:
if (creds.ctx_handle.length != 0)
xdr_free(__xdr_rpc_gss_creds, (caddr_t)&creds);
return (ret);
}
static bool_t
check_verf(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;
oa = &msg->rm_call.cb_cred;
if (oa->oa_length > MAX_AUTH_BYTES)
return (FALSE);
if (sizeof (hdr) < (8 * BYTES_PER_XDR_UNIT + RNDUP(oa->oa_length)))
return (FALSE);
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);
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);
}
static bool_t
set_response_verf(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);
}
static svc_rpc_gss_data *
create_client(void)
{
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));
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);
mutex_lock(&ctx_mutex);
if (num_gss_contexts >= max_gss_contexts) {
drop_lru_client();
if (num_gss_contexts >= max_gss_contexts) {
mutex_unlock(&ctx_mutex);
free((char *)client_data);
return (NULL);
}
}
for (;;) {
client_data->key = key++;
if (find_client(client_data->key) == NULL) {
insert_client(client_data);
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);
}
}
}
static void
insert_client(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++;
}
static svc_rpc_gss_data *
get_client(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);
}
static svc_rpc_gss_data *
find_client(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);
}
static void
destroy_client(svc_rpc_gss_data *client_data)
{
OM_uint32 minor;
int index = (client_data->key & HASHMASK);
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;
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 (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);
}
free(client_data->raw_cred.client_principal);
free(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--;
}
static void
sweep_clients(void)
{
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);
}
static void
drop_lru_client(void)
{
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();
}
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);
}
bool_t
__rpc_gss_set_svc_name(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);
}
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) {
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);
}
}
static bool_t
rpc_gss_refresh_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);
}
static bool_t
svc_rpc_gss_wrap(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 (!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));
}
static bool_t
svc_rpc_gss_unwrap(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 (!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(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));
}
static void retrans_add(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;
}
static void retrans_del(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;
}