diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/lib/rpcsec_gss | |
download | illumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/lib/rpcsec_gss')
-rw-r--r-- | usr/src/lib/rpcsec_gss/Makefile | 98 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/Makefile.com | 64 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/amd64/Makefile | 39 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/i386/Makefile | 37 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/mapfile-vers | 78 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/rpcsec_gss.c | 945 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/rpcsec_gss_misc.c | 346 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/rpcsec_gss_utils.c | 336 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/sparc/Makefile | 38 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/sparcv9/Makefile | 40 | ||||
-rw-r--r-- | usr/src/lib/rpcsec_gss/svc_rpcsec_gss.c | 1698 |
11 files changed, 3719 insertions, 0 deletions
diff --git a/usr/src/lib/rpcsec_gss/Makefile b/usr/src/lib/rpcsec_gss/Makefile new file mode 100644 index 0000000000..da7a5e7056 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/Makefile @@ -0,0 +1,98 @@ +# +# 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 (c) 1995,1997, by Sun Microsystems, Inc. +# All rights reserved. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +include ../../Makefile.master + +SUBDIRS= $(MACH) $(BUILD64) $(MACH64) + +# include library definitions +include ../Makefile.lib + +#override INS.liblink +INS.liblink= -$(RM) $@; $(SYMLINK) $(LIBLINKPATH)$(LIBLINKS)$(VERS) $@ + +HDRS= + +CHECKHDRS= $(HDRS:%.h=%.check) + +#install rule +$(ROOTDIRS)/%: % + $(INS.file) + +all := TARGET= all +clean := TARGET= clean +clobber := TARGET= clobber +install := TARGET= install +lint := TARGET= lint +_msg := TARGET= _msg + +LIBRARY= rpcsec.a +TEXT_DOMAIN= SUNW_OST_NETRPC +XGETFLAGS= -a +POFILE= $(LIBRARY:.a=.po) +POFILES= generic.po + +.KEEP_STATE: + +all: .WAIT $(SUBDIRS) + +lint: .WAIT $(SUBDIRS) + +install: all .WAIT $(SUBDIRS) + +install_h: + +clean clobber: $(SUBDIRS) + +check: $(CHECKHDRS) + +# include library targets +include ../Makefile.targ + +$(MACH) $(MACH64): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +_msg: $(MSGDOMAIN) $(POFILE) + $(RM) $(MSGDOMAIN)/$(POFILE) + $(CP) $(POFILE) $(MSGDOMAIN) + +$(POFILE): .WAIT $(POFILES) + $(RM) $@ + $(CAT) $(POFILES) > $@ + +$(POFILES): + $(RM) messages.po + $(XGETTEXT) $(XGETFLAGS) `$(GREP) -l gettext *.[ch]` + $(SED) -e '/^# msg/d' -e '/^domain/d' messages.po > $@ + $(RM) messages.po + +catalog: + +$(MSGDOMAIN): + $(INS.dir) + +FRC: diff --git a/usr/src/lib/rpcsec_gss/Makefile.com b/usr/src/lib/rpcsec_gss/Makefile.com new file mode 100644 index 0000000000..a7a3531ed5 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/Makefile.com @@ -0,0 +1,64 @@ +# +# 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 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +LIBRARY= rpcsec.a +VERS = .1 + +OBJECTS=rpcsec_gss.o rpcsec_gss_misc.o rpcsec_gss_utils.o svc_rpcsec_gss.o + +# include library definitions +include ../../Makefile.lib + +MAPFILE= ../mapfile-vers +SRCS= $(OBJECTS:%.o=../%.c) + +CPPFLAGS += -D_REENTRANT -I$(SRC)/uts/common/gssapi/include \ + -I$(SRC)/uts/common +CFLAGS += $(XFFLAG) + +LINTSRC= $(LINTLIB:%.ln=%) + +LIBS = $(DYNLIB) + +LDLIBS += -lgss -lnsl -lc +DYNFLAGS += -M$(MAPFILE) + +.KEEP_STATE: + +lint: lintcheck + +# include library targets +include ../../Makefile.targ + +# librpcsec build rules + +pics/%.o: ../%.c + $(COMPILE.c) -o $@ $< + $(POST_PROCESS_O) + +$(DYNLIB) : $(MAPFILE) diff --git a/usr/src/lib/rpcsec_gss/amd64/Makefile b/usr/src/lib/rpcsec_gss/amd64/Makefile new file mode 100644 index 0000000000..c70cf69700 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/amd64/Makefile @@ -0,0 +1,39 @@ +# +# 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 2004 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# + +include ../Makefile.com +include ../../Makefile.lib.64 + +LIBS= $(DYNLIB) + +.KEEP_STATE: + +all: $(LIBS) + +install: all $(ROOTLIBS64) $(ROOTLINKS64) + diff --git a/usr/src/lib/rpcsec_gss/i386/Makefile b/usr/src/lib/rpcsec_gss/i386/Makefile new file mode 100644 index 0000000000..6feae2497f --- /dev/null +++ b/usr/src/lib/rpcsec_gss/i386/Makefile @@ -0,0 +1,37 @@ +# +# 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 (c) 1997, by Sun Microsystems, Inc. +# All rights reserved. +# +#ident "%Z%%M% %I% %E% SMI" +# +# lib/rpcsec_gss/i386/Makefile + +include ../Makefile.com + +.KEEP_STATE: + +all: $(LIBS) + +install: all $(ROOTLIBS) $(ROOTLINKS) + diff --git a/usr/src/lib/rpcsec_gss/mapfile-vers b/usr/src/lib/rpcsec_gss/mapfile-vers new file mode 100644 index 0000000000..03dc20ed9b --- /dev/null +++ b/usr/src/lib/rpcsec_gss/mapfile-vers @@ -0,0 +1,78 @@ +# +#ident "%Z%%M% %I% %E% SMI" +# +# Copyright 2005 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# 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 +# +# Generic interface definition for usr/src/lib/rpcsec_gss. +# +# For information regarding the establishment of versioned definitions see: +# The Linker and Libraries Manual (version 2.5 or greater) +# This is part of the Developers Guide in the Answerbook. Specifically refer +# to Chapter 2 under section "Defining Additional Symbols" through section +# "Reducing Symbol Scope", and Chapter 5 "Versioning". +# +# For specific OSNET rules for the modification (evolution) of these version +# definitions see: +# Policy for Shared Library Version Names and Interface Definitions + + +# Note: Even though the SUNW_1.2 version now contains no symbols +# beyond what was present at Solaris 2.6, the SUNW_1.2 version MUST be +# present. This is because applications built on 2.6 Beta +# (when it did contain symbols explicitly) may depend on it. +# +SUNW_1.2 { # This empty version MUST BE preserved for 2.6Beta apps + global: + SUNW_1.2; +} SUNW_1.1; + +SUNW_1.1 { + global: + SUNW_1.1; +}; + +SUNWprivate_1.1 { + global: + __rpc_gss_seccreate; + __rpc_gss_set_defaults; + __rpc_gss_wrap; + __rpc_gss_unwrap; + __rpc_gss_max_data_length; + __rpc_gss_get_error; + __rpc_gss_mech_to_oid; + __rpc_gss_qop_to_num; + __rpc_gss_get_principal_name; + __rpc_gss_get_mechanisms; + __rpc_gss_get_mech_info; + __rpc_gss_get_versions; + __rpc_gss_is_installed; + __rpc_gss_set_callback; + __rpc_gss_getcred; + __svcrpcsec_gss; + __rpc_gss_set_svc_name; + __rpc_gss_svc_max_data_length; + + local: + *; +}; diff --git a/usr/src/lib/rpcsec_gss/rpcsec_gss.c b/usr/src/lib/rpcsec_gss/rpcsec_gss.c new file mode 100644 index 0000000000..c71840304d --- /dev/null +++ b/usr/src/lib/rpcsec_gss/rpcsec_gss.c @@ -0,0 +1,945 @@ +/* + * 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 (c) 1986-1995, 1997, 2001 by Sun Microsystems, Inc. + * All rights reserved. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + + +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved. + * + * $Header: + * /afs/gza.com/product/secure/rel-eng/src/1.1/rpc/RCS/auth_gssapi.c,v + * 1.14 1995/03/22 22:07:55 jik Exp $ + */ + +#include <stdio.h> +#include <stdlib.h> +#include <strings.h> +#include <errno.h> +#include <pthread.h> +#include <thread.h> +#include <syslog.h> +#include <gssapi/gssapi.h> +#include <rpc/rpc.h> +#include <rpc/rpcsec_defs.h> + +static void rpc_gss_nextverf(); +static bool_t rpc_gss_marshall(); +static bool_t rpc_gss_validate(); +static bool_t rpc_gss_refresh(); +static void rpc_gss_destroy(); +static void rpc_gss_destroy_pvt(); +static bool_t rpc_gss_seccreate_pvt(); +static bool_t validate_seqwin(); + +/* + * Globals that should have header files but don't. + */ +extern bool_t xdr_opaque_auth(XDR *, struct opaque_auth *); +extern int _thr_main(void); +extern int _thr_getspecific(thread_key_t key, void **valuep); +typedef void (*PFrV) (void *); +extern int _thr_keycreate(thread_key_t *pkey, PFrV destructor); +extern int _thr_setspecific(unsigned int key, void *value); + + +static struct auth_ops rpc_gss_ops = { + rpc_gss_nextverf, + rpc_gss_marshall, + rpc_gss_validate, + rpc_gss_refresh, + rpc_gss_destroy +}; + +/* + * Private data for RPCSEC_GSS. + */ +typedef struct _rpc_gss_data { + bool_t established; /* TRUE when established */ + CLIENT *clnt; /* associated client handle */ + uint_t version; /* RPCSEC version */ + gss_ctx_id_t context; /* GSS context id */ + gss_buffer_desc ctx_handle; /* RPCSEC context handle */ + uint_t seq_num; /* last sequence number rcvd */ + gss_cred_id_t my_cred; /* GSS credentials */ + OM_uint32 qop; /* requested QOP */ + rpc_gss_service_t service; /* requested service */ + uint_t gss_proc; /* GSS control procedure */ + gss_name_t target_name; /* target server */ + int req_flags; /* GSS request bits */ + gss_OID mech_type; /* GSS mechanism */ + OM_uint32 time_req; /* requested cred lifetime */ + bool_t invalid; /* can't use this any more */ + OM_uint32 seq_window; /* server sequence window */ + struct opaque_auth *verifier; /* rpc reply verifier saved for */ + /* validating the sequence window */ + gss_channel_bindings_t icb; +} rpc_gss_data; +#define AUTH_PRIVATE(auth) ((rpc_gss_data *)auth->ah_private) + +/* + * Create a context. + */ +AUTH * +__rpc_gss_seccreate(clnt, server_name, mech, service, qop, options_req, + options_ret) + CLIENT *clnt; /* associated client handle */ + char *server_name; /* target server */ + char *mech; /* security mechanism */ + rpc_gss_service_t service; /* security service */ + char *qop; /* requested QOP */ + rpc_gss_options_req_t *options_req; /* requested options */ + rpc_gss_options_ret_t *options_ret; /* returned options */ +{ + OM_uint32 gssstat; + OM_uint32 minor_stat; + gss_name_t target_name; + gss_OID mech_type; + OM_uint32 ret_flags; + OM_uint32 time_rec; + gss_buffer_desc input_name; + AUTH *auth = NULL; + rpc_gss_data *ap = NULL; + OM_uint32 qop_num; + rpc_gss_error_t error; + void __rpc_gss_get_error(); + + /* + * convert ascii strings to GSS values + */ + if (!__rpc_gss_qop_to_num(qop, mech, &qop_num)) { + __rpc_gss_get_error(&error); + return (NULL); + } + + if (!__rpc_gss_mech_to_oid(mech, &mech_type)) { + __rpc_gss_get_error(&error); + return (NULL); + } + + /* + * convert name to GSS internal type + */ + input_name.value = server_name; + input_name.length = strlen(server_name); + gssstat = gss_import_name(&minor_stat, &input_name, + (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, + &target_name); + if (gssstat != GSS_S_COMPLETE) { + rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; + rpc_gss_err.system_error = ENOMEM; + return (NULL); + } + + /* + * Create AUTH handle. Save the necessary interface information + * so that the client can refresh the handle later if needed. + */ + if ((auth = (AUTH *) malloc(sizeof (*auth))) != NULL) + ap = (rpc_gss_data *) malloc(sizeof (*ap)); + if (auth == NULL || ap == NULL) { + rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; + rpc_gss_err.system_error = ENOMEM; + if (auth != NULL) + free((char *)auth); + (void) gss_release_name(&minor_stat, &target_name); + return (NULL); + } + + memset((char *)ap, 0, sizeof (*ap)); + ap->clnt = clnt; + ap->version = RPCSEC_GSS_VERSION; + if (options_req != NULL) { + ap->my_cred = options_req->my_cred; + ap->req_flags = options_req->req_flags; + ap->time_req = options_req->time_req; + ap->icb = options_req->input_channel_bindings; + } else { + ap->my_cred = GSS_C_NO_CREDENTIAL; + ap->req_flags = GSS_C_MUTUAL_FLAG; + ap->time_req = 0; + ap->icb = NULL; + } + if ((ap->service = service) == rpc_gss_svc_default) + ap->service = rpc_gss_svc_integrity; + ap->qop = qop_num; + ap->target_name = target_name; + ap->mech_type = mech_type; + + /* + * Now invoke the real interface that sets up the context from + * the information stashed away in the private data. + */ + if (!rpc_gss_seccreate_pvt(&gssstat, &minor_stat, auth, ap, + &mech_type, &ret_flags, &time_rec)) { + if (ap->target_name) + (void) gss_release_name(&minor_stat, &ap->target_name); + free((char *)ap); + free((char *)auth); + return (NULL); + } + + /* + * Make sure that the requested service is supported. In all + * cases, integrity service must be available. + */ + if ((ap->service == rpc_gss_svc_privacy && + !(ret_flags & GSS_C_CONF_FLAG)) || + !(ret_flags & GSS_C_INTEG_FLAG)) { + rpc_gss_destroy(auth); + rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; + rpc_gss_err.system_error = EPROTONOSUPPORT; + return (NULL); + } + + /* + * return option values if requested + */ + if (options_ret != NULL) { + char *s; + + options_ret->major_status = gssstat; + options_ret->minor_status = minor_stat; + options_ret->rpcsec_version = ap->version; + options_ret->ret_flags = ret_flags; + options_ret->time_ret = time_rec; + options_ret->gss_context = ap->context; + if ((s = __rpc_gss_oid_to_mech(mech_type)) != NULL) + strcpy(options_ret->actual_mechanism, s); + else + options_ret->actual_mechanism[0] = '\0'; + } + return (auth); +} + +/* + * Private interface to create a context. This is the interface + * that's invoked when the context has to be refreshed. + */ +static bool_t +rpc_gss_seccreate_pvt(gssstat, minor_stat, auth, ap, actual_mech_type, + ret_flags, time_rec) + OM_uint32 *gssstat; + OM_uint32 *minor_stat; + AUTH *auth; + rpc_gss_data *ap; + gss_OID *actual_mech_type; + OM_uint32 *ret_flags; + OM_uint32 *time_rec; +{ + CLIENT *clnt = ap->clnt; + AUTH *save_auth; + enum clnt_stat callstat; + rpc_gss_init_arg call_arg; + rpc_gss_init_res call_res; + gss_buffer_desc *input_token_p, input_token; + bool_t free_results = FALSE; + + /* + * initialize error + */ + memset(&rpc_createerr, 0, sizeof (rpc_createerr)); + + /* + * (re)initialize AUTH handle and private data. + */ + memset((char *)auth, 0, sizeof (*auth)); + auth->ah_ops = &rpc_gss_ops; + auth->ah_private = (caddr_t)ap; + auth->ah_cred.oa_flavor = RPCSEC_GSS; + + ap->established = FALSE; + ap->ctx_handle.length = 0; + ap->ctx_handle.value = NULL; + ap->context = GSS_C_NO_CONTEXT; + ap->seq_num = 0; + ap->gss_proc = RPCSEC_GSS_INIT; + + /* + * should not change clnt->cl_auth at this time, so save + * old handle + */ + save_auth = clnt->cl_auth; + clnt->cl_auth = auth; + + /* + * set state for starting context setup + */ + input_token_p = GSS_C_NO_BUFFER; + +next_token: + *gssstat = gss_init_sec_context(minor_stat, + ap->my_cred, + &ap->context, + ap->target_name, + ap->mech_type, + ap->req_flags, + ap->time_req, + NULL, + input_token_p, + actual_mech_type, + &call_arg, + ret_flags, + time_rec); + + if (input_token_p != GSS_C_NO_BUFFER) { + OM_uint32 minor_stat2; + + (void) gss_release_buffer(&minor_stat2, input_token_p); + input_token_p = GSS_C_NO_BUFFER; + } + + if (*gssstat != GSS_S_COMPLETE && *gssstat != GSS_S_CONTINUE_NEEDED) { + + goto cleanup; + } + + /* + * if we got a token, pass it on + */ + if (call_arg.length != 0) { + struct timeval timeout = {30, 0}; + + memset((char *)&call_res, 0, sizeof (call_res)); + callstat = clnt_call(clnt, NULLPROC, + __xdr_rpc_gss_init_arg, (caddr_t)&call_arg, + __xdr_rpc_gss_init_res, (caddr_t)&call_res, + timeout); + (void) gss_release_buffer(minor_stat, &call_arg); + + if (callstat != RPC_SUCCESS) { + goto cleanup; + } + /* + * we have results - note that these need to be freed + */ + free_results = TRUE; + + if (call_res.gss_major != GSS_S_COMPLETE && + call_res.gss_major != GSS_S_CONTINUE_NEEDED) + goto cleanup; + + ap->gss_proc = RPCSEC_GSS_CONTINUE_INIT; + + /* + * check for ctx_handle + */ + if (ap->ctx_handle.length == 0) { + if (call_res.ctx_handle.length == 0) + goto cleanup; + GSS_DUP_BUFFER(ap->ctx_handle, + call_res.ctx_handle); + } else if (!GSS_BUFFERS_EQUAL(ap->ctx_handle, + call_res.ctx_handle)) + goto cleanup; + + /* + * check for token + */ + if (call_res.token.length != 0) { + if (*gssstat == GSS_S_COMPLETE) + goto cleanup; + GSS_DUP_BUFFER(input_token, call_res.token); + input_token_p = &input_token; + + } else if (*gssstat != GSS_S_COMPLETE) + goto cleanup; + + /* save the sequence window value; validate later */ + ap->seq_window = call_res.seq_window; + xdr_free(__xdr_rpc_gss_init_res, (caddr_t)&call_res); + free_results = FALSE; + } + + /* + * results were okay.. continue if necessary + */ + if (*gssstat == GSS_S_CONTINUE_NEEDED) + goto next_token; + + /* + * Validate the sequence window - RFC 2203 section 5.2.3.1 + */ + if (!validate_seqwin(ap)) { + goto cleanup; + } + + /* + * Done! Security context creation is successful. + * Ready for exchanging data. + */ + ap->established = TRUE; + ap->seq_num = 1; + ap->gss_proc = RPCSEC_GSS_DATA; + ap->invalid = FALSE; + + clnt->cl_auth = save_auth; /* restore cl_auth */ + return (TRUE); + +cleanup: + if (ap->context != GSS_C_NO_CONTEXT) + rpc_gss_destroy_pvt(auth); + if (free_results) + xdr_free(__xdr_rpc_gss_init_res, (caddr_t)&call_res); + clnt->cl_auth = save_auth; /* restore cl_auth */ + +/* + * if (rpc_createerr.cf_stat == 0) + * rpc_createerr.cf_stat = RPC_AUTHERROR; + */ + if (rpc_createerr.cf_stat == 0) { + rpc_gss_err.rpc_gss_error = RPC_GSS_ER_SYSTEMERROR; + rpc_gss_err.system_error = RPC_AUTHERROR; + } + + return (FALSE); +} + +/* + * Set service defaults. + */ +bool_t +__rpc_gss_set_defaults(auth, service, qop) + AUTH *auth; + rpc_gss_service_t service; + char *qop; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + char *mech; + OM_uint32 qop_num; + + switch (service) { + case rpc_gss_svc_integrity: + case rpc_gss_svc_privacy: + case rpc_gss_svc_none: + break; + case rpc_gss_svc_default: + service = rpc_gss_svc_integrity; + break; + default: + return (FALSE); + } + + if ((mech = __rpc_gss_oid_to_mech(ap->mech_type)) == NULL) + return (FALSE); + + if (!__rpc_gss_qop_to_num(qop, mech, &qop_num)) + return (FALSE); + + ap->qop = qop_num; + ap->service = service; + return (TRUE); +} + +/* + * Marshall credentials. + */ +static bool_t +marshall_creds(ap, xdrs) + rpc_gss_data *ap; + XDR *xdrs; +{ + rpc_gss_creds ag_creds; + char cred_buf[MAX_AUTH_BYTES]; + struct opaque_auth creds; + XDR cred_xdrs; + + ag_creds.version = ap->version; + ag_creds.gss_proc = ap->gss_proc; + ag_creds.seq_num = ap->seq_num; + ag_creds.service = ap->service; + + /* + * If context has not been set up yet, use NULL handle. + */ + if (ap->ctx_handle.length > 0) + ag_creds.ctx_handle = ap->ctx_handle; + else { + ag_creds.ctx_handle.length = 0; + ag_creds.ctx_handle.value = NULL; + } + + xdrmem_create(&cred_xdrs, (caddr_t)cred_buf, MAX_AUTH_BYTES, + XDR_ENCODE); + if (!__xdr_rpc_gss_creds(&cred_xdrs, &ag_creds)) { + XDR_DESTROY(&cred_xdrs); + return (FALSE); + } + + creds.oa_flavor = RPCSEC_GSS; + creds.oa_base = cred_buf; + creds.oa_length = xdr_getpos(&cred_xdrs); + XDR_DESTROY(&cred_xdrs); + + if (!xdr_opaque_auth(xdrs, &creds)) + return (FALSE); + + return (TRUE); +} + +/* + * Marshall verifier. The verifier is the checksum of the RPC header + * up to and including the credential field. The XDR handle that's + * passed in has the header up to and including the credential field + * encoded. A pointer to the transmit buffer is also passed in. + */ +static bool_t +marshall_verf(ap, xdrs, buf) + rpc_gss_data *ap; + XDR *xdrs; /* send XDR */ + char *buf; /* pointer of send buffer */ +{ + struct opaque_auth verf; + OM_uint32 major, minor; + gss_buffer_desc in_buf, out_buf; + bool_t ret = FALSE; + + /* + * If context is not established yet, use NULL verifier. + */ + if (!ap->established) { + verf.oa_flavor = AUTH_NONE; + verf.oa_base = NULL; + verf.oa_length = 0; + return (xdr_opaque_auth(xdrs, &verf)); + } + + verf.oa_flavor = RPCSEC_GSS; + in_buf.length = xdr_getpos(xdrs); + in_buf.value = buf; + if ((major = gss_sign(&minor, ap->context, ap->qop, &in_buf, + &out_buf)) != GSS_S_COMPLETE) { + if (major == GSS_S_CONTEXT_EXPIRED) { + ap->invalid = TRUE; + } + return (FALSE); + } + verf.oa_base = out_buf.value; + verf.oa_length = out_buf.length; + ret = xdr_opaque_auth(xdrs, &verf); + (void) gss_release_buffer(&minor, &out_buf); + + return (ret); +} + +/* + * Function: rpc_gss_nextverf. Not used. + */ +static void +rpc_gss_nextverf() +{ +} + +/* + * Function: rpc_gss_marshall - not used. + */ +static bool_t +rpc_gss_marshall(auth, xdrs) + AUTH *auth; + XDR *xdrs; +{ + if (!xdr_opaque_auth(xdrs, &auth->ah_cred) || + !xdr_opaque_auth(xdrs, &auth->ah_verf)) + return (FALSE); + return (TRUE); +} + +/* + * Validate sequence window upon a successful RPCSEC_GSS INIT session. + * The sequence window sent back by the server should be verifiable by + * the verifier which is a checksum of the sequence window. + */ +static bool_t +validate_seqwin(rpc_gss_data *ap) +{ + uint_t seq_win_net; + OM_uint32 major = 0, minor = 0; + gss_buffer_desc msg_buf, tok_buf; + int qop_state = 0; + + seq_win_net = (uint_t)htonl(ap->seq_window); + msg_buf.length = sizeof (seq_win_net); + msg_buf.value = (char *)&seq_win_net; + tok_buf.length = ap->verifier->oa_length; + tok_buf.value = ap->verifier->oa_base; + major = gss_verify(&minor, ap->context, &msg_buf, &tok_buf, &qop_state); + if (major != GSS_S_COMPLETE) + return (FALSE); + return (TRUE); +} + +/* + * Validate RPC response verifier from server. The response verifier + * is the checksum of the request sequence number. + */ +static bool_t +rpc_gss_validate(auth, verf) + AUTH *auth; + struct opaque_auth *verf; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + uint_t seq_num_net; + OM_uint32 major, minor; + gss_buffer_desc msg_buf, tok_buf; + int qop_state; + + /* + * If context is not established yet, save the verifier for + * validating the sequence window later at the end of context + * creation session. + */ + if (!ap->established) { + if (ap->verifier == NULL) { + ap->verifier = malloc(sizeof (struct opaque_auth)); + memset(ap->verifier, 0, sizeof (struct opaque_auth)); + if (verf->oa_length > 0) + ap->verifier->oa_base = malloc(verf->oa_length); + } else { + if (ap->verifier->oa_length > 0) + free(ap->verifier->oa_base); + if (verf->oa_length > 0) + ap->verifier->oa_base = malloc(verf->oa_length); + } + ap->verifier->oa_length = verf->oa_length; + bcopy(verf->oa_base, ap->verifier->oa_base, verf->oa_length); + return (TRUE); + } + + seq_num_net = (uint_t)htonl(ap->seq_num); + msg_buf.length = sizeof (seq_num_net); + msg_buf.value = (char *)&seq_num_net; + tok_buf.length = verf->oa_length; + tok_buf.value = verf->oa_base; + major = gss_verify(&minor, ap->context, &msg_buf, &tok_buf, &qop_state); + if (major != GSS_S_COMPLETE) + return (FALSE); + return (TRUE); +} + +/* + * Refresh client context. This is necessary sometimes because the + * server will ocassionally destroy contexts based on LRU method, or + * because of expired credentials. + */ +static bool_t +rpc_gss_refresh(auth, msg) + AUTH *auth; + struct rpc_msg *msg; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + OM_uint32 gssstat, minor_stat; + + /* + * The context needs to be recreated only when the error status + * returned from the server is one of the following: + * RPCSEC_GSS_NOCRED and RPCSEC_GSS_FAILED + * The existing context should not be destroyed unless the above + * error status codes are received or if the context has not + * been set up. + */ + + if (msg->rjcted_rply.rj_why == RPCSEC_GSS_NOCRED || + msg->rjcted_rply.rj_why == RPCSEC_GSS_FAILED || + !ap->established) { + /* + * Destroy the context if necessary. Use the same memory + * for the new context since we've already passed a pointer + * to it to the user. + */ + if (ap->context != GSS_C_NO_CONTEXT) { + (void) gss_delete_sec_context(&minor_stat, &ap->context, + NULL); + ap->context = GSS_C_NO_CONTEXT; + } + if (ap->ctx_handle.length != 0) { + (void) gss_release_buffer(&minor_stat, + &ap->ctx_handle); + ap->ctx_handle.length = 0; + ap->ctx_handle.value = NULL; + } + + /* + * If the context was not already established, don't try to + * recreate it. + */ + if (!ap->established) { + ap->invalid = TRUE; + return (FALSE); + } + + /* + * Recreate context. + */ + if (rpc_gss_seccreate_pvt(&gssstat, &minor_stat, auth, ap, + (gss_OID *)0, (OM_uint32 *)0, (OM_uint32 *)0)) + return (TRUE); + else { + ap->invalid = TRUE; + return (FALSE); + } + } + return (FALSE); +} + +/* + * Destroy a context. + */ +static void +rpc_gss_destroy(auth) + AUTH *auth; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + + rpc_gss_destroy_pvt(auth); + free((char *)ap); + free(auth); +} + +/* + * Private interface to destroy a context without freeing up + * the memory used by it. We need to do this when a refresh + * fails, for example, so the user will still have a handle. + */ +static void +rpc_gss_destroy_pvt(auth) + AUTH *auth; +{ + struct timeval timeout; + OM_uint32 minor_stat; + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + + /* + * If we have a server context id, inform server that we are + * destroying the context. + */ + if (ap->ctx_handle.length != 0) { + ap->gss_proc = RPCSEC_GSS_DESTROY; + timeout.tv_sec = 1; + timeout.tv_usec = 0; + (void) clnt_call(ap->clnt, NULLPROC, xdr_void, NULL, + xdr_void, NULL, timeout); + + (void) gss_release_buffer(&minor_stat, &ap->ctx_handle); + ap->ctx_handle.length = 0; + ap->ctx_handle.value = NULL; + } + + /* + * Destroy local GSS context. + */ + if (ap->context != GSS_C_NO_CONTEXT) { + (void) gss_delete_sec_context(&minor_stat, &ap->context, NULL); + ap->context = GSS_C_NO_CONTEXT; + } + + /* + * Looks like we need to release default credentials if we use it. + * Non-default creds need to be released by user. + */ + if (ap->my_cred == GSS_C_NO_CREDENTIAL) + (void) gss_release_cred(&minor_stat, &ap->my_cred); + + /* + * Release any internal name structures. + */ + if (ap->target_name != NULL) { + (void) gss_release_name(&minor_stat, &ap->target_name); + ap->target_name = NULL; + } + + /* + * Free the verifier saved for sequence window checking. + */ + if (ap->verifier != NULL) { + if (ap->verifier->oa_length > 0) + free(ap->verifier->oa_base); + free(ap->verifier); + ap->verifier = NULL; + } +} + +/* + * Wrap client side data. The encoded header is passed in through + * buf and buflen. The header is up to but not including the + * credential field. + */ +bool_t +__rpc_gss_wrap(auth, buf, buflen, out_xdrs, xdr_func, xdr_ptr) + AUTH *auth; + char *buf; /* encoded header */ + uint_t buflen; /* encoded header length */ + XDR *out_xdrs; + bool_t (*xdr_func)(); + caddr_t xdr_ptr; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + XDR xdrs; + char tmp_buf[512]; + + + /* + * Reject an invalid context. + */ + if (ap->invalid) + return (FALSE); + + /* + * If context is established, bump up sequence number. + */ + if (ap->established) + ap->seq_num++; + + /* + * Create the header in a temporary XDR context and buffer + * before putting it out. + */ + xdrmem_create(&xdrs, tmp_buf, sizeof (tmp_buf), XDR_ENCODE); + if (!XDR_PUTBYTES(&xdrs, buf, buflen)) + return (FALSE); + + /* + * create cred field + */ + if (!marshall_creds(ap, &xdrs)) + return (FALSE); + + /* + * create verifier + */ + if (!marshall_verf(ap, &xdrs, tmp_buf)) + return (FALSE); + + /* + * write out header and destroy temp structures + */ + if (!XDR_PUTBYTES(out_xdrs, tmp_buf, XDR_GETPOS(&xdrs))) + return (FALSE); + XDR_DESTROY(&xdrs); + + /* + * If context is not established, or if neither integrity + * nor privacy is used, just XDR encode data. + */ + if (!ap->established || ap->service == rpc_gss_svc_none) + return ((*xdr_func)(out_xdrs, xdr_ptr)); + + return (__rpc_gss_wrap_data(ap->service, ap->qop, ap->context, + ap->seq_num, out_xdrs, xdr_func, xdr_ptr)); +} + +/* + * Unwrap received data. + */ +bool_t +__rpc_gss_unwrap(auth, in_xdrs, xdr_func, xdr_ptr) + AUTH *auth; + XDR *in_xdrs; + bool_t (*xdr_func)(); + caddr_t xdr_ptr; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + + /* + * If context is not established, of if neither integrity + * nor privacy is used, just XDR encode data. + */ + if (!ap->established || ap->service == rpc_gss_svc_none) + return ((*xdr_func)(in_xdrs, xdr_ptr)); + + return (__rpc_gss_unwrap_data(ap->service, + ap->context, + ap->seq_num, + ap->qop, + in_xdrs, xdr_func, xdr_ptr)); +} + +int +__rpc_gss_max_data_length(auth, max_tp_unit_len) + AUTH *auth; + int max_tp_unit_len; +{ + /*LINTED*/ + rpc_gss_data *ap = AUTH_PRIVATE(auth); + + if (!ap->established || max_tp_unit_len <= 0) + return (0); + + return (__find_max_data_length(ap->service, + ap->context, + ap->qop, + max_tp_unit_len)); +} + +#undef rpc_gss_err + +rpc_gss_error_t rpc_gss_err; +static mutex_t rge_lock; /* protects TSD key creation */ + +rpc_gss_error_t * +__rpc_gss_err() +{ + static thread_key_t rpc_gss_err_key = 0; + rpc_gss_error_t *tsd = 0; + + if (_thr_main()) + return (&rpc_gss_err); + if (_thr_getspecific(rpc_gss_err_key, (void **) &tsd) != 0) { + mutex_lock(&rge_lock); + if (_thr_keycreate(&rpc_gss_err_key, free) != 0) { + mutex_unlock(&rge_lock); + return (&rpc_gss_err); + } + mutex_unlock(&rge_lock); + } + if (!tsd) { + tsd = (rpc_gss_error_t *) + calloc(1, sizeof (rpc_gss_error_t)); + if (_thr_setspecific(rpc_gss_err_key, (void *) tsd) != 0) { + if (tsd) + free(tsd); + return (&rpc_gss_err); + } + memset(tsd, 0, sizeof (rpc_gss_error_t)); + return (tsd); + } + return (tsd); +} + +void +__rpc_gss_get_error(error) + rpc_gss_error_t *error; +{ + + error->rpc_gss_error = rpc_gss_err.rpc_gss_error; + error->system_error = rpc_gss_err.system_error; +} diff --git a/usr/src/lib/rpcsec_gss/rpcsec_gss_misc.c b/usr/src/lib/rpcsec_gss/rpcsec_gss_misc.c new file mode 100644 index 0000000000..e9bf848365 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/rpcsec_gss_misc.c @@ -0,0 +1,346 @@ +/* + * 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 (c) 1986-1995, 1997, 2001 by Sun Microsystems, Inc. + * All rights reserved. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Copyright 1993 OpenVision Technologies, Inc., All Rights Reserved. + * + * $Header: + * /afs/gza.com/product/secure/rel-eng/src/1.1/rpc/RCS/auth_gssapi_misc.c,v + * 1.10 1994/10/27 12:39:23 jik Exp $ + */ + +#include <stdlib.h> +#include <gssapi/gssapi.h> +#include <rpc/rpc.h> +#include <rpc/rpcsec_defs.h> + +/* + * Miscellaneous XDR routines. + */ +bool_t +__xdr_gss_buf(xdrs, buf) + XDR *xdrs; + gss_buffer_t buf; +{ + u_int cast_len, bound_len; + + /* + * We go through this contortion because size_t is a now a ulong, + * GSS-API uses ulongs. + */ + + if (xdrs->x_op != XDR_DECODE) { + bound_len = cast_len = (u_int) buf->length; + } else { + bound_len = (u_int)-1; + } + + if (xdr_bytes(xdrs, (char **)&buf->value, &cast_len, + bound_len) == TRUE) { + if (xdrs->x_op == XDR_DECODE) + buf->length = cast_len; + + return (TRUE); + } + + return (FALSE); +} + +bool_t +__xdr_rpc_gss_creds(xdrs, creds) + XDR *xdrs; + rpc_gss_creds *creds; +{ + if (!xdr_u_int(xdrs, &creds->version) || + !xdr_u_int(xdrs, &creds->gss_proc) || + !xdr_u_int(xdrs, &creds->seq_num) || + !xdr_u_int(xdrs, (u_int *)&creds->service) || + !__xdr_gss_buf(xdrs, &creds->ctx_handle)) + return (FALSE); + return (TRUE); +} + +bool_t +__xdr_rpc_gss_init_arg(xdrs, init_arg) + XDR *xdrs; + rpc_gss_init_arg *init_arg; +{ + if (!__xdr_gss_buf(xdrs, init_arg)) + return (FALSE); + return (TRUE); +} + +bool_t +__xdr_rpc_gss_init_res(xdrs, init_res) + XDR *xdrs; + rpc_gss_init_res *init_res; +{ + if (!__xdr_gss_buf(xdrs, &init_res->ctx_handle) || + !xdr_u_int(xdrs, (u_int *)&init_res->gss_major) || + !xdr_u_int(xdrs, (u_int *)&init_res->gss_minor) || + !xdr_u_int(xdrs, (u_int *)&init_res->seq_window) || + !__xdr_gss_buf(xdrs, &init_res->token)) + return (FALSE); + return (TRUE); +} + +/* + * Generic routine to wrap data used by client and server sides. + */ +bool_t +__rpc_gss_wrap_data(service, qop, context, seq_num, out_xdrs, xdr_func, + xdr_ptr) + OM_uint32 qop; + rpc_gss_service_t service; + gss_ctx_id_t context; + u_int seq_num; + XDR *out_xdrs; + bool_t (*xdr_func)(); + caddr_t xdr_ptr; +{ + OM_uint32 minor; + gss_buffer_desc in_buf, out_buf; + XDR temp_xdrs; + bool_t conf_state; + bool_t ret = FALSE; + u_int bufsiz; + char *buf; + + /* + * Create a temporary XDR/buffer to hold the data to be wrapped. + */ + out_buf.length = 0; + bufsiz = xdr_sizeof(xdr_func, xdr_ptr) + + xdr_sizeof(xdr_u_int, &seq_num); + if ((buf = (char *)malloc(bufsiz)) == NULL) { + fprintf(stderr, dgettext(TEXT_DOMAIN, "malloc failed in " + "__rpc_gss_wrap_data\n")); + return (FALSE); + } + xdrmem_create(&temp_xdrs, buf, bufsiz, XDR_ENCODE); + + /* + * serialize the sequence number into tmp memory + */ + if (!xdr_u_int(&temp_xdrs, &seq_num)) + goto fail; + + /* + * serialize the arguments into tmp memory + */ + if (!(*xdr_func)(&temp_xdrs, xdr_ptr)) + goto fail; + + /* + * Data to be wrapped goes in in_buf. If privacy is used, + * out_buf will have wrapped data (in_buf will no longer be + * needed). If integrity is used, out_buf will have checksum + * which will follow the data in in_buf. + */ + in_buf.length = xdr_getpos(&temp_xdrs); + in_buf.value = temp_xdrs.x_base; + + switch (service) { + case rpc_gss_svc_privacy: + if (gss_seal(&minor, context, TRUE, qop, &in_buf, + &conf_state, &out_buf) != GSS_S_COMPLETE) + goto fail; + in_buf.length = 0; /* in_buf not needed */ + if (!conf_state) + goto fail; + break; + case rpc_gss_svc_integrity: + if (gss_sign(&minor, context, qop, &in_buf, + &out_buf) != GSS_S_COMPLETE) + goto fail; + break; + default: + goto fail; + } + + /* + * write out in_buf and out_buf as needed + */ + if (in_buf.length != 0) { + if (!__xdr_gss_buf(out_xdrs, &in_buf)) + goto fail; + } + + if (!__xdr_gss_buf(out_xdrs, &out_buf)) + goto fail; + ret = TRUE; +fail: + XDR_DESTROY(&temp_xdrs); + if (buf) + (void) free(buf); + if (out_buf.length != 0) + (void) gss_release_buffer(&minor, &out_buf); + return (ret); +} + +/* + * Generic routine to unwrap data used by client and server sides. + */ +bool_t +__rpc_gss_unwrap_data(service, context, seq_num, qop_check, in_xdrs, xdr_func, + xdr_ptr) + rpc_gss_service_t service; + gss_ctx_id_t context; + u_int seq_num; + OM_uint32 qop_check; + XDR *in_xdrs; + bool_t (*xdr_func)(); + caddr_t xdr_ptr; +{ + gss_buffer_desc in_buf, out_buf; + XDR temp_xdrs; + u_int seq_num2; + bool_t conf; + OM_uint32 major = GSS_S_COMPLETE, minor = 0; + int qop; + + in_buf.value = NULL; + out_buf.value = NULL; + + /* + * Pull out wrapped data. For privacy service, this is the + * encrypted data. For integrity service, this is the data + * followed by a checksum. + */ + if (!__xdr_gss_buf(in_xdrs, &in_buf)) + return (FALSE); + + if (service == rpc_gss_svc_privacy) { + major = gss_unseal(&minor, context, &in_buf, &out_buf, &conf, + &qop); + free(in_buf.value); + if (major != GSS_S_COMPLETE) + return (FALSE); + /* + * Keep the returned token (unencrypted data) in in_buf. + */ + in_buf.length = out_buf.length; + in_buf.value = out_buf.value; + + /* + * If privacy was not used, or if QOP is not what we are + * expecting, fail. + */ + if (!conf || qop != qop_check) + goto fail; + + } else if (service == rpc_gss_svc_integrity) { + if (!__xdr_gss_buf(in_xdrs, &out_buf)) + return (FALSE); + major = gss_verify(&minor, context, &in_buf, &out_buf, &qop); + free(out_buf.value); + if (major != GSS_S_COMPLETE) { + free(in_buf.value); + return (FALSE); + } + + /* + * If QOP is not what we are expecting, fail. + */ + if (qop != qop_check) + goto fail; + } + + xdrmem_create(&temp_xdrs, in_buf.value, in_buf.length, XDR_DECODE); + + /* + * The data consists of the sequence number followed by the + * arguments. Make sure sequence number is what we are + * expecting (i.e., the value in the header). + */ + if (!xdr_u_int(&temp_xdrs, &seq_num2)) + goto fail; + if (seq_num2 != seq_num) + goto fail; + + /* + * Deserialize the arguments into xdr_ptr, and release in_buf. + */ + if (!(*xdr_func)(&temp_xdrs, xdr_ptr)) + goto fail; + + if (service == rpc_gss_svc_privacy) + (void) gss_release_buffer(&minor, &in_buf); + else + free(in_buf.value); + XDR_DESTROY(&temp_xdrs); + return (TRUE); +fail: + XDR_DESTROY(&temp_xdrs); + if (service == rpc_gss_svc_privacy) + (void) gss_release_buffer(&minor, &in_buf); + else + free(in_buf.value); + return (FALSE); +} + +/*ARGSUSED*/ +int +__find_max_data_length(service, context, qop, max_tp_unit_len) + rpc_gss_service_t service; + gss_ctx_id_t context; + OM_uint32 qop; + int max_tp_unit_len; +{ + int conf; + OM_uint32 maj_stat = GSS_S_COMPLETE, min_stat = 0; + OM_uint32 max_input_size; + int ret_val = 0; + + if (service == rpc_gss_svc_integrity || service == rpc_gss_svc_default) + conf = 0; + else if (service == rpc_gss_svc_privacy) + conf = 1; + else if (service == rpc_gss_svc_none) + return (max_tp_unit_len); + + maj_stat = gss_wrap_size_limit(&min_stat, + context, conf, qop, + max_tp_unit_len, &max_input_size); + + /* + * max_input_size may result in negative value + */ + if (maj_stat == GSS_S_COMPLETE) { + if ((int)max_input_size <= 0) + ret_val = 0; + else + ret_val = (int)(max_input_size); + } else { + fprintf(stderr, dgettext(TEXT_DOMAIN, + "gss_wrap_size_limit failed in " + "__find_max_data_length\n")); + } + + return (ret_val); +} diff --git a/usr/src/lib/rpcsec_gss/rpcsec_gss_utils.c b/usr/src/lib/rpcsec_gss/rpcsec_gss_utils.c new file mode 100644 index 0000000000..7b7e1e2f75 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/rpcsec_gss_utils.c @@ -0,0 +1,336 @@ +/* + * 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 (c) 1986-1995,1997, by Sun Microsystems, Inc. + * All rights reserved. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <strings.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <syslog.h> +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_ext.h> +#include <rpc/rpc.h> +#include <rpc/rpcsec_defs.h> +#include <rpc/trace.h> + +#define SVC_INTEGRITY "integrity" +#define SVC_PRIVACY "privacy" +#define SVC_NONE "none" +#define SVC_DEFAULT "default" + +#define MCALL_MSG_SIZE 24 +/* + * Private data kept per client handle + */ +struct cu_data { + int cu_fd; /* connections fd */ + bool_t cu_closeit; /* opened by library */ + struct netbuf cu_raddr; /* remote address */ + struct timeval cu_wait; /* retransmit interval */ + struct timeval cu_total; /* total time for the call */ + struct rpc_err cu_error; + struct t_unitdata *cu_tr_data; + XDR cu_outxdrs; + char *cu_outbuf_start; + char cu_outbuf[MCALL_MSG_SIZE]; + u_int cu_xdrpos; + u_int cu_sendsz; /* send size */ + u_int cu_recvsz; /* recv size */ + struct pollfd pfdp; + char cu_inbuf[1]; +}; + +/* + * Internal utility routines. + */ +bool_t +__rpc_gss_mech_to_oid(mech, oid) + char *mech; + rpc_gss_OID *oid; +{ + + if (__gss_mech_to_oid(mech, (gss_OID*)oid) != GSS_S_COMPLETE) + return (FALSE); + + return (TRUE); +} + +char * +__rpc_gss_oid_to_mech(oid) + rpc_gss_OID oid; +{ + + return ((char *)__gss_oid_to_mech((const gss_OID)oid)); +} + + +bool_t +__rpc_gss_qop_to_num(qop, mech, num) + char *qop; + char *mech; + OM_uint32 *num; +{ + + if (__gss_qop_to_num(qop, mech, num) != GSS_S_COMPLETE) + return (FALSE); + return (TRUE); +} + +char * +__rpc_gss_num_to_qop(mech, num) + char *mech; + OM_uint32 num; +{ + char *qop; + + if (__gss_num_to_qop(mech, num, &qop) != GSS_S_COMPLETE) + return (NULL); + return (qop); +} + +bool_t +__rpc_gss_svc_to_num(svc, num) + char *svc; + rpc_gss_service_t *num; +{ + if (strcasecmp(svc, SVC_INTEGRITY) == 0) + *num = rpc_gss_svc_integrity; + else if (strcasecmp(svc, SVC_PRIVACY) == 0) + *num = rpc_gss_svc_privacy; + else if (strcasecmp(svc, SVC_NONE) == 0) + *num = rpc_gss_svc_none; + else if (strcasecmp(svc, SVC_DEFAULT) == 0) + *num = rpc_gss_svc_default; + else + return (FALSE); + return (TRUE); +} + +char * +__rpc_gss_num_to_svc(num) + rpc_gss_service_t num; +{ + switch (num) { + case rpc_gss_svc_integrity: + return (strdup(SVC_INTEGRITY)); + case rpc_gss_svc_privacy: + return (strdup(SVC_PRIVACY)); + case rpc_gss_svc_none: + return (strdup(SVC_NONE)); + case rpc_gss_svc_default: + return (strdup(SVC_DEFAULT)); + default: + return (NULL); + } +} + +/* + * Given the user name, node, and security domain, get the mechanism + * specific principal name (for the user name) in exported form. + */ +bool_t +__rpc_gss_get_principal_name(principal, mech, user, node, secdomain) + rpc_gss_principal_t *principal; + char *mech; + char *user; + char *node; + char *secdomain; +{ + gss_name_t gss_name, gss_canon_name; + gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER; + char user_name[256], *s; + gss_OID mech_oid; + int nlen = 0, slen = 0, plen; + OM_uint32 major, minor; + + *principal = NULL; + if (user == NULL || strlen(user) == 0) + return (FALSE); + + if (!__rpc_gss_mech_to_oid(mech, (rpc_gss_OID *) &mech_oid)) { + syslog(LOG_ERR, "rpc_gss_get_principal_name: can't get" + "mech oid"); + return (FALSE); + } + + if (secdomain != NULL) + slen = strlen(secdomain); + + if (node != NULL) + nlen = strlen(node); + + strcpy(user_name, user); + if (nlen > 0) { + strcat(user_name, "/"); + strcat(user_name, node); + } + + if (slen > 0) { + strcat(user_name, "@"); + strcat(user_name, secdomain); + } + + name_buf.value = user_name; + name_buf.length = strlen(user_name); + + /* + * Convert a text string to a GSSAPI Internal name. + */ + if ((major = gss_import_name(&minor, &name_buf, + (gss_OID) GSS_C_NT_USER_NAME, &gss_name)) != GSS_S_COMPLETE) { + syslog(LOG_ERR, "rpc_gss_get_principal_name: import name" + "failed 0x%x", major); + return (FALSE); + } + + /* + * Convert the GSSAPI Internal name to a MN - Mechanism Name + */ + if ((major = gss_canonicalize_name(&minor, gss_name, mech_oid, + &gss_canon_name)) != GSS_S_COMPLETE) { + syslog(LOG_ERR, "rpc_gss_get_principal_name: canonicalize name" + "failed 0x%x", major); + gss_release_name(&minor, &gss_name); + return (FALSE); + } + gss_release_name(&minor, &gss_name); + + /* + * Convert the MN Internal name to an exported flat name, so + * it is suitable for binary comparison. + */ + if ((major = gss_export_name(&minor, gss_canon_name, &name_buf)) != + GSS_S_COMPLETE) { + syslog(LOG_ERR, "rpc_gss_get_principal_name: export name" + "failed %x", major); + gss_release_name(&minor, &gss_canon_name); + return (FALSE); + } + gss_release_name(&minor, &gss_canon_name); + + /* + * Put the exported name into rpc_gss_principal_t structure. + */ + plen = RNDUP(name_buf.length) + sizeof (int); + (*principal) = (rpc_gss_principal_t)malloc(plen); + if ((*principal) == NULL) { + gss_release_buffer(&minor, &name_buf); + return (FALSE); + } + bzero((caddr_t)(*principal), plen); + (*principal)->len = RNDUP(name_buf.length); + s = (*principal)->name; + memcpy(s, name_buf.value, name_buf.length); + gss_release_buffer(&minor, &name_buf); + + return (TRUE); +} + +/* + * Return supported mechanisms. + */ +char ** +__rpc_gss_get_mechanisms() +{ + static char *mech_list[MAX_MECH_OID_PAIRS+1]; + + *mech_list = NULL; + __gss_get_mechanisms(mech_list, MAX_MECH_OID_PAIRS+1); + return (mech_list); +} + +/* + * For a given mechanism, return information about it. + */ +/* + * static char *krb5_qop_list[] = {Q_DEFAULT, NULL}; + */ + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ +/* Don't know how to get the service type for a given mech. */ +/* "service" should NOT be there! */ +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!*!!!!!!!!!!! */ + +char ** +__rpc_gss_get_mech_info(mech, service) + char *mech; + rpc_gss_service_t *service; +{ + char **l; + + l = calloc(MAX_QOPS_PER_MECH + 1, sizeof (char *)); + if (l == NULL) + return (NULL); + + if (__gss_get_mech_info(mech, l) != GSS_S_COMPLETE) { + free(l); + return (NULL); + } + /* !!!!!!!!!!!!!!!! */ + *service = rpc_gss_svc_privacy; /* What service type? */ + /* !!!!!!!!!!!!!!!! */ + return (l); +} + +/* + * Returns highest and lowest versions of RPCSEC_GSS flavor supported. + */ +bool_t +__rpc_gss_get_versions(vers_hi, vers_lo) + u_int *vers_hi; + u_int *vers_lo; +{ + *vers_hi = RPCSEC_GSS_VERSION; + *vers_lo = RPCSEC_GSS_VERSION; + return (TRUE); +} + +/* + * Check if a mechanism is installed. + */ +bool_t +__rpc_gss_is_installed(mech) + char *mech; +{ + char **l; + + if (mech == NULL) + return (FALSE); + + if ((l = __rpc_gss_get_mechanisms()) == NULL) + return (FALSE); + + while (*l != NULL) { + if (strcmp(*l, mech) == 0) + return (TRUE); + l++; + } + return (FALSE); +} diff --git a/usr/src/lib/rpcsec_gss/sparc/Makefile b/usr/src/lib/rpcsec_gss/sparc/Makefile new file mode 100644 index 0000000000..3d1000e3e6 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/sparc/Makefile @@ -0,0 +1,38 @@ +# +# 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 1997-2003 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +# ident "%Z%%M% %I% %E% SMI" +# +# lib/rpcsec_gss/sparc/Makefile + +include ../Makefile.com + +LIBS = $(DYNLIB) + +.KEEP_STATE: + +all: $(LIBS) + +install: all $(ROOTLIBS) $(ROOTLINKS) diff --git a/usr/src/lib/rpcsec_gss/sparcv9/Makefile b/usr/src/lib/rpcsec_gss/sparcv9/Makefile new file mode 100644 index 0000000000..2a9866a38c --- /dev/null +++ b/usr/src/lib/rpcsec_gss/sparcv9/Makefile @@ -0,0 +1,40 @@ +# +# 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 (c) 1997, by Sun Microsystems, Inc. +# All rights reserved. +# +#ident "%Z%%M% %I% %E% SMI" +# +# lib/rpcsec_gss/sparcv9/Makefile + +include ../Makefile.com +include ../../Makefile.lib.64 + +LIBS= $(DYNLIB) + +.KEEP_STATE: + +all: $(LIBS) + +install: all $(ROOTLIBS64) $(ROOTLINKS64) + diff --git a/usr/src/lib/rpcsec_gss/svc_rpcsec_gss.c b/usr/src/lib/rpcsec_gss/svc_rpcsec_gss.c new file mode 100644 index 0000000000..38fd1efd81 --- /dev/null +++ b/usr/src/lib/rpcsec_gss/svc_rpcsec_gss.c @@ -0,0 +1,1698 @@ +/* + * 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; +} |