Skip to content
Snippets Groups Projects
auth_gss.c 51.2 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
/*
 * linux/net/sunrpc/auth_gss/auth_gss.c
Linus Torvalds's avatar
Linus Torvalds committed
 *
 * RPCSEC_GSS client authentication.
Linus Torvalds's avatar
Linus Torvalds committed
 *  Copyright (c) 2000 The Regents of the University of Michigan.
 *  All rights reserved.
 *
 *  Dug Song       <dugsong@monkey.org>
 *  Andy Adamson   <andros@umich.edu>
 *
 *  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.
 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/pagemap.h>
Linus Torvalds's avatar
Linus Torvalds committed
#include <linux/sunrpc/clnt.h>
#include <linux/sunrpc/auth.h>
#include <linux/sunrpc/auth_gss.h>
#include <linux/sunrpc/svcauth_gss.h>
#include <linux/sunrpc/gss_err.h>
#include <linux/workqueue.h>
#include <linux/sunrpc/rpc_pipe_fs.h>
#include <linux/sunrpc/gss_api.h>
#include <asm/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed

static const struct rpc_authops authgss_ops;
Linus Torvalds's avatar
Linus Torvalds committed

static const struct rpc_credops gss_credops;
static const struct rpc_credops gss_nullops;
Linus Torvalds's avatar
Linus Torvalds committed

#define GSS_RETRY_EXPIRED 5
static unsigned int gss_expired_cred_retry_delay = GSS_RETRY_EXPIRED;

#define GSS_KEY_EXPIRE_TIMEO 240
static unsigned int gss_key_expire_timeo = GSS_KEY_EXPIRE_TIMEO;

Linus Torvalds's avatar
Linus Torvalds committed
#ifdef RPC_DEBUG
# define RPCDBG_FACILITY	RPCDBG_AUTH
#endif

#define GSS_CRED_SLACK		(RPC_MAX_AUTH_SIZE * 2)
Linus Torvalds's avatar
Linus Torvalds committed
/* length of a krb5 verifier (48), plus data added before arguments when
 * using integrity (two 4-byte integers): */
Olga Kornievskaia's avatar
Olga Kornievskaia committed
#define GSS_VERF_SLACK		100
Linus Torvalds's avatar
Linus Torvalds committed

static DEFINE_HASHTABLE(gss_auth_hash_table, 4);
static DEFINE_SPINLOCK(gss_auth_hash_lock);

