/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
/*
* Copyright 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 <gssapi/gssapi_ext.h>
#include <rpc/rpcsec_defs.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <syslog.h>
/*
* Sequence window definitions.
*/
/* cache retransmit data */
typedef struct _retrans_entry {
/*
* Server side RPCSEC_GSS context information.
*/
typedef struct _svc_rpc_gss_data {
void *cookie;
int ref_cnt;
/*
* Data structures used for LRU based context management.
*/
static int num_gss_contexts = 0;
static int last_swept = 0;
static int init_lifetime = 0;
/*
*/
/*
* server credential management data and structures
*/
typedef struct svc_creds_list_s {
char *server_name;
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)
*/
/*
* server callback list
*/
typedef struct cblist_s {
} cblist_t;
/*
* lock used with callback variables
*/
/*
* forward declarations
*/
static bool_t svc_rpc_gss_wrap();
static bool_t svc_rpc_gss_unwrap();
static svc_rpc_gss_data *create_client();
static svc_rpc_gss_data *get_client();
static svc_rpc_gss_data *find_client();
static void destroy_client();
static void sweep_clients();
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();
rpc_gss_init_res *);
static void retrans_del(struct _svc_rpc_gss_data *);
/*
*/
};
/*
* 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
{
if (old_cleanup_cb != NULL)
(*old_cleanup_cb)(xprt);
/*
* First check if current context needs to be cleaned up.
*/
/*LINTED*/
} else
}
/*
* Check for other expired contexts.
*/
/*
* Check again, in case some other thread got in.
*/
}
}
/*
* Set server parameters.
*/
void
int init_cred_lifetime;
int max_cred_lifetime;
int cache_size;
{
/*
* Ignore parameters unless greater than zero.
*/
if (cache_size > 0)
if (max_cred_lifetime > 0)
if (init_cred_lifetime > 0)
}
/*
* Shift the array arr of length arrlen right by nbits bits.
*/
static void
int arrlen;
int nbits;
{
int i, j;
/*
* If the number of bits to be shifted exceeds SEQ_WIN, just
* zero out the array.
*/
for (i = 0; i < nbits; i++) {
hi = 0;
for (j = 0; j < arrlen; j++) {
arr[j] >>= 1;
if (hi)
arr[j] |= SEQ_HI_BIT;
}
}
} else {
for (j = 0; j < arrlen; j++)
arr[j] = 0;
}
}
/*
* Check that the received sequence number seq_num is valid.
*/
static bool_t
{
int i, j;
/*
* If it exceeds the maximum, kill context.
*/
*kill_context = TRUE;
return (FALSE);
}
/*
* If greater than the last seen sequence number, just shift
* the sequence window so that it starts at the new sequence
* number and extends downwards by SEQ_WIN.
*/
return (TRUE);
}
/*
* If it is outside the sequence window, return failure.
*/
if (i >= SEQ_WIN)
return (FALSE);
/*
* If within sequence window, set the bit corresponding to it
* if not already seen; if already seen, return failure.
*/
i >>= DIV_BY_32;
return (FALSE);
return (TRUE);
}
/*
* Convert a name in gss exported type to rpc_gss_principal_t type.
*/
static bool_t
{
int plen;
char *s;
return (FALSE);
return (TRUE);
}
/*
* Convert a name in internal form to the exported type.
*/
static bool_t
{
if (major != GSS_S_COMPLETE)
return (FALSE);
return (ret);
}
/*
* Set server callback.
*/
{
return (FALSE);
return (FALSE);
return (TRUE);
}
/*
* Locate callback (if specified) and call server. Release any
* delegated credentials unless passed to server and the server
* accepts the context. If a callback is not specified, accept
* the incoming context.
*/
static bool_t
{
continue;
if (ret) {
}
break;
}
if (!found) {
}
}
return (ret);
}
/*
* Return caller credentials.
*/
void **cookie;
{
int len = 0;
/*LINTED*/
}
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(
"mech_to_oid failed in getcred.\n"));
} else {
&len);
if (status == GSS_S_COMPLETE) {
(short)len;
} else
}
}
} else {
/*
* gid's already set;
* check if they have expired.
*/
> gid_timeout) {
/* Refresh gid's */
&len);
if (status == GSS_S_COMPLETE) {
(short)len;
} else {
}
}
else
}
}
return (TRUE);
}
/*
* Server side authentication for RPCSEC_GSS.
*/
enum auth_stat
{
int ret;
/*
* Initialize response verifier to NULL verifier. If
* necessary, this will be changed later.
*/
/*
* Need to null out results to start with.
*/
/*
* Pull out and check credential and verifier.
*/
return (AUTH_BADCRED);
}
XDR_DESTROY(&xdrs);
ret = AUTH_BADCRED;
goto error;
}
XDR_DESTROY(&xdrs);
/*
* If this is a control message and proc is GSSAPI_INIT, then
* create a client handle for this client. Otherwise, look up
* the existing handle.
*/
ret = AUTH_BADCRED;
goto error;
}
ret = AUTH_FAILED;
goto error;
}
} else {
/*
* Only verify values for service parameter when proc
* not RPCSEC_GSS_INIT or RPCSEC_GSS_CONTINUE_INIT.
* RFC2203 says contents for sequence and service args
* are undefined for creation procs.
*
* Note: only need to check for *CONTINUE_INIT here because
* if() clause already checked for RPCSEC_GSS_INIT
*/
case rpc_gss_svc_none:
case rpc_gss_svc_integrity:
case rpc_gss_svc_privacy:
break;
default:
ret = AUTH_BADCRED;
goto error;
}
}
ret = AUTH_BADCRED;
goto error;
}
goto error;
}
}
/*
* lock the client data until it's safe; if it's already stale,
* no more processing is possible
*/
if (client_data->stale) {
goto error2;
}
/*
* Any response we send will use ctx_handle, so set it now;
* also set seq_window since this won't change.
*/
/*
*/
/*
* Keep copy of parameters we'll need for response, for the
* sake of reentrancy (we don't want to look in the context
* data because when we are sending a response, another
* request may have come in.
*/
if (!client_data->established) {
goto error2;
}
/*
* If the context is not established, then only GSSAPI_INIT
* and _CONTINUE requests are valid.
*/
goto error2;
}
/*
* call is for us, deserialize arguments
*/
goto error2;
}
minor_stat = 0;
/*
* set next sc to point to the server cred
* if the client_data contains server_creds
*/
continue;
&call_arg,
&time_rec,
NULL);
if (gssstat == GSS_S_CREDENTIALS_EXPIRED) {
if (rpc_gss_refresh_svc_cred(sc)) {
&call_arg,
&time_rec,
NULL);
} else {
break;
}
} else
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.
*/
== NULL) {
(void) gss_release_buffer(&minor_stat,
&output_token);
} else if (!set_client_principal(client_data->
(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.
*/
break;
}
/* Make sure to free output_token in case of failure. */
if (output_token.length != 0) {
(void) gss_release_buffer(&minor_stat,
&output_token);
}
}
if (gssstat != GSS_S_COMPLETE &&
gssstat != GSS_S_CONTINUE_NEEDED) {
/*
* We have a failure - send response and delete
* the context. Don't dispatch. Set ctx_handle
* to NULL and seq_window to 0.
*/
call_res.seq_window = 0;
*no_dispatch = 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) {
}
/*
* set response verifier: checksum of SEQ_WIN
*/
if (gssstat == GSS_S_COMPLETE) {
(void) gss_release_buffer(&minor_stat,
&output_token);
goto error2;
}
}
/*
* Cache last response in case it is lost and the client
* retries on an established context.
*/
*no_dispatch = TRUE;
/*
* 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).
*/
if (time_rec == GSS_C_INDEFINITE) {
if (max_lifetime != GSS_C_INDEFINITE)
max_lifetime + time(0);
else
} else if (max_lifetime == GSS_C_INDEFINITE ||
else
time(0);
}
} else {
case RPCSEC_GSS_CONTINUE_INIT:
/*
* This is an established context. Continue to
* satisfy retried continue init requests out of
* the retransmit cache. Throw away any that don't
* have a matching xid or the cach is empty.
* Delete the retransmit cache once the client sends
* a data request.
*/
if (client_data->retrans_data &&
*no_dispatch = TRUE;
goto success;
}
}
/* fall thru to default */
default:
"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)
/*
* Context is already established. Check verifier, and
* note parameters we will need for response in gss_parms.
*/
goto error2;
}
/*
* Check and invoke callback if necessary.
*/
if (!client_data->done_docallback) {
ret = AUTH_FAILED;
goto error2;
}
}
/*
* If the context was locked, make sure that the client
* has not changed QOP.
*/
if (client_data->locked &&
ret = AUTH_BADVERF;
goto error2;
}
/*
* Validate sequence number.
*/
&client_data->stale)) {
if (client_data->stale)
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.
*/
*no_dispatch = TRUE;
}
goto error2;
}
/*
* set response verifier
*/
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.
*/
*no_dispatch = 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 &&
ret = AUTH_FAILED;
goto error2;
}
/*
* Set client credentials to raw credential
* structure in context. This is okay, since
* this will not change during the lifetime of
* the context (so it's MT safe).
*/
}
}
/*
* Success.
*/
return (AUTH_OK);
/*
* Failure.
*/
return (ret);
}
/*
* Check verifier. The verifier is the checksum of the RPC header
* upto and including the credentials field.
*/
static bool_t
int *qop_state;
{
int len;
/*
* We have to reconstruct the RPC header from the previously
* parsed information, since we haven't kept the header intact.
*/
return (FALSE);
/* 8 XDR units from the IXDR macro calls. */
return (FALSE);
*(buf - 1) = 0;
}
if (gssstat != GSS_S_COMPLETE)
return (FALSE);
return (TRUE);
}
/*
* Set response verifier. This is the checksum of the given number.
* (e.g. sequence number or sequence window)
*/
static bool_t
{
&out_buf) != GSS_S_COMPLETE)
return (FALSE);
return (TRUE);
}
/*
* Create client context.
*/
static svc_rpc_gss_data *
{
if (client_data == NULL)
return (NULL);
/*
* set up client data structure
*/
client_data->time_secs_set = 0;
/*
* Check totals. If we've hit the limit, we destroy a context
* based on LRU method.
*/
if (num_gss_contexts >= max_gss_contexts) {
/*
* now try on LRU basis
*/
if (num_gss_contexts >= max_gss_contexts) {
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 (;;) {
/*
* Set cleanup callback if we haven't.
*/
if (!cleanup_cb_set) {
(void (*)()) __svc_set_proc_cleanup_cb(
(void *)ctx_cleanup);
}
return (client_data);
}
}
/*NOTREACHED*/
}
/*
* Insert client context into hash list and LRU list.
*/
static void
{
else
}
/*
* Fetch a client, given the client context handle. Move it to the
* top of the LRU list since this is the most recently used context.
*/
static svc_rpc_gss_data *
{
return (NULL);
}
else
}
}
return (cl);
}
/*
* Given the client context handle, find the context corresponding to it.
* Don't change its LRU state since it may not be used.
*/
static svc_rpc_gss_data *
{
break;
}
return (cl);
}
/*
* Destroy a client context.
*/
static void
{
/*
* remove from hash list
*/
else
/*
* remove from LRU list
*/
else
else
/*
* If there is a GSS context, clean up GSS state.
*/
NULL);
if (client_data->client_name)
}
}
/*
* Check for expired client contexts.
*/
static void
{
int index;
while (cl) {
} else
} else
}
}
last_swept = time(0);
}
/*
* Drop the least recently used client context, if possible.
*/
static void
{
else
}
/*
* find service credentials
* return cred if found,
* other wise, NULL
*/
if (!svc_creds_list)
return (NULL);
continue;
continue;
return (sc);
}
return (NULL);
}
/*
* Set the server principal name.
*/
char *server_name;
char *mech;
{
return (FALSE);
}
if (major != GSS_S_COMPLETE) {
return (FALSE);
}
/* Check if there is already an entry in the svc_creds_list. */
&ret_time);
if (major == GSS_S_COMPLETE) {
/*
* Successfully added the mech to the cred handle
* free the existing oid_set in svc_cred
*/
return (TRUE);
} else if (major == GSS_S_DUPLICATE_ELEMENT) {
return (TRUE);
} else if (major == GSS_S_CREDENTIALS_EXPIRED) {
if (rpc_gss_refresh_svc_cred(svc_cred)) {
return (TRUE);
} else {
return (FALSE);
}
} else {
return (FALSE);
}
} else {
return (FALSE);
}
if (major != GSS_S_COMPLETE) {
return (FALSE);
}
return (FALSE);
}
return (TRUE);
}
}
/*
* Refresh server credentials.
*/
static bool_t
{
&ret_time);
if (major != GSS_S_COMPLETE) {
return (FALSE);
}
return (TRUE);
}
/*
* Encrypt the serialized arguments from xdr_func applied to xdr_ptr
* and write the result to xdrs.
*/
static bool_t
{
/*
* If context is not established, or if neither integrity nor
* privacy service is used, don't wrap - just XDR encode.
* Otherwise, wrap data using service and QOP parameters.
*/
if (!gss_parms->established ||
}
/*
* Decrypt the serialized arguments and XDR decode them.
*/
static bool_t
{
/*
* If context is not established, or if neither integrity nor
* privacy service is used, don't unwrap - just XDR decode.
* Otherwise, unwrap data.
*/
if (!gss_parms->established ||
}
int
int max_tp_unit_len;
{
return (0);
}
/*
* Add retransmit entry to the context cache entry for a new xid.
* If there is already an entry, delete it before adding the new one.
*/
{
return;
return;
}
if (client->retrans_data)
}
/*
* Delete the retransmit data from the context cache entry.
*/
{
return;
}
}