callback.c 6.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/*
 * linux/fs/nfs/callback.c
 *
 * Copyright (C) 2004 Trond Myklebust
 *
 * NFSv4 callback handling
 */

#include <linux/completion.h>
#include <linux/ip.h>
#include <linux/module.h>
#include <linux/smp_lock.h>
#include <linux/sunrpc/svc.h>
#include <linux/sunrpc/svcsock.h>
#include <linux/nfs_fs.h>
I
Ingo Molnar 已提交
16
#include <linux/mutex.h>
17
#include <linux/freezer.h>
18
#include <linux/kthread.h>
19
#include <linux/sunrpc/svcauth_gss.h>
20 21 22

#include <net/inet_sock.h>

23
#include "nfs4_fs.h"
L
Linus Torvalds 已提交
24
#include "callback.h"
25
#include "internal.h"
L
Linus Torvalds 已提交
26 27 28 29 30

#define NFSDBG_FACILITY NFSDBG_CALLBACK

struct nfs_callback_data {
	unsigned int users;
31
	struct svc_rqst *rqst;
32
	struct task_struct *task;
L
Linus Torvalds 已提交
33 34 35
};

static struct nfs_callback_data nfs_callback_info;
I
Ingo Molnar 已提交
36
static DEFINE_MUTEX(nfs_callback_mutex);
L
Linus Torvalds 已提交
37 38
static struct svc_program nfs4_callback_program;

39
unsigned int nfs_callback_set_tcpport;
L
Linus Torvalds 已提交
40
unsigned short nfs_callback_tcpport;
41
unsigned short nfs_callback_tcpport6;
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
static const int nfs_set_port_min = 0;
static const int nfs_set_port_max = 65535;

static int param_set_port(const char *val, struct kernel_param *kp)
{
	char *endp;
	int num = simple_strtol(val, &endp, 0);
	if (endp == val || *endp || num < nfs_set_port_min || num > nfs_set_port_max)
		return -EINVAL;
	*((int *)kp->arg) = num;
	return 0;
}

module_param_call(callback_tcpport, param_set_port, param_get_int,
		 &nfs_callback_set_tcpport, 0644);
L
Linus Torvalds 已提交
57 58 59 60

/*
 * This is the callback kernel thread.
 */
61
static int
62
nfs4_callback_svc(void *vrqstp)
L
Linus Torvalds 已提交
63
{
64
	int err, preverr = 0;
65
	struct svc_rqst *rqstp = vrqstp;
L
Linus Torvalds 已提交
66

67
	set_freezable();
L
Linus Torvalds 已提交
68

69 70 71 72 73 74
	/*
	 * FIXME: do we really need to run this under the BKL? If so, please
	 * add a comment about what it's intended to protect.
	 */
	lock_kernel();
	while (!kthread_should_stop()) {
L
Linus Torvalds 已提交
75 76 77
		/*
		 * Listen for a request on the socket
		 */
78
		err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT);
79 80
		if (err == -EAGAIN || err == -EINTR) {
			preverr = err;
L
Linus Torvalds 已提交
81
			continue;
82
		}
L
Linus Torvalds 已提交
83
		if (err < 0) {
84 85 86 87 88 89 90
			if (err != preverr) {
				printk(KERN_WARNING "%s: unexpected error "
					"from svc_recv (%d)\n", __func__, err);
				preverr = err;
			}
			schedule_timeout_uninterruptible(HZ);
			continue;
L
Linus Torvalds 已提交
91
		}
92
		preverr = err;
93
		svc_process(rqstp);
L
Linus Torvalds 已提交
94 95
	}
	unlock_kernel();
96
	return 0;
L
Linus Torvalds 已提交
97 98 99
}

/*
100
 * Prepare to bring up the NFSv4 callback service
L
Linus Torvalds 已提交
101
 */
102 103
struct svc_rqst *
nfs4_callback_up(struct svc_serv *serv)
L
Linus Torvalds 已提交
104
{
105
	int ret;
106

107
	ret = svc_create_xprt(serv, "tcp", PF_INET,
108
				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
109
	if (ret <= 0)
110
		goto out_err;
111
	nfs_callback_tcpport = ret;
112
	dprintk("NFS: Callback listener port = %u (af %u)\n",
113
			nfs_callback_tcpport, PF_INET);
114

115 116 117 118 119 120 121 122 123 124 125
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	ret = svc_create_xprt(serv, "tcp", PF_INET6,
				nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS);
	if (ret > 0) {
		nfs_callback_tcpport6 = ret;
		dprintk("NFS: Callback listener port = %u (af %u)\n",
				nfs_callback_tcpport6, PF_INET6);
	} else if (ret != -EAFNOSUPPORT)
		goto out_err;
#endif	/* defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) */

126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
	return svc_prepare_thread(serv, &serv->sv_pools[0]);

out_err:
	if (ret == 0)
		ret = -ENOMEM;
	return ERR_PTR(ret);
}

/*
 * Bring up the callback thread if it is not already up.
 */
int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt)
{
	struct svc_serv *serv = NULL;
	struct svc_rqst *rqstp;
	int (*callback_svc)(void *vrqstp);
	char svc_name[12];
	int ret = 0;

	mutex_lock(&nfs_callback_mutex);
	if (nfs_callback_info.users++ || nfs_callback_info.task != NULL)
		goto out;
	serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, NULL);
	if (!serv) {
		ret = -ENOMEM;
		goto out_err;
	}

	if (!minorversion) {
		rqstp = nfs4_callback_up(serv);
		callback_svc = nfs4_callback_svc;
	} else {
		BUG();	/* for now */
	}

	if (IS_ERR(rqstp)) {
		ret = PTR_ERR(rqstp);
163
		goto out_err;
164 165 166 167
	}

	svc_sock_update_bufs(serv);