struct gss_pipe {
	struct rpc_pipe_dir_object pdo;
	struct rpc_pipe *pipe;
	struct rpc_clnt *clnt;
	const char *name;
Linus Torvalds's avatar
Linus Torvalds committed
struct gss_auth {
Linus Torvalds's avatar
Linus Torvalds committed
	struct rpc_auth rpc_auth;
	struct gss_api_mech *mech;
	enum rpc_gss_svc service;
	struct rpc_clnt *client;
	struct net *net;
	/*
	 * There are two upcall pipes; dentry[1], named "gssd", is used
	 * for the new text-based upcall; dentry[0] is named after the
	 * mechanism (for example, "krb5") and exists for
	 * backwards-compatibility with older gssd's.
	 */
	const char *target_name;
Linus Torvalds's avatar
Linus Torvalds committed
};

/* pipe_version >= 0 if and only if someone has a pipe open. */
static DEFINE_SPINLOCK(pipe_version_lock);
static struct rpc_wait_queue pipe_version_rpc_waitqueue;
static DECLARE_WAIT_QUEUE_HEAD(pipe_version_waitqueue);
static void gss_put_auth(struct gss_auth *gss_auth);
static void gss_free_ctx(struct gss_cl_ctx *);
static const struct rpc_pipe_ops gss_upcall_ops_v0;
static const struct rpc_pipe_ops gss_upcall_ops_v1;
Linus Torvalds's avatar
Linus Torvalds committed

static inline struct gss_cl_ctx *
gss_get_ctx(struct gss_cl_ctx *ctx)
{
	atomic_inc(&ctx->count);
	return ctx;
}

static inline void
gss_put_ctx(struct gss_cl_ctx *ctx)
{
	if (atomic_dec_and_test(&ctx->count))
		gss_free_ctx(ctx);
/* gss_cred_set_ctx:
 * called by gss_upcall_callback and gss_create_upcall in order
 * to set the gss context. The actual exchange of an old context
 * and a new one is protected by the pipe->lock.
Linus Torvalds's avatar
Linus Torvalds committed
static void
gss_cred_set_ctx(struct rpc_cred *cred, struct gss_cl_ctx *ctx)
{
	struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
	if (!test_bit(RPCAUTH_CRED_NEW, &cred->cr_flags))
		return;
	gss_get_ctx(ctx);
	rcu_assign_pointer(gss_cred->gc_ctx, ctx);
	set_bit(RPCAUTH_CRED_UPTODATE, &cred->cr_flags);
	clear_bit(RPCAUTH_CRED_NEW, &cred->cr_flags);
Linus Torvalds's avatar
Linus Torvalds committed
}

static const void *
simple_get_bytes(const void *p, const void *end, void *res, size_t len)
{
	const void *q = (const void *)((const char *)p + len);
	if (unlikely(q > end || q < p))
		return ERR_PTR(-EFAULT);
	memcpy(res, p, len);
	return q;
}

static inline const void *
simple_get_netobj(const void *p, const void *end, struct xdr_netobj *dest)
{
	const void *q;
	unsigned int len;

	p = simple_get_bytes(p, end, &len, sizeof(len));
	if (IS_ERR(p))
		return p;
	q = (const void *)((const char *)p + len);
	if (unlikely(q > end || q < p))
		return ERR_PTR(-EFAULT);
	dest->data = kmemdup(p, len, GFP_NOFS);
Linus Torvalds's avatar
Linus Torvalds committed
	if (unlikely(dest->data == NULL))
		return ERR_PTR(-ENOMEM);
	dest->len = len;
	return q;
}

static struct gss_cl_ctx *
gss_cred_get_ctx(struct rpc_cred *cred)
{
	struct gss_cred *gss_cred = container_of(cred, struct gss_cred, gc_base);
	struct gss_cl_ctx *ctx = NULL;

Linus Torvalds's avatar
Linus Torvalds committed
	if (gss_cred->gc_ctx)
		ctx = gss_get_ctx(gss_cred->gc_ctx);
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
	return ctx;
}

static struct gss_cl_ctx *
gss_alloc_context(void)
{
	struct gss_cl_ctx *ctx;

	ctx = kzalloc(sizeof(*ctx), GFP_NOFS);
Linus Torvalds's avatar
Linus Torvalds committed
	if (ctx != NULL) {
		ctx->gc_proc = RPC_GSS_PROC_DATA;
		ctx->gc_seq = 1;	/* NetApp 6.4R1 doesn't accept seq. no. 0 */
		spin_lock_init(&ctx->gc_seq_lock);
		atomic_set(&ctx->count,1);
	}
	return ctx;
}

#define GSSD_MIN_TIMEOUT (60 * 60)
static const void *
gss_fill_context(const void *p, const void *end, struct gss_cl_ctx *ctx, struct gss_api_mech *gm)
{
	const void *q;
	unsigned int seclen;
	unsigned int timeout;
	unsigned long now = jiffies;
Linus Torvalds's avatar
Linus Torvalds committed
	u32 window_size;
	int ret;

	/* First unsigned int gives the remaining lifetime in seconds of the
	 * credential - e.g. the remaining TGT lifetime for Kerberos or
	 * the -t value passed to GSSD.
	 */
Linus Torvalds's avatar
Linus Torvalds committed
	p = simple_get_bytes(p, end, &timeout, sizeof(timeout));
	if (IS_ERR(p))
		goto err;
	if (timeout == 0)
		timeout = GSSD_MIN_TIMEOUT;
	ctx->gc_expiry = now + ((unsigned long)timeout * HZ);
	/* Sequence number window. Determines the maximum number of
	 * simultaneous requests
	 */
Linus Torvalds's avatar
Linus Torvalds committed
	p = simple_get_bytes(p, end, &window_size, sizeof(window_size));
	if (IS_ERR(p))
		goto err;
	ctx->gc_win = window_size;
	/* gssd signals an error by passing ctx->gc_win = 0: */
	if (ctx->gc_win == 0) {
		/*
		 * in which case, p points to an error code. Anything other
		 * than -EKEYEXPIRED gets converted to -EACCES.
		 */
		p = simple_get_bytes(p, end, &ret, sizeof(ret));
		if (!IS_ERR(p))
			p = (ret == -EKEYEXPIRED) ? ERR_PTR(-EKEYEXPIRED) :
						    ERR_PTR(-EACCES);
Linus Torvalds's avatar
Linus Torvalds committed
		goto err;
	}
	/* copy the opaque wire context */
	p = simple_get_netobj(p, end, &ctx->gc_wire_ctx);
	if (IS_ERR(p))
		goto err;
	/* import the opaque security context */
	p  = simple_get_bytes(p, end, &seclen, sizeof(seclen));
	if (IS_ERR(p))
		goto err;
	q = (const void *)((const char *)p + seclen);
	if (unlikely(q > end || q < p)) {
		p = ERR_PTR(-EFAULT);
		goto err;
	}
	ret = gss_import_sec_context(p, seclen, gm, &ctx->gc_gss_ctx, NULL, GFP_NOFS);
Linus Torvalds's avatar
Linus Torvalds committed
	if (ret < 0) {
		p = ERR_PTR(ret);
		goto err;
	}
	dprintk("RPC:       %s Success. gc_expiry %lu now %lu timeout %u\n",
		__func__, ctx->gc_expiry, now, timeout);
Linus Torvalds's avatar
Linus Torvalds committed
	return q;
err:
	dprintk("RPC:       %s returns error %ld\n", __func__, -PTR_ERR(p));
Linus Torvalds's avatar
Linus Torvalds committed
	return p;
}

