diff options
Diffstat (limited to 'net/sunrpc/auth_gss/svcauth_gss.c')
| -rw-r--r-- | net/sunrpc/auth_gss/svcauth_gss.c | 347 | 
1 files changed, 338 insertions, 9 deletions
diff --git a/net/sunrpc/auth_gss/svcauth_gss.c b/net/sunrpc/auth_gss/svcauth_gss.c index 20eedecc35f..58f5bc32940 100644 --- a/net/sunrpc/auth_gss/svcauth_gss.c +++ b/net/sunrpc/auth_gss/svcauth_gss.c @@ -48,8 +48,8 @@  #include <linux/sunrpc/svcauth.h>  #include <linux/sunrpc/svcauth_gss.h>  #include <linux/sunrpc/cache.h> +#include "gss_rpc_upcall.h" -#include "../netns.h"  #ifdef RPC_DEBUG  # define RPCDBG_FACILITY	RPCDBG_AUTH @@ -988,13 +988,10 @@ gss_write_init_verf(struct cache_detail *cd, struct svc_rqst *rqstp,  }  static inline int -gss_read_verf(struct rpc_gss_wire_cred *gc, -	      struct kvec *argv, __be32 *authp, -	      struct xdr_netobj *in_handle, -	      struct xdr_netobj *in_token) +gss_read_common_verf(struct rpc_gss_wire_cred *gc, +		     struct kvec *argv, __be32 *authp, +		     struct xdr_netobj *in_handle)  { -	struct xdr_netobj tmpobj; -  	/* Read the verifier; should be NULL: */  	*authp = rpc_autherr_badverf;  	if (argv->iov_len < 2 * 4) @@ -1010,6 +1007,23 @@ gss_read_verf(struct rpc_gss_wire_cred *gc,  	if (dup_netobj(in_handle, &gc->gc_ctx))  		return SVC_CLOSE;  	*authp = rpc_autherr_badverf; + +	return 0; +} + +static inline int +gss_read_verf(struct rpc_gss_wire_cred *gc, +	      struct kvec *argv, __be32 *authp, +	      struct xdr_netobj *in_handle, +	      struct xdr_netobj *in_token) +{ +	struct xdr_netobj tmpobj; +	int res; + +	res = gss_read_common_verf(gc, argv, authp, in_handle); +	if (res) +		return res; +  	if (svc_safe_getnetobj(argv, &tmpobj)) {  		kfree(in_handle->data);  		return SVC_DENIED; @@ -1022,6 +1036,40 @@ gss_read_verf(struct rpc_gss_wire_cred *gc,  	return 0;  } +/* Ok this is really heavily depending on a set of semantics in + * how rqstp is set up by svc_recv and pages laid down by the + * server when reading a request. We are basically guaranteed that + * the token lays all down linearly across a set of pages, starting + * at iov_base in rq_arg.head[0] which happens to be the first of a + * set of pages stored in rq_pages[]. + * rq_arg.head[0].iov_base will provide us the page_base to pass + * to the upcall. + */ +static inline int +gss_read_proxy_verf(struct svc_rqst *rqstp, +		    struct rpc_gss_wire_cred *gc, __be32 *authp, +		    struct xdr_netobj *in_handle, +		    struct gssp_in_token *in_token) +{ +	struct kvec *argv = &rqstp->rq_arg.head[0]; +	u32 inlen; +	int res; + +	res = gss_read_common_verf(gc, argv, authp, in_handle); +	if (res) +		return res; + +	inlen = svc_getnl(argv); +	if (inlen > (argv->iov_len + rqstp->rq_arg.page_len)) +		return SVC_DENIED; + +	in_token->pages = rqstp->rq_pages; +	in_token->page_base = (ulong)argv->iov_base & ~PAGE_MASK; +	in_token->page_len = inlen; + +	return 0; +} +  static inline int  gss_write_resv(struct kvec *resv, size_t size_limit,  	       struct xdr_netobj *out_handle, struct xdr_netobj *out_token, @@ -1049,7 +1097,7 @@ gss_write_resv(struct kvec *resv, size_t size_limit,   * the upcall results are available, write the verifier and result.   * Otherwise, drop the request pending an answer to the upcall.   */ -static int svcauth_gss_handle_init(struct svc_rqst *rqstp, +static int svcauth_gss_legacy_init(struct svc_rqst *rqstp,  			struct rpc_gss_wire_cred *gc, __be32 *authp)  {  	struct kvec *argv = &rqstp->rq_arg.head[0]; @@ -1089,6 +1137,278 @@ out:  	return ret;  } +static int gss_proxy_save_rsc(struct cache_detail *cd, +				struct gssp_upcall_data *ud, +				uint64_t *handle) +{ +	struct rsc rsci, *rscp = NULL; +	static atomic64_t ctxhctr; +	long long ctxh; +	struct gss_api_mech *gm = NULL; +	time_t expiry; +	int status = -EINVAL; + +	memset(&rsci, 0, sizeof(rsci)); +	/* context handle */ +	status = -ENOMEM; +	/* the handle needs to be just a unique id, +	 * use a static counter */ +	ctxh = atomic64_inc_return(&ctxhctr); + +	/* make a copy for the caller */ +	*handle = ctxh; + +	/* make a copy for the rsc cache */ +	if (dup_to_netobj(&rsci.handle, (char *)handle, sizeof(uint64_t))) +		goto out; +	rscp = rsc_lookup(cd, &rsci); +	if (!rscp) +		goto out; + +	/* creds */ +	if (!ud->found_creds) { +		/* userspace seem buggy, we should always get at least a +		 * mapping to nobody */ +		dprintk("RPC:       No creds found, marking Negative!\n"); +		set_bit(CACHE_NEGATIVE, &rsci.h.flags); +	} else { + +		/* steal creds */ +		rsci.cred = ud->creds; +		memset(&ud->creds, 0, sizeof(struct svc_cred)); + +		status = -EOPNOTSUPP; +		/* get mech handle from OID */ +		gm = gss_mech_get_by_OID(&ud->mech_oid); +		if (!gm) +			goto out; + +		status = -EINVAL; +		/* mech-specific data: */ +		status = gss_import_sec_context(ud->out_handle.data, +						ud->out_handle.len, +						gm, &rsci.mechctx, +						&expiry, GFP_KERNEL); +		if (status) +			goto out; +	} + +	rsci.h.expiry_time = expiry; +	rscp = rsc_update(cd, &rsci, rscp); +	status = 0; +out: +	gss_mech_put(gm); +	rsc_free(&rsci); +	if (rscp) +		cache_put(&rscp->h, cd); +	else +		status = -ENOMEM; +	return status; +} + +static int svcauth_gss_proxy_init(struct svc_rqst *rqstp, +			struct rpc_gss_wire_cred *gc, __be32 *authp) +{ +	struct kvec *resv = &rqstp->rq_res.head[0]; +	struct xdr_netobj cli_handle; +	struct gssp_upcall_data ud; +	uint64_t handle; +	int status; +	int ret; +	struct net *net = rqstp->rq_xprt->xpt_net; +	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); + +	memset(&ud, 0, sizeof(ud)); +	ret = gss_read_proxy_verf(rqstp, gc, authp, +				  &ud.in_handle, &ud.in_token); +	if (ret) +		return ret; + +	ret = SVC_CLOSE; + +	/* Perform synchronous upcall to gss-proxy */ +	status = gssp_accept_sec_context_upcall(net, &ud); +	if (status) +		goto out; + +	dprintk("RPC:       svcauth_gss: gss major status = %d\n", +			ud.major_status); + +	switch (ud.major_status) { +	case GSS_S_CONTINUE_NEEDED: +		cli_handle = ud.out_handle; +		break; +	case GSS_S_COMPLETE: +		status = gss_proxy_save_rsc(sn->rsc_cache, &ud, &handle); +		if (status) +			goto out; +		cli_handle.data = (u8 *)&handle; +		cli_handle.len = sizeof(handle); +		break; +	default: +		ret = SVC_CLOSE; +		goto out; +	} + +	/* Got an answer to the upcall; use it: */ +	if (gss_write_init_verf(sn->rsc_cache, rqstp, +				&cli_handle, &ud.major_status)) +		goto out; +	if (gss_write_resv(resv, PAGE_SIZE, +			   &cli_handle, &ud.out_token, +			   ud.major_status, ud.minor_status)) +		goto out; + +	ret = SVC_COMPLETE; +out: +	gssp_free_upcall_data(&ud); +	return ret; +} + +DEFINE_SPINLOCK(use_gssp_lock); + +static bool use_gss_proxy(struct net *net) +{ +	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); + +	if (sn->use_gss_proxy != -1) +		return sn->use_gss_proxy; +	spin_lock(&use_gssp_lock); +	/* +	 * If you wanted gss-proxy, you should have said so before +	 * starting to accept requests: +	 */ +	sn->use_gss_proxy = 0; +	spin_unlock(&use_gssp_lock); +	return 0; +} + +static bool set_gss_proxy(struct net *net, int type) +{ +	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); +	int ret = 0; + +	WARN_ON_ONCE(type != 0 && type != 1); +	spin_lock(&use_gssp_lock); +	if (sn->use_gss_proxy == -1 || sn->use_gss_proxy == type) +		sn->use_gss_proxy = type; +	else +		ret = -EBUSY; +	spin_unlock(&use_gssp_lock); +	wake_up(&sn->gssp_wq); +	return ret; +} + +static inline bool gssp_ready(struct sunrpc_net *sn) +{ +	switch (sn->use_gss_proxy) { +		case -1: +			return false; +		case 0: +			return true; +		case 1: +			return sn->gssp_clnt; +	} +	WARN_ON_ONCE(1); +	return false; +} + +static int wait_for_gss_proxy(struct net *net) +{ +	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); + +	return wait_event_interruptible(sn->gssp_wq, gssp_ready(sn)); +} + +#ifdef CONFIG_PROC_FS + +static ssize_t write_gssp(struct file *file, const char __user *buf, +			 size_t count, loff_t *ppos) +{ +	struct net *net = PDE(file->f_path.dentry->d_inode)->data; +	char tbuf[20]; +	unsigned long i; +	int res; + +	if (*ppos || count > sizeof(tbuf)-1) +		return -EINVAL; +	if (copy_from_user(tbuf, buf, count)) +		return -EFAULT; + +	tbuf[count] = 0; +	res = kstrtoul(tbuf, 0, &i); +	if (res) +		return res; +	if (i != 1) +		return -EINVAL; +	res = set_gss_proxy(net, 1); +	if (res) +		return res; +	res = set_gssp_clnt(net); +	if (res) +		return res; +	return count; +} + +static ssize_t read_gssp(struct file *file, char __user *buf, +			 size_t count, loff_t *ppos) +{ +	struct net *net = PDE(file->f_path.dentry->d_inode)->data; +	unsigned long p = *ppos; +	char tbuf[10]; +	size_t len; +	int ret; + +	ret = wait_for_gss_proxy(net); +	if (ret) +		return ret; + +	snprintf(tbuf, sizeof(tbuf), "%d\n", use_gss_proxy(net)); +	len = strlen(tbuf); +	if (p >= len) +		return 0; +	len -= p; +	if (len > count) +		len = count; +	if (copy_to_user(buf, (void *)(tbuf+p), len)) +		return -EFAULT; +	*ppos += len; +	return len; +} + +static const struct file_operations use_gss_proxy_ops = { +	.open = nonseekable_open, +	.write = write_gssp, +	.read = read_gssp, +}; + +static int create_use_gss_proxy_proc_entry(struct net *net) +{ +	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); +	struct proc_dir_entry **p = &sn->use_gssp_proc; + +	sn->use_gss_proxy = -1; +	*p = proc_create_data("use-gss-proxy", S_IFREG|S_IRUSR|S_IWUSR, +			      sn->proc_net_rpc, +			      &use_gss_proxy_ops, net); +	if (!*p) +		return -ENOMEM; +	init_gssp_clnt(sn); +	return 0; +} + +static void destroy_use_gss_proxy_proc_entry(struct net *net) +{ +	struct sunrpc_net *sn = net_generic(net, sunrpc_net_id); + +	if (sn->use_gssp_proc) { +		remove_proc_entry("use-gss-proxy", sn->proc_net_rpc);  +		clear_gssp_clnt(sn); +	} +} + +#endif /* CONFIG_PROC_FS */ +  /*   * Accept an rpcsec packet.   * If context establishment, punt to user space @@ -1155,7 +1475,10 @@ svcauth_gss_accept(struct svc_rqst *rqstp, __be32 *authp)  	switch (gc->gc_proc) {  	case RPC_GSS_PROC_INIT:  	case RPC_GSS_PROC_CONTINUE_INIT: -		return svcauth_gss_handle_init(rqstp, gc, authp); +		if (use_gss_proxy(SVC_NET(rqstp))) +			return svcauth_gss_proxy_init(rqstp, gc, authp); +		else +			return svcauth_gss_legacy_init(rqstp, gc, authp);  	case RPC_GSS_PROC_DATA:  	case RPC_GSS_PROC_DESTROY:  		/* Look up the context, and check the verifier: */ @@ -1530,7 +1853,12 @@ gss_svc_init_net(struct net *net)  	rv = rsi_cache_create_net(net);  	if (rv)  		goto out1; +	rv = create_use_gss_proxy_proc_entry(net); +	if (rv) +		goto out2;  	return 0; +out2: +	destroy_use_gss_proxy_proc_entry(net);  out1:  	rsc_cache_destroy_net(net);  	return rv; @@ -1539,6 +1867,7 @@ out1:  void  gss_svc_shutdown_net(struct net *net)  { +	destroy_use_gss_proxy_proc_entry(net);  	rsi_cache_destroy_net(net);  	rsc_cache_destroy_net(net);  }  |