summaryrefslogtreecommitdiffstats
path: root/lib/librpcsec_gss/svc_rpcsec_gss.c
diff options
context:
space:
mode:
authordfr <dfr@FreeBSD.org>2008-08-06 14:02:05 +0000
committerdfr <dfr@FreeBSD.org>2008-08-06 14:02:05 +0000
commitea3d7030c0c6118b636ea8909a5583b94d819e3a (patch)
treea928d209076dec713f636439ec8dc5be13863460 /lib/librpcsec_gss/svc_rpcsec_gss.c
parent627a3ab3ef939e027409fe287f7e3c401c373003 (diff)
downloadFreeBSD-src-ea3d7030c0c6118b636ea8909a5583b94d819e3a.zip
FreeBSD-src-ea3d7030c0c6118b636ea8909a5583b94d819e3a.tar.gz
Add an implementation of the RPCSEC_GSS authentication protocol for RPC. This
is based on an old implementation from the University of Michigan with lots of changes and fixes by me and the addition of a Solaris-compatible API. Sponsored by: Isilon Systems Reviewed by: alfred
Diffstat (limited to 'lib/librpcsec_gss/svc_rpcsec_gss.c')
-rw-r--r--lib/librpcsec_gss/svc_rpcsec_gss.c1214
1 files changed, 1214 insertions, 0 deletions
diff --git a/lib/librpcsec_gss/svc_rpcsec_gss.c b/lib/librpcsec_gss/svc_rpcsec_gss.c
new file mode 100644
index 0000000..7eff62a
--- /dev/null
+++ b/lib/librpcsec_gss/svc_rpcsec_gss.c
@@ -0,0 +1,1214 @@
+/*-
+ * Copyright (c) 2008 Doug Rabson
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+/*
+ svc_rpcsec_gss.c
+
+ Copyright (c) 2000 The Regents of the University of Michigan.
+ All rights reserved.
+
+ Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
+ All rights reserved, all wrongs reversed.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the University nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ $Id: svc_auth_gss.c,v 1.27 2002/01/15 15:43:00 andros Exp $
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pwd.h>
+#include <grp.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/queue.h>
+#include <rpc/rpc.h>
+#include <rpc/rpcsec_gss.h>
+#include "rpcsec_gss_int.h"
+
+static bool_t svc_rpc_gss_initialised = FALSE;
+
+static bool_t svc_rpc_gss_wrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
+static bool_t svc_rpc_gss_unwrap(SVCAUTH *, XDR *, xdrproc_t, caddr_t);
+static enum auth_stat svc_rpc_gss(struct svc_req *, struct rpc_msg *);
+
+static struct svc_auth_ops svc_auth_gss_ops = {
+ svc_rpc_gss_wrap,
+ svc_rpc_gss_unwrap,
+};
+
+struct svc_rpc_gss_callback {
+ SLIST_ENTRY(svc_rpc_gss_callback) cb_link;
+ rpc_gss_callback_t cb_callback;
+};
+static SLIST_HEAD(svc_rpc_gss_callback_list, svc_rpc_gss_callback)
+ svc_rpc_gss_callbacks = SLIST_HEAD_INITIALIZER(&svc_rpc_gss_callbacks);
+
+struct svc_rpc_gss_svc_name {
+ SLIST_ENTRY(svc_rpc_gss_svc_name) sn_link;
+ char *sn_principal;
+ gss_OID sn_mech;
+ u_int sn_req_time;
+ gss_cred_id_t sn_cred;
+ u_int sn_program;
+ u_int sn_version;
+};
+static SLIST_HEAD(svc_rpc_gss_svc_name_list, svc_rpc_gss_svc_name)
+ svc_rpc_gss_svc_names = SLIST_HEAD_INITIALIZER(&svc_rpc_gss_svc_names);
+
+enum svc_rpc_gss_client_state {
+ CLIENT_NEW, /* still authenticating */
+ CLIENT_ESTABLISHED, /* context established */
+ CLIENT_STALE /* garbage to collect */
+};
+
+#define SVC_RPC_GSS_SEQWINDOW 128
+
+struct svc_rpc_gss_client {
+ TAILQ_ENTRY(svc_rpc_gss_client) cl_link;
+ TAILQ_ENTRY(svc_rpc_gss_client) cl_alllink;
+ uint32_t cl_id;
+ time_t cl_expiration; /* when to gc */
+ enum svc_rpc_gss_client_state cl_state; /* client state */
+ bool_t cl_locked; /* fixed service+qop */
+ gss_ctx_id_t cl_ctx; /* context id */
+ gss_cred_id_t cl_creds; /* delegated creds */
+ gss_name_t cl_cname; /* client name */
+ struct svc_rpc_gss_svc_name *cl_sname; /* server name used */
+ rpc_gss_rawcred_t cl_rawcred; /* raw credentials */
+ rpc_gss_ucred_t cl_ucred; /* unix-style credentials */
+ bool_t cl_done_callback; /* TRUE after call */
+ void *cl_cookie; /* user cookie from callback */
+ gid_t cl_gid_storage[NGRPS];
+ gss_OID cl_mech; /* mechanism */
+ gss_qop_t cl_qop; /* quality of protection */
+ u_int cl_seq; /* current sequence number */
+ u_int cl_win; /* sequence window size */
+ u_int cl_seqlast; /* sequence window origin */
+ uint32_t cl_seqmask[SVC_RPC_GSS_SEQWINDOW/32]; /* bitmask of seqnums */
+ gss_buffer_desc cl_verf; /* buffer for verf checksum */
+};
+TAILQ_HEAD(svc_rpc_gss_client_list, svc_rpc_gss_client);
+
+#define CLIENT_HASH_SIZE 256
+#define CLIENT_MAX 128
+struct svc_rpc_gss_client_list svc_rpc_gss_client_hash[CLIENT_HASH_SIZE];
+struct svc_rpc_gss_client_list svc_rpc_gss_clients;
+static size_t svc_rpc_gss_client_count;
+static uint32_t svc_rpc_gss_next_clientid = 1;
+
+#ifdef __GNUC__
+static void svc_rpc_gss_init(void) __attribute__ ((constructor));
+#endif
+
+static void
+svc_rpc_gss_init(void)
+{
+ int i;
+
+ if (!svc_rpc_gss_initialised) {
+ for (i = 0; i < CLIENT_HASH_SIZE; i++)
+ TAILQ_INIT(&svc_rpc_gss_client_hash[i]);
+ TAILQ_INIT(&svc_rpc_gss_clients);
+ svc_auth_reg(RPCSEC_GSS, svc_rpc_gss);
+ svc_rpc_gss_initialised = TRUE;
+ }
+}
+
+bool_t
+rpc_gss_set_callback(rpc_gss_callback_t *cb)
+{
+ struct svc_rpc_gss_callback *scb;
+
+ scb = malloc(sizeof(struct svc_rpc_gss_callback));
+ if (!scb) {
+ _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
+ return (FALSE);
+ }
+ scb->cb_callback = *cb;
+ SLIST_INSERT_HEAD(&svc_rpc_gss_callbacks, scb, cb_link);
+
+ return (TRUE);
+}
+
+bool_t
+rpc_gss_set_svc_name(const char *principal, const char *mechanism,
+ u_int req_time, u_int program, u_int version)
+{
+ OM_uint32 maj_stat, min_stat;
+ struct svc_rpc_gss_svc_name *sname;
+ gss_buffer_desc namebuf;
+ gss_name_t name;
+ gss_OID mech_oid;
+ gss_OID_set_desc oid_set;
+ gss_cred_id_t cred;
+
+ svc_rpc_gss_init();
+
+ if (!rpc_gss_mech_to_oid(mechanism, &mech_oid))
+ return (FALSE);
+ oid_set.count = 1;
+ oid_set.elements = mech_oid;
+
+ namebuf.value = (void *)(intptr_t) principal;
+ namebuf.length = strlen(principal);
+
+ maj_stat = gss_import_name(&min_stat, &namebuf,
+ GSS_C_NT_HOSTBASED_SERVICE, &name);
+ if (maj_stat != GSS_S_COMPLETE)
+ return (FALSE);
+
+ maj_stat = gss_acquire_cred(&min_stat, name,
+ req_time, &oid_set, GSS_C_ACCEPT, &cred, NULL, NULL);
+ if (maj_stat != GSS_S_COMPLETE)
+ return (FALSE);
+
+ gss_release_name(&min_stat, &name);
+
+ sname = malloc(sizeof(struct svc_rpc_gss_svc_name));
+ if (!sname)
+ return (FALSE);
+ sname->sn_principal = strdup(principal);
+ sname->sn_mech = mech_oid;
+ sname->sn_req_time = req_time;
+ sname->sn_cred = cred;
+ sname->sn_program = program;
+ sname->sn_version = version;
+ SLIST_INSERT_HEAD(&svc_rpc_gss_svc_names, sname, sn_link);
+
+ return (TRUE);
+}
+
+bool_t
+rpc_gss_get_principal_name(rpc_gss_principal_t *principal,
+ const char *mech, const char *name, const char *node, const char *domain)
+{
+ OM_uint32 maj_stat, min_stat;
+ gss_OID mech_oid;
+ size_t namelen;
+ gss_buffer_desc buf;
+ gss_name_t gss_name, gss_mech_name;
+ rpc_gss_principal_t result;
+
+ svc_rpc_gss_init();
+
+ if (!rpc_gss_mech_to_oid(mech, &mech_oid))
+ return (FALSE);
+
+ /*
+ * Construct a gss_buffer containing the full name formatted
+ * as "name/node@domain" where node and domain are optional.
+ */
+ namelen = strlen(name);
+ if (node) {
+ namelen += strlen(node) + 1;
+ }
+ if (domain) {
+ namelen += strlen(domain) + 1;
+ }
+
+ buf.value = malloc(namelen);
+ buf.length = namelen;
+ strcpy((char *) buf.value, name);
+ if (node) {
+ strcat((char *) buf.value, "/");
+ strcat((char *) buf.value, node);
+ }
+ if (domain) {
+ strcat((char *) buf.value, "@");
+ strcat((char *) buf.value, domain);
+ }
+
+ /*
+ * Convert that to a gss_name_t and then convert that to a
+ * mechanism name in the selected mechanism.
+ */
+ maj_stat = gss_import_name(&min_stat, &buf,
+ GSS_C_NT_USER_NAME, &gss_name);
+ free(buf.value);
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_import_name", mech_oid, maj_stat, min_stat);
+ return (FALSE);
+ }
+ maj_stat = gss_canonicalize_name(&min_stat, gss_name, mech_oid,
+ &gss_mech_name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_canonicalize_name", mech_oid, maj_stat,
+ min_stat);
+ gss_release_name(&min_stat, &gss_name);
+ return (FALSE);
+ }
+ gss_release_name(&min_stat, &gss_name);
+
+ /*
+ * Export the mechanism name and use that to construct the
+ * rpc_gss_principal_t result.
+ */
+ maj_stat = gss_export_name(&min_stat, gss_mech_name, &buf);
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_export_name", mech_oid, maj_stat, min_stat);
+ gss_release_name(&min_stat, &gss_mech_name);
+ return (FALSE);
+ }
+ gss_release_name(&min_stat, &gss_mech_name);
+
+ result = malloc(sizeof(int) + buf.length);
+ if (!result) {
+ gss_release_buffer(&min_stat, &buf);
+ return (FALSE);
+ }
+ result->len = buf.length;
+ memcpy(result->name, buf.value, buf.length);
+ gss_release_buffer(&min_stat, &buf);
+
+ *principal = result;
+ return (TRUE);
+}
+
+bool_t
+rpc_gss_getcred(struct svc_req *req, rpc_gss_rawcred_t **rcred,
+ rpc_gss_ucred_t **ucred, void **cookie)
+{
+ struct svc_rpc_gss_client *client;
+
+ if (req->rq_cred.oa_flavor != RPCSEC_GSS)
+ return (FALSE);
+
+ client = req->rq_clntcred;
+ if (rcred)
+ *rcred = &client->cl_rawcred;
+ if (ucred)
+ *ucred = &client->cl_ucred;
+ if (cookie)
+ *cookie = client->cl_cookie;
+ return (TRUE);
+}
+
+int
+rpc_gss_svc_max_data_length(struct svc_req *req, int max_tp_unit_len)
+{
+ struct svc_rpc_gss_client *client = req->rq_clntcred;
+ int want_conf;
+ OM_uint32 max;
+ OM_uint32 maj_stat, min_stat;
+ int result;
+
+ switch (client->cl_rawcred.service) {
+ case rpc_gss_svc_none:
+ return (max_tp_unit_len);
+ break;
+
+ case rpc_gss_svc_default:
+ case rpc_gss_svc_integrity:
+ want_conf = FALSE;
+ break;
+
+ case rpc_gss_svc_privacy:
+ want_conf = TRUE;
+ break;
+
+ default:
+ return (0);
+ }
+
+ maj_stat = gss_wrap_size_limit(&min_stat, client->cl_ctx, want_conf,
+ client->cl_qop, max_tp_unit_len, &max);
+
+ if (maj_stat == GSS_S_COMPLETE) {
+ result = (int) max;
+ if (result < 0)
+ result = 0;
+ return (result);
+ } else {
+ log_status("gss_wrap_size_limit", client->cl_mech,
+ maj_stat, min_stat);
+ return (0);
+ }
+}
+
+static struct svc_rpc_gss_client *
+svc_rpc_gss_find_client(uint32_t clientid)
+{
+ struct svc_rpc_gss_client *client;
+ struct svc_rpc_gss_client_list *list;
+
+
+ log_debug("in svc_rpc_gss_find_client(%d)", clientid);
+
+ list = &svc_rpc_gss_client_hash[clientid % CLIENT_HASH_SIZE];
+ TAILQ_FOREACH(client, list, cl_link) {
+ if (client->cl_id == clientid) {
+ /*
+ * Move this client to the front of the LRU
+ * list.
+ */
+ TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
+ TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client,
+ cl_alllink);
+ return client;
+ }
+ }
+
+ return (NULL);
+}
+
+static struct svc_rpc_gss_client *
+svc_rpc_gss_create_client(void)
+{
+ struct svc_rpc_gss_client *client;
+ struct svc_rpc_gss_client_list *list;
+
+ log_debug("in svc_rpc_gss_create_client()");
+
+ client = mem_alloc(sizeof(struct svc_rpc_gss_client));
+ memset(client, 0, sizeof(struct svc_rpc_gss_client));
+ client->cl_id = svc_rpc_gss_next_clientid++;
+ list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
+ TAILQ_INSERT_HEAD(list, client, cl_link);
+ TAILQ_INSERT_HEAD(&svc_rpc_gss_clients, client, cl_alllink);
+
+ /*
+ * Start the client off with a short expiration time. We will
+ * try to get a saner value from the client creds later.
+ */
+ client->cl_state = CLIENT_NEW;
+ client->cl_locked = FALSE;
+ client->cl_expiration = time(0) + 5*60;
+ svc_rpc_gss_client_count++;
+
+ return (client);
+}
+
+static void
+svc_rpc_gss_destroy_client(struct svc_rpc_gss_client *client)
+{
+ struct svc_rpc_gss_client_list *list;
+ OM_uint32 min_stat;
+
+ log_debug("in svc_rpc_gss_destroy_client()");
+
+ if (client->cl_ctx)
+ gss_delete_sec_context(&min_stat,
+ &client->cl_ctx, GSS_C_NO_BUFFER);
+
+ if (client->cl_cname)
+ gss_release_name(&min_stat, &client->cl_cname);
+
+ if (client->cl_rawcred.client_principal)
+ free(client->cl_rawcred.client_principal);
+
+ if (client->cl_verf.value)
+ gss_release_buffer(&min_stat, &client->cl_verf);
+
+ list = &svc_rpc_gss_client_hash[client->cl_id % CLIENT_HASH_SIZE];
+ TAILQ_REMOVE(list, client, cl_link);
+ TAILQ_REMOVE(&svc_rpc_gss_clients, client, cl_alllink);
+ svc_rpc_gss_client_count--;
+ mem_free(client, sizeof(*client));
+}
+
+static void
+svc_rpc_gss_timeout_clients(void)
+{
+ struct svc_rpc_gss_client *client;
+ struct svc_rpc_gss_client *nclient;
+ time_t now = time(0);
+
+ log_debug("in svc_rpc_gss_timeout_clients()");
+ /*
+ * First enforce the max client limit. We keep
+ * svc_rpc_gss_clients in LRU order.
+ */
+ while (svc_rpc_gss_client_count > CLIENT_MAX)
+ svc_rpc_gss_destroy_client(TAILQ_LAST(&svc_rpc_gss_clients,
+ svc_rpc_gss_client_list));
+ TAILQ_FOREACH_SAFE(client, &svc_rpc_gss_clients, cl_alllink, nclient) {
+ if (client->cl_state == CLIENT_STALE
+ || now > client->cl_expiration) {
+ log_debug("expiring client %p", client);
+ svc_rpc_gss_destroy_client(client);
+ }
+ }
+}
+
+#ifdef DEBUG
+/*
+ * OID<->string routines. These are uuuuugly.
+ */
+static OM_uint32
+gss_oid_to_str(OM_uint32 *minor_status, gss_OID oid, gss_buffer_t oid_str)
+{
+ char numstr[128];
+ unsigned long number;
+ int numshift;
+ size_t string_length;
+ size_t i;
+ unsigned char *cp;
+ char *bp;
+
+ /* Decoded according to krb5/gssapi_krb5.c */
+
+ /* First determine the size of the string */
+ string_length = 0;
+ number = 0;
+ numshift = 0;
+ cp = (unsigned char *) oid->elements;
+ number = (unsigned long) cp[0];
+ sprintf(numstr, "%ld ", number/40);
+ string_length += strlen(numstr);
+ sprintf(numstr, "%ld ", number%40);
+ string_length += strlen(numstr);
+ for (i=1; i<oid->length; i++) {
+ if ( (size_t) (numshift+7) < (sizeof(unsigned long)*8)) {
+ number = (number << 7) | (cp[i] & 0x7f);
+ numshift += 7;
+ }
+ else {
+ *minor_status = 0;
+ return(GSS_S_FAILURE);
+ }
+ if ((cp[i] & 0x80) == 0) {
+ sprintf(numstr, "%ld ", number);
+ string_length += strlen(numstr);
+ number = 0;
+ numshift = 0;
+ }
+ }
+ /*
+ * If we get here, we've calculated the length of "n n n ... n ". Add 4
+ * here for "{ " and "}\0".
+ */
+ string_length += 4;
+ if ((bp = (char *) malloc(string_length))) {
+ strcpy(bp, "{ ");
+ number = (unsigned long) cp[0];
+ sprintf(numstr, "%ld ", number/40);
+ strcat(bp, numstr);
+ sprintf(numstr, "%ld ", number%40);
+ strcat(bp, numstr);
+ number = 0;
+ cp = (unsigned char *) oid->elements;
+ for (i=1; i<oid->length; i++) {
+ number = (number << 7) | (cp[i] & 0x7f);
+ if ((cp[i] & 0x80) == 0) {
+ sprintf(numstr, "%ld ", number);
+ strcat(bp, numstr);
+ number = 0;
+ }
+ }
+ strcat(bp, "}");
+ oid_str->length = strlen(bp)+1;
+ oid_str->value = (void *) bp;
+ *minor_status = 0;
+ return(GSS_S_COMPLETE);
+ }
+ *minor_status = 0;
+ return(GSS_S_FAILURE);
+}
+#endif
+
+static void
+svc_rpc_gss_build_ucred(struct svc_rpc_gss_client *client,
+ const gss_name_t name)
+{
+ OM_uint32 maj_stat, min_stat;
+ char buf[128];
+ uid_t uid;
+ struct passwd pwd, *pw;
+ rpc_gss_ucred_t *uc = &client->cl_ucred;
+
+ uc->uid = 65534;
+ uc->gid = 65534;
+ uc->gidlen = 0;
+ uc->gidlist = client->cl_gid_storage;
+
+ maj_stat = gss_pname_to_uid(&min_stat, name, client->cl_mech, &uid);
+ if (maj_stat != GSS_S_COMPLETE)
+ return;
+
+ getpwuid_r(uid, &pwd, buf, sizeof(buf), &pw);
+ if (pw) {
+ int len = NGRPS;
+ uc->uid = pw->pw_uid;
+ uc->gid = pw->pw_gid;
+ uc->gidlist = client->cl_gid_storage;
+ getgrouplist(pw->pw_name, pw->pw_gid, uc->gidlist, &len);
+ uc->gidlen = len;
+ }
+}
+
+static bool_t
+svc_rpc_gss_accept_sec_context(struct svc_rpc_gss_client *client,
+ struct svc_req *rqst,
+ struct rpc_gss_init_res *gr,
+ struct rpc_gss_cred *gc)
+{
+ gss_buffer_desc recv_tok;
+ gss_OID mech;
+ OM_uint32 maj_stat = 0, min_stat = 0, ret_flags;
+ OM_uint32 cred_lifetime;
+ struct svc_rpc_gss_svc_name *sname;
+
+ log_debug("in svc_rpc_gss_accept_context()");
+
+ /* Deserialize arguments. */
+ memset(&recv_tok, 0, sizeof(recv_tok));
+
+ if (!svc_getargs(rqst->rq_xprt,
+ (xdrproc_t) xdr_gss_buffer_desc,
+ (caddr_t) &recv_tok)) {
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+
+ /*
+ * First time round, try all the server names we have until
+ * one matches. Afterwards, stick with that one.
+ */
+ if (!client->cl_sname) {
+ SLIST_FOREACH(sname, &svc_rpc_gss_svc_names, sn_link) {
+ if (sname->sn_program == rqst->rq_prog
+ && sname->sn_version == rqst->rq_vers) {
+ gr->gr_major = gss_accept_sec_context(
+ &gr->gr_minor,
+ &client->cl_ctx,
+ sname->sn_cred,
+ &recv_tok,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &client->cl_cname,
+ &mech,
+ &gr->gr_token,
+ &ret_flags,
+ &cred_lifetime,
+ &client->cl_creds);
+ if (gr->gr_major == GSS_S_COMPLETE
+ || gr->gr_major == GSS_S_CONTINUE_NEEDED) {
+ client->cl_sname = sname;
+ break;
+ }
+ }
+ }
+ } else {
+ gr->gr_major = gss_accept_sec_context(
+ &gr->gr_minor,
+ &client->cl_ctx,
+ client->cl_sname->sn_cred,
+ &recv_tok,
+ GSS_C_NO_CHANNEL_BINDINGS,
+ &client->cl_cname,
+ &mech,
+ &gr->gr_token,
+ &ret_flags,
+ &cred_lifetime,
+ NULL);
+ }
+
+ xdr_free((xdrproc_t) xdr_gss_buffer_desc, (char *) &recv_tok);
+
+ /*
+ * If we get an error from gss_accept_sec_context, send the
+ * reply anyway so that the client gets a chance to see what
+ * is wrong.
+ */
+ if (gr->gr_major != GSS_S_COMPLETE &&
+ gr->gr_major != GSS_S_CONTINUE_NEEDED) {
+ log_status("accept_sec_context", client->cl_mech,
+ gr->gr_major, gr->gr_minor);
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+
+ gr->gr_handle.value = &client->cl_id;
+ gr->gr_handle.length = sizeof(uint32_t);
+ gr->gr_win = SVC_RPC_GSS_SEQWINDOW;
+
+ /* Save client info. */
+ client->cl_mech = mech;
+ client->cl_qop = GSS_C_QOP_DEFAULT;
+ client->cl_seq = gc->gc_seq;
+ client->cl_win = gr->gr_win;
+ client->cl_done_callback = FALSE;
+
+ if (gr->gr_major == GSS_S_COMPLETE) {
+ gss_buffer_desc export_name;
+
+ /*
+ * Change client expiration time to be near when the
+ * client creds expire (or 24 hours if we can't figure
+ * that out).
+ */
+ if (cred_lifetime == GSS_C_INDEFINITE)
+ cred_lifetime = time(0) + 24*60*60;
+
+ client->cl_expiration = time(0) + cred_lifetime;
+
+ /*
+ * Fill in cred details in the rawcred structure.
+ */
+ client->cl_rawcred.version = RPCSEC_GSS_VERSION;
+ rpc_gss_oid_to_mech(mech, &client->cl_rawcred.mechanism);
+ maj_stat = gss_export_name(&min_stat, client->cl_cname,
+ &export_name);
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_export_name", client->cl_mech,
+ maj_stat, min_stat);
+ return (FALSE);
+ }
+ client->cl_rawcred.client_principal =
+ malloc(sizeof(*client->cl_rawcred.client_principal)
+ + export_name.length);
+ client->cl_rawcred.client_principal->len = export_name.length;
+ memcpy(client->cl_rawcred.client_principal->name,
+ export_name.value, export_name.length);
+ gss_release_buffer(&min_stat, &export_name);
+ client->cl_rawcred.svc_principal =
+ client->cl_sname->sn_principal;
+ client->cl_rawcred.service = gc->gc_svc;
+
+ /*
+ * Use gss_pname_to_uid to map to unix creds. For
+ * kerberos5, this uses krb5_aname_to_localname.
+ */
+ svc_rpc_gss_build_ucred(client, client->cl_cname);
+
+#ifdef DEBUG
+ {
+ gss_buffer_desc mechname;
+
+ gss_oid_to_str(&min_stat, mech, &mechname);
+
+ log_debug("accepted context for %s with "
+ "<mech %.*s, qop %d, svc %d>",
+ client->cl_rawcred.client_principal->name,
+ mechname.length, (char *)mechname.value,
+ client->cl_qop, client->rawcred.service);
+
+ gss_release_buffer(&min_stat, &mechname);
+ }
+#endif /* DEBUG */
+ }
+ return (TRUE);
+}
+
+static bool_t
+svc_rpc_gss_validate(struct svc_rpc_gss_client *client, struct rpc_msg *msg,
+ gss_qop_t *qop)
+{
+ struct opaque_auth *oa;
+ gss_buffer_desc rpcbuf, checksum;
+ OM_uint32 maj_stat, min_stat;
+ gss_qop_t qop_state;
+ u_char rpchdr[128];
+ int32_t *buf;
+
+ log_debug("in svc_rpc_gss_validate()");
+
+ memset(rpchdr, 0, sizeof(rpchdr));
+
+ /* Reconstruct RPC header for signing (from xdr_callmsg). */
+ buf = (int32_t *)rpchdr;
+ IXDR_PUT_LONG(buf, msg->rm_xid);
+ IXDR_PUT_ENUM(buf, msg->rm_direction);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_rpcvers);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_prog);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_vers);
+ IXDR_PUT_LONG(buf, msg->rm_call.cb_proc);
+ oa = &msg->rm_call.cb_cred;
+ IXDR_PUT_ENUM(buf, oa->oa_flavor);
+ IXDR_PUT_LONG(buf, oa->oa_length);
+ if (oa->oa_length) {
+ memcpy((caddr_t)buf, oa->oa_base, oa->oa_length);
+ buf += RNDUP(oa->oa_length) / sizeof(int32_t);
+ }
+ rpcbuf.value = rpchdr;
+ rpcbuf.length = (u_char *)buf - rpchdr;
+
+ checksum.value = msg->rm_call.cb_verf.oa_base;
+ checksum.length = msg->rm_call.cb_verf.oa_length;
+
+ maj_stat = gss_verify_mic(&min_stat, client->cl_ctx, &rpcbuf, &checksum,
+ &qop_state);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_verify_mic", client->cl_mech,
+ maj_stat, min_stat);
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+ *qop = qop_state;
+ return (TRUE);
+}
+
+static bool_t
+svc_rpc_gss_nextverf(struct svc_rpc_gss_client *client,
+ struct svc_req *rqst, u_int seq)
+{
+ gss_buffer_desc signbuf;
+ OM_uint32 maj_stat, min_stat;
+ uint32_t nseq;
+
+ log_debug("in svc_rpc_gss_nextverf()");
+
+ nseq = htonl(seq);
+ signbuf.value = &nseq;
+ signbuf.length = sizeof(nseq);
+
+ if (client->cl_verf.value)
+ gss_release_buffer(&min_stat, &client->cl_verf);
+
+ maj_stat = gss_get_mic(&min_stat, client->cl_ctx, client->cl_qop,
+ &signbuf, &client->cl_verf);
+
+ if (maj_stat != GSS_S_COMPLETE) {
+ log_status("gss_get_mic", client->cl_mech, maj_stat, min_stat);
+ client->cl_state = CLIENT_STALE;
+ return (FALSE);
+ }
+ rqst->rq_xprt->xp_verf.oa_flavor = RPCSEC_GSS;
+ rqst->rq_xprt->xp_verf.oa_base = (caddr_t)client->cl_verf.value;
+ rqst->rq_xprt->xp_verf.oa_length = (u_int)client->cl_verf.length;
+
+ return (TRUE);
+}
+
+static bool_t
+svc_rpc_gss_callback(struct svc_rpc_gss_client *client, struct svc_req *rqst)
+{
+ struct svc_rpc_gss_callback *scb;
+ rpc_gss_lock_t lock;
+ void *cookie;
+ bool_t cb_res;
+ bool_t result;
+
+ /*
+ * See if we have a callback for this guy.
+ */
+ result = TRUE;
+ SLIST_FOREACH(scb, &svc_rpc_gss_callbacks, cb_link) {
+ if (scb->cb_callback.program == rqst->rq_prog
+ && scb->cb_callback.version == rqst->rq_vers) {
+ /*
+ * This one matches. Call the callback and see
+ * if it wants to veto or something.
+ */
+ lock.locked = FALSE;
+ lock.raw_cred = &client->cl_rawcred;
+ cb_res = scb->cb_callback.callback(rqst,
+ client->cl_creds,
+ client->cl_ctx,
+ &lock,
+ &cookie);
+
+ if (!cb_res) {
+ client->cl_state = CLIENT_STALE;
+ result = FALSE;
+ break;
+ }
+
+ /*
+ * The callback accepted the connection - it
+ * is responsible for freeing client->cl_creds
+ * now.
+ */
+ client->cl_creds = GSS_C_NO_CREDENTIAL;
+ client->cl_locked = lock.locked;
+ client->cl_cookie = cookie;
+ return (TRUE);
+ }
+ }
+
+ /*
+ * Either no callback exists for this program/version or one
+ * of the callbacks rejected the connection. We just need to
+ * clean up the delegated client creds, if any.
+ */
+ if (client->cl_creds) {
+ OM_uint32 min_ver;
+ gss_release_cred(&min_ver, &client->cl_creds);
+ }
+ return (result);
+}
+
+static bool_t
+svc_rpc_gss_check_replay(struct svc_rpc_gss_client *client, uint32_t seq)
+{
+ u_int32_t offset;
+ int word, bit;
+
+ if (seq < client->cl_seqlast) {
+ /*
+ * The request sequence number is less than
+ * the largest we have seen so far. If it is
+ * outside the window or if we have seen a
+ * request with this sequence before, silently
+ * discard it.
+ */
+ offset = client->cl_seqlast - seq;
+ if (offset >= client->cl_win)
+ return (FALSE);
+ word = offset / 32;
+ bit = offset % 32;
+ if (client->cl_seqmask[word] & (1 << bit))
+ return (FALSE);
+ client->cl_seqmask[word] |= (1 << bit);
+ }
+
+ return (TRUE);
+}
+
+static void
+svc_rpc_gss_update_seq(struct svc_rpc_gss_client *client, uint32_t seq)
+{
+ int offset, i;
+ uint32_t carry, newcarry;
+
+ if (seq > client->cl_seqlast) {
+ /*
+ * This request has a sequence number greater
+ * than any we have seen so far. Advance the
+ * seq window and set bit zero of the window
+ * (which corresponds to the new sequence
+ * number)
+ */
+ offset = seq - client->cl_seqlast;
+ while (offset > 32) {
+ for (i = (SVC_RPC_GSS_SEQWINDOW / 32) - 1;
+ i > 0; i--) {
+ client->cl_seqmask[i] = client->cl_seqmask[i-1];
+ }
+ client->cl_seqmask[0] = 0;
+ offset -= 32;
+ }
+ carry = 0;
+ for (i = 0; i < SVC_RPC_GSS_SEQWINDOW / 32; i++) {
+ newcarry = client->cl_seqmask[i] >> (32 - offset);
+ client->cl_seqmask[i] =
+ (client->cl_seqmask[i] << offset) | carry;
+ carry = newcarry;
+ }
+ client->cl_seqmask[0] |= 1;
+ client->cl_seqlast = seq;
+ }
+}
+
+enum auth_stat
+svc_rpc_gss(struct svc_req *rqst, struct rpc_msg *msg)
+
+{
+ OM_uint32 min_stat;
+ XDR xdrs;
+ struct svc_rpc_gss_client *client;
+ struct rpc_gss_cred gc;
+ struct rpc_gss_init_res gr;
+ gss_qop_t qop;
+ int call_stat;
+ enum auth_stat result;
+
+ log_debug("in svc_rpc_gss()");
+
+ /* Garbage collect old clients. */
+ svc_rpc_gss_timeout_clients();
+
+ /* Initialize reply. */
+ rqst->rq_xprt->xp_verf = _null_auth;
+
+ /* Deserialize client credentials. */
+ if (rqst->rq_cred.oa_length <= 0)
+ return (AUTH_BADCRED);
+
+ memset(&gc, 0, sizeof(gc));
+
+ xdrmem_create(&xdrs, rqst->rq_cred.oa_base,
+ rqst->rq_cred.oa_length, XDR_DECODE);
+
+ if (!xdr_rpc_gss_cred(&xdrs, &gc)) {
+ XDR_DESTROY(&xdrs);
+ return (AUTH_BADCRED);
+ }
+ XDR_DESTROY(&xdrs);
+
+ /* Check version. */
+ if (gc.gc_version != RPCSEC_GSS_VERSION) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+
+ /* Check the proc and find the client (or create it) */
+ if (gc.gc_proc == RPCSEC_GSS_INIT) {
+ client = svc_rpc_gss_create_client();
+ } else {
+ if (gc.gc_handle.length != sizeof(uint32_t)) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+ uint32_t *p = gc.gc_handle.value;
+ client = svc_rpc_gss_find_client(*p);
+ if (!client) {
+ /*
+ * Can't find the client - we may have
+ * destroyed it - tell the other side to
+ * re-authenticate.
+ */
+ result = RPCSEC_GSS_CREDPROBLEM;
+ goto out;
+ }
+ }
+ rqst->rq_clntcred = client;
+
+ /*
+ * The service and sequence number must be ignored for
+ * RPCSEC_GSS_INIT and RPCSEC_GSS_CONTINUE_INIT.
+ */
+ if (gc.gc_proc != RPCSEC_GSS_INIT
+ && gc.gc_proc != RPCSEC_GSS_CONTINUE_INIT) {
+ /*
+ * Check for sequence number overflow.
+ */
+ if (gc.gc_seq >= MAXSEQ) {
+ result = RPCSEC_GSS_CTXPROBLEM;
+ goto out;
+ }
+ client->cl_seq = gc.gc_seq;
+
+ /*
+ * Check for valid service.
+ */
+ if (gc.gc_svc != rpc_gss_svc_none &&
+ gc.gc_svc != rpc_gss_svc_integrity &&
+ gc.gc_svc != rpc_gss_svc_privacy) {
+ result = AUTH_BADCRED;
+ goto out;
+ }
+ }
+
+ /* Handle RPCSEC_GSS control procedure. */
+ switch (gc.gc_proc) {
+
+ case RPCSEC_GSS_INIT:
+ case RPCSEC_GSS_CONTINUE_INIT:
+ if (rqst->rq_proc != NULLPROC) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+
+ memset(&gr, 0, sizeof(gr));
+ if (!svc_rpc_gss_accept_sec_context(client, rqst, &gr, &gc)) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+
+ if (gr.gr_major == GSS_S_COMPLETE) {
+ if (!svc_rpc_gss_nextverf(client, rqst, gr.gr_win)) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+ } else {
+ rqst->rq_xprt->xp_verf.oa_flavor = AUTH_NULL;
+ rqst->rq_xprt->xp_verf.oa_length = 0;
+ }
+
+ call_stat = svc_sendreply(rqst->rq_xprt,
+ (xdrproc_t) xdr_rpc_gss_init_res,
+ (caddr_t) &gr);
+
+ gss_release_buffer(&min_stat, &gr.gr_token);
+
+ if (!call_stat) {
+ result = AUTH_FAILED;
+ break;
+ }
+
+ if (gr.gr_major == GSS_S_COMPLETE)
+ client->cl_state = CLIENT_ESTABLISHED;
+
+ result = RPCSEC_GSS_NODISPATCH;
+ break;
+
+ case RPCSEC_GSS_DATA:
+ case RPCSEC_GSS_DESTROY:
+ if (!svc_rpc_gss_check_replay(client, gc.gc_seq)) {
+ result = RPCSEC_GSS_NODISPATCH;
+ break;
+ }
+
+ if (!svc_rpc_gss_validate(client, msg, &qop)) {
+ result = RPCSEC_GSS_CREDPROBLEM;
+ break;
+ }
+
+ if (!svc_rpc_gss_nextverf(client, rqst, gc.gc_seq)) {
+ result = RPCSEC_GSS_CTXPROBLEM;
+ break;
+ }
+
+ svc_rpc_gss_update_seq(client, gc.gc_seq);
+
+ /*
+ * Change the SVCAUTH ops on the transport to point at
+ * our own code so that we can unwrap the arguments
+ * and wrap the result. The caller will re-set this on
+ * every request to point to a set of null wrap/unwrap
+ * methods.
+ */
+ SVC_AUTH(rqst->rq_xprt).svc_ah_ops = &svc_auth_gss_ops;
+ SVC_AUTH(rqst->rq_xprt).svc_ah_private = client;
+
+ if (gc.gc_proc == RPCSEC_GSS_DATA) {
+ /*
+ * We might be ready to do a callback to the server to
+ * see if it wants to accept/reject the connection.
+ */
+ if (!client->cl_done_callback) {
+ client->cl_done_callback = TRUE;
+ client->cl_qop = qop;
+ client->cl_rawcred.qop = _rpc_gss_num_to_qop(
+ client->cl_rawcred.mechanism, qop);
+ if (!svc_rpc_gss_callback(client, rqst)) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+ }
+
+ /*
+ * If the server has locked this client to a
+ * particular service+qop pair, enforce that
+ * restriction now.
+ */
+ if (client->cl_locked) {
+ if (client->cl_rawcred.service != gc.gc_svc) {
+ result = AUTH_FAILED;
+ break;
+ } else if (client->cl_qop != qop) {
+ result = AUTH_BADVERF;
+ break;
+ }
+ }
+
+ /*
+ * If the qop changed, look up the new qop
+ * name for rawcred.
+ */
+ if (client->cl_qop != qop) {
+ client->cl_qop = qop;
+ client->cl_rawcred.qop = _rpc_gss_num_to_qop(
+ client->cl_rawcred.mechanism, qop);
+ }
+
+ /*
+ * Make sure we use the right service value
+ * for unwrap/wrap.
+ */
+ client->cl_rawcred.service = gc.gc_svc;
+
+ result = AUTH_OK;
+ } else {
+ if (rqst->rq_proc != NULLPROC) {
+ result = AUTH_REJECTEDCRED;
+ break;
+ }
+
+ call_stat = svc_sendreply(rqst->rq_xprt,
+ (xdrproc_t) xdr_void, (caddr_t) NULL);
+
+ if (!call_stat) {
+ result = AUTH_FAILED;
+ break;
+ }
+
+ svc_rpc_gss_destroy_client(client);
+
+ result = RPCSEC_GSS_NODISPATCH;
+ break;
+ }
+ break;
+
+ default:
+ result = AUTH_BADCRED;
+ break;
+ }
+out:
+ xdr_free((xdrproc_t) xdr_rpc_gss_cred, (char *) &gc);
+ return (result);
+}
+
+bool_t
+svc_rpc_gss_wrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+ struct svc_rpc_gss_client *client;
+
+ log_debug("in svc_rpc_gss_wrap()");
+
+ client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
+ if (client->cl_state != CLIENT_ESTABLISHED
+ || client->cl_rawcred.service == rpc_gss_svc_none) {
+ return xdr_func(xdrs, xdr_ptr);
+ }
+ return (xdr_rpc_gss_wrap_data(xdrs, xdr_func, xdr_ptr,
+ client->cl_ctx, client->cl_qop,
+ client->cl_rawcred.service, client->cl_seq));
+}
+
+bool_t
+svc_rpc_gss_unwrap(SVCAUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr)
+{
+ struct svc_rpc_gss_client *client;
+
+ log_debug("in svc_rpc_gss_unwrap()");
+
+ client = (struct svc_rpc_gss_client *) auth->svc_ah_private;
+ if (client->cl_state != CLIENT_ESTABLISHED
+ || client->cl_rawcred.service == rpc_gss_svc_none) {
+ return xdr_func(xdrs, xdr_ptr);
+ }
+ return (xdr_rpc_gss_unwrap_data(xdrs, xdr_func, xdr_ptr,
+ client->cl_ctx, client->cl_qop,
+ client->cl_rawcred.service, client->cl_seq));
+}
OpenPOWER on IntegriCloud