#define UPCALL_BUF_LEN 128
Linus Torvalds's avatar
Linus Torvalds committed

struct gss_upcall_msg {
	atomic_t count;
Linus Torvalds's avatar
Linus Torvalds committed
	struct rpc_pipe_msg msg;
	struct list_head list;
	struct gss_auth *auth;
	struct rpc_pipe *pipe;
Linus Torvalds's avatar
Linus Torvalds committed
	struct rpc_wait_queue rpc_waitqueue;
	wait_queue_head_t waitqueue;
	struct gss_cl_ctx *ctx;
	char databuf[UPCALL_BUF_LEN];
Linus Torvalds's avatar
Linus Torvalds committed
};

static int get_pipe_version(struct net *net)
	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
	int ret;

	spin_lock(&pipe_version_lock);
	if (sn->pipe_version >= 0) {
		atomic_inc(&sn->pipe_users);
		ret = sn->pipe_version;
	} else
		ret = -EAGAIN;
	spin_unlock(&pipe_version_lock);
	return ret;
}

static void put_pipe_version(struct net *net)
	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);

	if (atomic_dec_and_lock(&sn->pipe_users, &pipe_version_lock)) {
		sn->pipe_version = -1;
Linus Torvalds's avatar
Linus Torvalds committed
static void
gss_release_msg(struct gss_upcall_msg *gss_msg)
{
	struct net *net = gss_msg->auth->net;
Linus Torvalds's avatar
Linus Torvalds committed
	if (!atomic_dec_and_test(&gss_msg->count))
		return;
Linus Torvalds's avatar
Linus Torvalds committed
	BUG_ON(!list_empty(&gss_msg->list));
	if (gss_msg->ctx != NULL)
		gss_put_ctx(gss_msg->ctx);
	rpc_destroy_wait_queue(&gss_msg->rpc_waitqueue);
Linus Torvalds's avatar
Linus Torvalds committed
	kfree(gss_msg);
}

static struct gss_upcall_msg *
__gss_find_upcall(struct rpc_pipe *pipe, kuid_t uid)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gss_upcall_msg *pos;
	list_for_each_entry(pos, &pipe->in_downcall, list) {
		if (!uid_eq(pos->uid, uid))
Linus Torvalds's avatar
Linus Torvalds committed
			continue;
		atomic_inc(&pos->count);
		dprintk("RPC:       %s found msg %p\n", __func__, pos);
Linus Torvalds's avatar
Linus Torvalds committed
		return pos;
	}
	dprintk("RPC:       %s found nothing\n", __func__);
Linus Torvalds's avatar
Linus Torvalds committed
	return NULL;
}

/* Try to add an upcall to the pipefs queue.
Linus Torvalds's avatar
Linus Torvalds committed
 * If an upcall owned by our uid already exists, then we return a reference
 * to that upcall instead of adding the new upcall.
 */
static inline struct gss_upcall_msg *
gss_add_msg(struct gss_upcall_msg *gss_msg)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct rpc_pipe *pipe = gss_msg->pipe;
Linus Torvalds's avatar
Linus Torvalds committed
	struct gss_upcall_msg *old;

	spin_lock(&pipe->lock);
	old = __gss_find_upcall(pipe, gss_msg->uid);
Linus Torvalds's avatar
Linus Torvalds committed
	if (old == NULL) {
		atomic_inc(&gss_msg->count);
		list_add(&gss_msg->list, &pipe->in_downcall);
Linus Torvalds's avatar
Linus Torvalds committed
	} else
		gss_msg = old;
	spin_unlock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
	return gss_msg;
}

static void
__gss_unhash_msg(struct gss_upcall_msg *gss_msg)
{
	list_del_init(&gss_msg->list);
	rpc_wake_up_status(&gss_msg->rpc_waitqueue, gss_msg->msg.errno);
	wake_up_all(&gss_msg->waitqueue);
	atomic_dec(&gss_msg->count);
}