168 169 170
	sprintf(svc_name, "nfsv4.%u-svc", minorversion);
	nfs_callback_info.rqst = rqstp;
	nfs_callback_info.task = kthread_run(callback_svc,
171
					     nfs_callback_info.rqst,
172
					     svc_name);
173 174
	if (IS_ERR(nfs_callback_info.task)) {
		ret = PTR_ERR(nfs_callback_info.task);
175 176
		svc_exit_thread(nfs_callback_info.rqst);
		nfs_callback_info.rqst = NULL;
177 178 179
		nfs_callback_info.task = NULL;
		goto out_err;
	}
L
Linus Torvalds 已提交
180
out:
181 182
	/*
	 * svc_create creates the svc_serv with sv_nrthreads == 1, and then
183
	 * svc_prepare_thread increments that. So we need to call svc_destroy
184 185 186 187 188
	 * on both success and failure so that the refcount is 1 when the
	 * thread exits.
	 */
	if (serv)
		svc_destroy(serv);
I
Ingo Molnar 已提交
189
	mutex_unlock(&nfs_callback_mutex);
L
Linus Torvalds 已提交
190
	return ret;
191
out_err:
192 193
	dprintk("NFS: Couldn't create callback socket or server thread; "
		"err = %d\n", ret);
L
Linus Torvalds 已提交
194 195 196 197 198
	nfs_callback_info.users--;
	goto out;
}

/*
199
 * Kill the callback thread if it's no longer being used.
L
Linus Torvalds 已提交
200
 */
201
void nfs_callback_down(void)
L
Linus Torvalds 已提交
202
{
I
Ingo Molnar 已提交
203
	mutex_lock(&nfs_callback_mutex);
204
	nfs_callback_info.users--;
205
	if (nfs_callback_info.users == 0 && nfs_callback_info.task != NULL) {
206
		kthread_stop(nfs_callback_info.task);
207 208 209 210
		svc_exit_thread(nfs_callback_info.rqst);
		nfs_callback_info.rqst = NULL;
		nfs_callback_info.task = NULL;
	}
I
Ingo Molnar 已提交
211
	mutex_unlock(&nfs_callback_mutex);
L
Linus Torvalds 已提交
212 213
}

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
static int check_gss_callback_principal(struct nfs_client *clp,
					struct svc_rqst *rqstp)
{
	struct rpc_clnt *r = clp->cl_rpcclient;
	char *p = svc_gss_principal(rqstp);

	/*
	 * It might just be a normal user principal, in which case
	 * userspace won't bother to tell us the name at all.
	 */
	if (p == NULL)
		return SVC_DENIED;

	/* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */

	if (memcmp(p, "nfs@", 4) != 0)
		return SVC_DENIED;
	p += 4;
	if (strcmp(p, r->cl_server) != 0)
		return SVC_DENIED;
	return SVC_OK;
}

L
Linus Torvalds 已提交
237 238
static int nfs_callback_authenticate(struct svc_rqst *rqstp)
{
239
	struct nfs_client *clp;
240
	RPC_IFDEBUG(char buf[RPC_MAX_ADDRBUFLEN]);
241
	int ret = SVC_OK;
L
Linus Torvalds 已提交
242 243

	/* Don't talk to strangers */
244
	clp = nfs_find_client(svc_addr(rqstp), 4);
L
Linus Torvalds 已提交
245 246
	if (clp == NULL)
		return SVC_DROP;
247

248
	dprintk("%s: %s NFSv4 callback!\n", __func__,
249 250
			svc_print_addr(rqstp, buf, sizeof(buf)));

L
Linus Torvalds 已提交
251 252 253
	switch (rqstp->rq_authop->flavour) {
		case RPC_AUTH_NULL:
			if (rqstp->rq_proc != CB_NULL)
254
				ret = SVC_DENIED;
L
Linus Torvalds 已提交
255 256 257 258
			break;
		case RPC_AUTH_UNIX:
			break;
		case RPC_AUTH_GSS:
259 260
			ret = check_gss_callback_principal(clp, rqstp);
			break;
L
Linus Torvalds 已提交
261
		default:
262
			ret = SVC_DENIED;
L
Linus Torvalds 已提交
263
	}
264 265
	nfs_put_client(clp);
	return ret;
L
Linus Torvalds 已提交
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
}

/*
 * Define NFS4 callback program
 */
static struct svc_version *nfs4_callback_version[] = {
	[1] = &nfs4_callback_version1,
};

static struct svc_stat nfs4_callback_stats;

static struct svc_program nfs4_callback_program = {
	.pg_prog = NFS4_CALLBACK,			/* RPC service number */
	.pg_nvers = ARRAY_SIZE(nfs4_callback_version),	/* Number of entries */
	.pg_vers = nfs4_callback_version,		/* version table */
	.pg_name = "NFSv4 callback",			/* service name */
	.pg_class = "nfs",				/* authentication class */
	.pg_stats = &nfs4_callback_stats,
	.pg_authenticate = nfs_callback_authenticate,
};