static void
gss_unhash_msg(struct gss_upcall_msg *gss_msg)
{
	struct rpc_pipe *pipe = gss_msg->pipe;
Linus Torvalds's avatar
Linus Torvalds committed

	if (list_empty(&gss_msg->list))
		return;
	spin_lock(&pipe->lock);
	if (!list_empty(&gss_msg->list))
		__gss_unhash_msg(gss_msg);
	spin_unlock(&pipe->lock);
static void
gss_handle_downcall_result(struct gss_cred *gss_cred, struct gss_upcall_msg *gss_msg)
{
	switch (gss_msg->msg.errno) {
	case 0:
		if (gss_msg->ctx == NULL)
			break;
		clear_bit(RPCAUTH_CRED_NEGATIVE, &gss_cred->gc_base.cr_flags);
		gss_cred_set_ctx(&gss_cred->gc_base, gss_msg->ctx);
		break;
	case -EKEYEXPIRED:
		set_bit(RPCAUTH_CRED_NEGATIVE, &gss_cred->gc_base.cr_flags);
	}
	gss_cred->gc_upcall_timestamp = jiffies;
	gss_cred->gc_upcall = NULL;
	rpc_wake_up_status(&gss_msg->rpc_waitqueue, gss_msg->msg.errno);
}

Linus Torvalds's avatar
Linus Torvalds committed
static void
gss_upcall_callback(struct rpc_task *task)
{
	struct gss_cred *gss_cred = container_of(task->tk_rqstp->rq_cred,
Linus Torvalds's avatar
Linus Torvalds committed
			struct gss_cred, gc_base);
	struct gss_upcall_msg *gss_msg = gss_cred->gc_upcall;
	struct rpc_pipe *pipe = gss_msg->pipe;
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock(&pipe->lock);
	gss_handle_downcall_result(gss_cred, gss_msg);
	spin_unlock(&pipe->lock);
	task->tk_status = gss_msg->msg.errno;
Linus Torvalds's avatar
Linus Torvalds committed
	gss_release_msg(gss_msg);
}

static void gss_encode_v0_msg(struct gss_upcall_msg *gss_msg)
{
	uid_t uid = from_kuid(&init_user_ns, gss_msg->uid);
	memcpy(gss_msg->databuf, &uid, sizeof(uid));
	gss_msg->msg.data = gss_msg->databuf;
	gss_msg->msg.len = sizeof(uid);

	BUILD_BUG_ON(sizeof(uid) > sizeof(gss_msg->databuf));
static int gss_encode_v1_msg(struct gss_upcall_msg *gss_msg,
				const char *service_name,
				const char *target_name)
	struct gss_api_mech *mech = gss_msg->auth->mech;
	char *p = gss_msg->databuf;
	size_t buflen = sizeof(gss_msg->databuf);
	int len;

	len = scnprintf(p, buflen, "mech=%s uid=%d ", mech->gm_name,
			from_kuid(&init_user_ns, gss_msg->uid));
	buflen -= len;
	p += len;
	gss_msg->msg.len = len;
		len = scnprintf(p, buflen, "target=%s ", target_name);
		buflen -= len;
		p += len;
		gss_msg->msg.len += len;
	}
	if (service_name != NULL) {
		len = scnprintf(p, buflen, "service=%s ", service_name);
		buflen -= len;
		p += len;
		gss_msg->msg.len += len;
	}
	if (mech->gm_upcall_enctypes) {
		len = scnprintf(p, buflen, "enctypes=%s ",
				mech->gm_upcall_enctypes);
		buflen -= len;
	len = scnprintf(p, buflen, "\n");
	if (len == 0)
		goto out_overflow;
	gss_msg->msg.len += len;

	gss_msg->msg.data = gss_msg->databuf;
	return 0;
out_overflow:
	WARN_ON_ONCE(1);
	return -ENOMEM;
static struct gss_upcall_msg *
gss_alloc_msg(struct gss_auth *gss_auth,
		kuid_t uid, const char *service_name)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gss_upcall_msg *gss_msg;
Linus Torvalds's avatar
Linus Torvalds committed

	gss_msg = kzalloc(sizeof(*gss_msg), GFP_NOFS);
	if (gss_msg == NULL)
	vers = get_pipe_version(gss_auth->net);
	gss_msg->pipe = gss_auth->gss_pipe[vers]->pipe;
	INIT_LIST_HEAD(&gss_msg->list);
	rpc_init_wait_queue(&gss_msg->rpc_waitqueue, "RPCSEC_GSS upcall waitq");
	init_waitqueue_head(&gss_msg->waitqueue);
	atomic_set(&gss_msg->count, 1);
	gss_msg->uid = uid;
	gss_msg->auth = gss_auth;
	switch (vers) {
	case 0:
		gss_encode_v0_msg(gss_msg);
		err = gss_encode_v1_msg(gss_msg, service_name, gss_auth->target_name);
		if (err)
			goto err_put_pipe_version;
Linus Torvalds's avatar
Linus Torvalds committed
	return gss_msg;
err_put_pipe_version:
	put_pipe_version(gss_auth->net);
err_free_msg:
	kfree(gss_msg);
err:
	return ERR_PTR(err);
Linus Torvalds's avatar
Linus Torvalds committed
}

static struct gss_upcall_msg *
gss_setup_upcall(struct gss_auth *gss_auth, struct rpc_cred *cred)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct gss_cred *gss_cred = container_of(cred,
			struct gss_cred, gc_base);
Linus Torvalds's avatar
Linus Torvalds committed
	struct gss_upcall_msg *gss_new, *gss_msg;
	kuid_t uid = cred->cr_uid;
Linus Torvalds's avatar
Linus Torvalds committed

	gss_new = gss_alloc_msg(gss_auth, uid, gss_cred->gc_principal);
	if (IS_ERR(gss_new))
		return gss_new;
	gss_msg = gss_add_msg(gss_new);
Linus Torvalds's avatar
Linus Torvalds committed
	if (gss_msg == gss_new) {
		int res = rpc_queue_upcall(gss_new->pipe, &gss_new->msg);
Linus Torvalds's avatar
Linus Torvalds committed
		if (res) {
			gss_unhash_msg(gss_new);
			gss_msg = ERR_PTR(res);
		}
	} else
		gss_release_msg(gss_new);
	return gss_msg;
}

	dprintk("AUTH_GSS upcall failed. Please check user daemon is running.\n");
Linus Torvalds's avatar
Linus Torvalds committed
static inline int
gss_refresh_upcall(struct rpc_task *task)
{
	struct rpc_cred *cred = task->tk_rqstp->rq_cred;
	struct gss_auth *gss_auth = container_of(cred->cr_auth,
Linus Torvalds's avatar
Linus Torvalds committed
			struct gss_auth, rpc_auth);
	struct gss_cred *gss_cred = container_of(cred,
			struct gss_cred, gc_base);
	struct gss_upcall_msg *gss_msg;
	struct rpc_pipe *pipe;
Linus Torvalds's avatar
Linus Torvalds committed
	int err = 0;

	dprintk("RPC: %5u %s for uid %u\n",
		task->tk_pid, __func__, from_kuid(&init_user_ns, cred->cr_uid));
	gss_msg = gss_setup_upcall(gss_auth, cred);
	if (PTR_ERR(gss_msg) == -EAGAIN) {
		/* XXX: warning on the first, under the assumption we
		 * shouldn't normally hit this case on a refresh. */
		warn_gssd();
		task->tk_timeout = 15*HZ;
		rpc_sleep_on(&pipe_version_rpc_waitqueue, task, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
	if (IS_ERR(gss_msg)) {
		err = PTR_ERR(gss_msg);
		goto out;
	}
	pipe = gss_msg->pipe;
	spin_lock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
	if (gss_cred->gc_upcall != NULL)
		rpc_sleep_on(&gss_cred->gc_upcall->rpc_waitqueue, task, NULL);
	else if (gss_msg->ctx == NULL && gss_msg->msg.errno >= 0) {
Linus Torvalds's avatar
Linus Torvalds committed
		task->tk_timeout = 0;
		gss_cred->gc_upcall = gss_msg;
		/* gss_upcall_callback will release the reference to gss_upcall_msg */
		atomic_inc(&gss_msg->count);
		rpc_sleep_on(&gss_msg->rpc_waitqueue, task, gss_upcall_callback);
	} else {
		gss_handle_downcall_result(gss_cred, gss_msg);
Linus Torvalds's avatar
Linus Torvalds committed
		err = gss_msg->msg.errno;
	spin_unlock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
	gss_release_msg(gss_msg);
out:
	dprintk("RPC: %5u %s for uid %u result %d\n",
		task->tk_pid, __func__,
		from_kuid(&init_user_ns, cred->cr_uid),	err);
Linus Torvalds's avatar
Linus Torvalds committed
	return err;
}

static inline int
gss_create_upcall(struct gss_auth *gss_auth, struct gss_cred *gss_cred)
{
	struct net *net = gss_auth->net;
	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
	struct rpc_pipe *pipe;
Linus Torvalds's avatar
Linus Torvalds committed
	struct rpc_cred *cred = &gss_cred->gc_base;
	struct gss_upcall_msg *gss_msg;
	DEFINE_WAIT(wait);
Linus Torvalds's avatar
Linus Torvalds committed

	dprintk("RPC:       %s for uid %u\n",
		__func__, from_kuid(&init_user_ns, cred->cr_uid));
	/* if gssd is down, just skip upcalling altogether */
	if (!gssd_running(net)) {
		warn_gssd();
		return -EACCES;
	}
	gss_msg = gss_setup_upcall(gss_auth, cred);
	if (PTR_ERR(gss_msg) == -EAGAIN) {
		err = wait_event_interruptible_timeout(pipe_version_waitqueue,
			warn_gssd();
			err = -EACCES;
		}
Linus Torvalds's avatar
Linus Torvalds committed
	if (IS_ERR(gss_msg)) {
		err = PTR_ERR(gss_msg);
		goto out;
	}
	pipe = gss_msg->pipe;
Linus Torvalds's avatar
Linus Torvalds committed
	for (;;) {
		prepare_to_wait(&gss_msg->waitqueue, &wait, TASK_KILLABLE);
		spin_lock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
		if (gss_msg->ctx != NULL || gss_msg->msg.errno < 0) {
			break;
		}
		spin_unlock(&pipe->lock);
		if (fatal_signal_pending(current)) {
Linus Torvalds's avatar
Linus Torvalds committed
			err = -ERESTARTSYS;
			goto out_intr;
		}
		schedule();
	}
	if (gss_msg->ctx)
		gss_cred_set_ctx(cred, gss_msg->ctx);
Linus Torvalds's avatar
Linus Torvalds committed
	else
		err = gss_msg->msg.errno;
	spin_unlock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
out_intr:
	finish_wait(&gss_msg->waitqueue, &wait);
	gss_release_msg(gss_msg);
out:
	dprintk("RPC:       %s for uid %u result %d\n",
		__func__, from_kuid(&init_user_ns, cred->cr_uid), err);
Linus Torvalds's avatar
Linus Torvalds committed
	return err;
}

#define MSG_BUF_MAXSIZE 1024

static ssize_t
gss_pipe_downcall(struct file *filp, const char __user *src, size_t mlen)
{
	const void *p, *end;
	void *buf;
	struct gss_upcall_msg *gss_msg;
Al Viro's avatar
Al Viro committed
	struct rpc_pipe *pipe = RPC_I(file_inode(filp))->pipe;
Linus Torvalds's avatar
Linus Torvalds committed
	struct gss_cl_ctx *ctx;
Linus Torvalds's avatar
Linus Torvalds committed

	if (mlen > MSG_BUF_MAXSIZE)
		goto out;
	err = -ENOMEM;
	buf = kmalloc(mlen, GFP_NOFS);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!buf)
		goto out;

	err = -EFAULT;
	if (copy_from_user(buf, src, mlen))
		goto err;

	end = (const void *)((char *)buf + mlen);
	p = simple_get_bytes(buf, end, &id, sizeof(id));
Linus Torvalds's avatar
Linus Torvalds committed
	if (IS_ERR(p)) {
		err = PTR_ERR(p);
		goto err;
	}

	uid = make_kuid(&init_user_ns, id);
	if (!uid_valid(uid)) {
		err = -EINVAL;
		goto err;
	}

Linus Torvalds's avatar
Linus Torvalds committed
	err = -ENOMEM;
	ctx = gss_alloc_context();
	if (ctx == NULL)
		goto err;

	err = -ENOENT;
	/* Find a matching upcall */
	spin_lock(&pipe->lock);
	gss_msg = __gss_find_upcall(pipe, uid);
		spin_unlock(&pipe->lock);
		goto err_put_ctx;
	}
	list_del_init(&gss_msg->list);
	spin_unlock(&pipe->lock);
	p = gss_fill_context(p, end, ctx, gss_msg->auth->mech);
Linus Torvalds's avatar
Linus Torvalds committed
	if (IS_ERR(p)) {
		err = PTR_ERR(p);
		case -EKEYEXPIRED:
			gss_msg->msg.errno = err;
			err = mlen;
			break;
		case -EFAULT:
		case -ENOMEM:
		case -EINVAL:
		case -ENOSYS:
			gss_msg->msg.errno = -EAGAIN;
			break;
		default:
			printk(KERN_CRIT "%s: bad return from "
				"gss_fill_context: %zd\n", __func__, err);
Linus Torvalds's avatar
Linus Torvalds committed
	}
	gss_msg->ctx = gss_get_ctx(ctx);
	err = mlen;

err_release_msg:
	spin_lock(&pipe->lock);
	spin_unlock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
err_put_ctx:
	gss_put_ctx(ctx);
err:
	kfree(buf);
out:
	dprintk("RPC:       %s returning %Zd\n", __func__, err);
Linus Torvalds's avatar
Linus Torvalds committed
	return err;
}

static int gss_pipe_open(struct inode *inode, int new_version)
	struct net *net = inode->i_sb->s_fs_info;
	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id);
	int ret = 0;

	spin_lock(&pipe_version_lock);
		/* First open of any gss pipe determines the version: */
		sn->pipe_version = new_version;
		rpc_wake_up(&pipe_version_rpc_waitqueue);
		wake_up(&pipe_version_waitqueue);
	} else if (sn->pipe_version != new_version) {
		/* Trying to open a pipe of a different version */
		ret = -EBUSY;
		goto out;
	spin_unlock(&pipe_version_lock);
	return ret;

}

static int gss_pipe_open_v0(struct inode *inode)
{
	return gss_pipe_open(inode, 0);
}

static int gss_pipe_open_v1(struct inode *inode)
{
	return gss_pipe_open(inode, 1);
Linus Torvalds's avatar
Linus Torvalds committed
static void
gss_pipe_release(struct inode *inode)
{
	struct net *net = inode->i_sb->s_fs_info;
	struct rpc_pipe *pipe = RPC_I(inode)->pipe;
	struct gss_upcall_msg *gss_msg;
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock(&pipe->lock);
	list_for_each_entry(gss_msg, &pipe->in_downcall, list) {
Linus Torvalds's avatar
Linus Torvalds committed

		if (!list_empty(&gss_msg->msg.list))
			continue;
Linus Torvalds's avatar
Linus Torvalds committed
		gss_msg->msg.errno = -EPIPE;
		atomic_inc(&gss_msg->count);
		__gss_unhash_msg(gss_msg);
		spin_unlock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
		gss_release_msg(gss_msg);
		goto restart;
Linus Torvalds's avatar
Linus Torvalds committed
	}
	spin_unlock(&pipe->lock);
Linus Torvalds's avatar
Linus Torvalds committed
}

static void
gss_pipe_destroy_msg(struct rpc_pipe_msg *msg)
{
	struct gss_upcall_msg *gss_msg = container_of(msg, struct gss_upcall_msg, msg);

	if (msg->errno < 0) {
		dprintk("RPC:       %s releasing msg %p\n",
			__func__, gss_msg);
Linus Torvalds's avatar
Linus Torvalds committed
		atomic_inc(&gss_msg->count);
		gss_unhash_msg(gss_msg);
		if (msg->errno == -ETIMEDOUT)
			warn_gssd();
Linus Torvalds's avatar
Linus Torvalds committed
		gss_release_msg(gss_msg);
	}
}

static void gss_pipe_dentry_destroy(struct dentry *dir,
		struct rpc_pipe_dir_object *pdo)
	struct gss_pipe *gss_pipe = pdo->pdo_data;
	struct rpc_pipe *pipe = gss_pipe->pipe;
	if (pipe->dentry != NULL) {
		rpc_unlink(pipe->dentry);
		pipe->dentry = NULL;
static int gss_pipe_dentry_create(struct dentry *dir,
		struct rpc_pipe_dir_object *pdo)
	dentry = rpc_mkpipe_dentry(dir, p->name, p->clnt, p->pipe);
	if (IS_ERR(dentry))
		return PTR_ERR(dentry);
	p->pipe->dentry = dentry;
	return 0;
}

static const struct rpc_pipe_dir_object_ops gss_pipe_dir_object_ops = {
	.create = gss_pipe_dentry_create,
	.destroy = gss_pipe_dentry_destroy,
};

static struct gss_pipe *gss_pipe_alloc(struct rpc_clnt *clnt,
		const char *name,
		const struct rpc_pipe_ops *upcall_ops)
{
	struct gss_pipe *p;
	int err = -ENOMEM;
	p = kmalloc(sizeof(*p), GFP_KERNEL);
	if (p == NULL)
	p->pipe = rpc_mkpipe_data(upcall_ops, RPC_PIPE_WAIT_FOR_OPEN);
	if (IS_ERR(p->pipe)) {
		err = PTR_ERR(p->pipe);
		goto err_free_gss_pipe;
	rpc_init_pipe_dir_object(&p->pdo,
			&gss_pipe_dir_object_ops,
			p);
struct gss_alloc_pdo {
	struct rpc_clnt *clnt;
	const char *name;
	const struct rpc_pipe_ops *upcall_ops;
};

static int gss_pipe_match_pdo(struct rpc_pipe_dir_object *pdo, void *data)
{
	struct gss_pipe *gss_pipe;
	struct gss_alloc_pdo *args = data;

	if (pdo->pdo_ops != &gss_pipe_dir_object_ops)
		return 0;
	gss_pipe = container_of(pdo, struct gss_pipe, pdo);
	if (strcmp(gss_pipe->name, args->name) != 0)
		return 0;
	if (!kref_get_unless_zero(&gss_pipe->kref))
		return 0;
	return 1;
}

static struct rpc_pipe_dir_object *gss_pipe_alloc_pdo(void *data)
{
	struct gss_pipe *gss_pipe;
	struct gss_alloc_pdo *args = data;

	gss_pipe = gss_pipe_alloc(args->clnt, args->name, args->upcall_ops);
	if (!IS_ERR(gss_pipe))
		return &gss_pipe->pdo;
	return NULL;
}

static struct gss_pipe *gss_pipe_get(struct rpc_clnt *clnt,
		const char *name,
		const struct rpc_pipe_ops *upcall_ops)
{
	struct net *net = rpc_net_ns(clnt);
	struct rpc_pipe_dir_object *pdo;
	struct gss_alloc_pdo args = {
		.clnt = clnt,
		.name = name,
		.upcall_ops = upcall_ops,
	};

	pdo = rpc_find_or_alloc_pipe_dir_object(net,
			&clnt->cl_pipedir_objects,
			gss_pipe_match_pdo,
			gss_pipe_alloc_pdo,
			&args);
	if (pdo != NULL)
		return container_of(pdo, struct gss_pipe, pdo);
	return ERR_PTR(-ENOMEM);
}

static void __gss_pipe_free(struct gss_pipe *p)
	struct rpc_clnt *clnt = p->clnt;
	struct net *net = rpc_net_ns(clnt);
	rpc_remove_pipe_dir_object(net,
			&clnt->cl_pipedir_objects,
			&p->pdo);
	rpc_destroy_pipe_data(p->pipe);
	kfree(p);
static void __gss_pipe_release(struct kref *kref)
{
	struct gss_pipe *p = container_of(kref, struct gss_pipe, kref);

	__gss_pipe_free(p);
}

static void gss_pipe_free(struct gss_pipe *p)
		kref_put(&p->kref, __gss_pipe_release);
/*
 * NOTE: we have the opportunity to use different
Linus Torvalds's avatar
Linus Torvalds committed
 * parameters based on the input flavor (which must be a pseudoflavor)
 */
static struct gss_auth *
gss_create_new(struct rpc_auth_create_args *args, struct rpc_clnt *clnt)
Linus Torvalds's avatar
Linus Torvalds committed
{
	rpc_authflavor_t flavor = args->pseudoflavor;
Linus Torvalds's avatar
Linus Torvalds committed
	struct gss_auth *gss_auth;
Linus Torvalds's avatar
Linus Torvalds committed
	struct rpc_auth * auth;
	int err = -ENOMEM; /* XXX? */
Linus Torvalds's avatar
Linus Torvalds committed

	dprintk("RPC:       creating GSS authenticator for client %p\n", clnt);
Linus Torvalds's avatar
Linus Torvalds committed

	if (!try_module_get(THIS_MODULE))
Linus Torvalds's avatar
Linus Torvalds committed
	if (!(gss_auth = kmalloc(sizeof(*gss_auth), GFP_KERNEL)))
		goto out_dec;
	INIT_HLIST_NODE(&gss_auth->hash);
	gss_auth->target_name = NULL;
	if (args->target_name) {
		gss_auth->target_name = kstrdup(args->target_name, GFP_KERNEL);
		if (gss_auth->target_name == NULL)
			goto err_free;
	}
Linus Torvalds's avatar
Linus Torvalds committed
	gss_auth->client = clnt;
	gss_auth->net = get_net(rpc_net_ns(clnt));
Linus Torvalds's avatar
Linus Torvalds committed
	gss_auth->mech = gss_mech_get_by_pseudoflavor(flavor);
	if (!gss_auth->mech) {
		dprintk("RPC:       Pseudoflavor %d not found!\n", flavor);
		goto err_put_net;
Linus Torvalds's avatar
Linus Torvalds committed
	}
	gss_auth->service = gss_pseudoflavor_to_service(gss_auth->mech, flavor);
	if (gss_auth->service == 0)
		goto err_put_mech;
	if (!gssd_running(gss_auth->net))
		goto err_put_mech;