Commit | Line | Data |
---|---|---|
b2441318 | 1 | // SPDX-License-Identifier: GPL-2.0 |
1da177e4 LT |
2 | /* |
3 | * linux/fs/nfs/callback.c | |
4 | * | |
5 | * Copyright (C) 2004 Trond Myklebust | |
6 | * | |
7 | * NFSv4 callback handling | |
8 | */ | |
9 | ||
1da177e4 LT |
10 | #include <linux/completion.h> |
11 | #include <linux/ip.h> | |
12 | #include <linux/module.h> | |
3f07c014 | 13 | #include <linux/sched/signal.h> |
1da177e4 LT |
14 | #include <linux/sunrpc/svc.h> |
15 | #include <linux/sunrpc/svcsock.h> | |
16 | #include <linux/nfs_fs.h> | |
758201e2 | 17 | #include <linux/errno.h> |
353ab6e9 | 18 | #include <linux/mutex.h> |
83144186 | 19 | #include <linux/freezer.h> |
945b34a7 | 20 | #include <linux/sunrpc/svcauth_gss.h> |
a43cde94 | 21 | #include <linux/sunrpc/bc_xprt.h> |
14c85021 ACM |
22 | |
23 | #include <net/inet_sock.h> | |
24 | ||
4ce79717 | 25 | #include "nfs4_fs.h" |
1da177e4 | 26 | #include "callback.h" |
24c8dbbb | 27 | #include "internal.h" |
bbe0a3aa | 28 | #include "netns.h" |
1da177e4 LT |
29 | |
30 | #define NFSDBG_FACILITY NFSDBG_CALLBACK | |
31 | ||
32 | struct nfs_callback_data { | |
33 | unsigned int users; | |
a43cde94 | 34 | struct svc_serv *serv; |
1da177e4 LT |
35 | }; |
36 | ||
e82dc22d | 37 | static struct nfs_callback_data nfs_callback_info[NFS4_MAX_MINOR_VERSION + 1]; |
353ab6e9 | 38 | static DEFINE_MUTEX(nfs_callback_mutex); |
1da177e4 LT |
39 | static struct svc_program nfs4_callback_program; |
40 | ||
c946556b SK |
41 | static int nfs4_callback_up_net(struct svc_serv *serv, struct net *net) |
42 | { | |
4df493a2 | 43 | const struct cred *cred = current_cred(); |
c946556b | 44 | int ret; |
bbe0a3aa | 45 | struct nfs_net *nn = net_generic(net, nfs_net_id); |
c946556b | 46 | |
352ad314 CL |
47 | ret = svc_xprt_create(serv, "tcp", net, PF_INET, |
48 | nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS, | |
49 | cred); | |
c946556b SK |
50 | if (ret <= 0) |
51 | goto out_err; | |
bbe0a3aa | 52 | nn->nfs_callback_tcpport = ret; |
e4949e4b VA |
53 | dprintk("NFS: Callback listener port = %u (af %u, net %x)\n", |
54 | nn->nfs_callback_tcpport, PF_INET, net->ns.inum); | |
c946556b | 55 | |
352ad314 CL |
56 | ret = svc_xprt_create(serv, "tcp", net, PF_INET6, |
57 | nfs_callback_set_tcpport, SVC_SOCK_ANONYMOUS, | |
58 | cred); | |
c946556b | 59 | if (ret > 0) { |
29dcc16a | 60 | nn->nfs_callback_tcpport6 = ret; |
91bd2ffa | 61 | dprintk("NFS: Callback listener port = %u (af %u, net %x)\n", |
e4949e4b | 62 | nn->nfs_callback_tcpport6, PF_INET6, net->ns.inum); |
c946556b SK |
63 | } else if (ret != -EAFNOSUPPORT) |
64 | goto out_err; | |
65 | return 0; | |
66 | ||
67 | out_err: | |
68 | return (ret) ? ret : -ENOMEM; | |
69 | } | |
70 | ||
1da177e4 | 71 | /* |
e82dc22d | 72 | * This is the NFSv4 callback kernel thread. |
1da177e4 | 73 | */ |
a277e33c | 74 | static int |
71468513 | 75 | nfs4_callback_svc(void *vrqstp) |
1da177e4 | 76 | { |
5b444cc9 | 77 | int err; |
a277e33c | 78 | struct svc_rqst *rqstp = vrqstp; |
1da177e4 | 79 | |
83144186 | 80 | set_freezable(); |
1da177e4 | 81 | |
ed6473dd TM |
82 | while (!kthread_freezable_should_stop(NULL)) { |
83 | ||
84 | if (signal_pending(current)) | |
85 | flush_signals(current); | |
1da177e4 LT |
86 | /* |
87 | * Listen for a request on the socket | |
88 | */ | |
6fb2b47f | 89 | err = svc_recv(rqstp, MAX_SCHEDULE_TIMEOUT); |
5b444cc9 | 90 | if (err == -EAGAIN || err == -EINTR) |
1da177e4 | 91 | continue; |
6fb2b47f | 92 | svc_process(rqstp); |
1da177e4 | 93 | } |
f49169c9 | 94 | |
ed6473dd | 95 | svc_exit_thread(rqstp); |
a277e33c | 96 | return 0; |
1da177e4 LT |
97 | } |
98 | ||
a43cde94 RL |
99 | #if defined(CONFIG_NFS_V4_1) |
100 | /* | |
101 | * The callback service for NFSv4.1 callbacks | |
102 | */ | |
103 | static int | |
104 | nfs41_callback_svc(void *vrqstp) | |
105 | { | |
106 | struct svc_rqst *rqstp = vrqstp; | |
107 | struct svc_serv *serv = rqstp->rq_server; | |
108 | struct rpc_rqst *req; | |
109 | int error; | |
110 | DEFINE_WAIT(wq); | |
111 | ||
112 | set_freezable(); | |
113 | ||
ed6473dd TM |
114 | while (!kthread_freezable_should_stop(NULL)) { |
115 | ||
116 | if (signal_pending(current)) | |
117 | flush_signals(current); | |
25d280aa | 118 | |
5d05e54a | 119 | prepare_to_wait(&serv->sv_cb_waitq, &wq, TASK_INTERRUPTIBLE); |
a43cde94 RL |
120 | spin_lock_bh(&serv->sv_cb_lock); |
121 | if (!list_empty(&serv->sv_cb_list)) { | |
122 | req = list_first_entry(&serv->sv_cb_list, | |
123 | struct rpc_rqst, rq_bc_list); | |
124 | list_del(&req->rq_bc_list); | |
125 | spin_unlock_bh(&serv->sv_cb_lock); | |
6ffa30d3 | 126 | finish_wait(&serv->sv_cb_waitq, &wq); |
a43cde94 RL |
127 | dprintk("Invoking bc_svc_process()\n"); |
128 | error = bc_svc_process(serv, req, rqstp); | |
129 | dprintk("bc_svc_process() returned w/ error code= %d\n", | |
130 | error); | |
131 | } else { | |
132 | spin_unlock_bh(&serv->sv_cb_lock); | |
ed6473dd TM |
133 | if (!kthread_should_stop()) |
134 | schedule(); | |
6ffa30d3 | 135 | finish_wait(&serv->sv_cb_waitq, &wq); |
a43cde94 | 136 | } |
a43cde94 | 137 | } |
f49169c9 | 138 | |
ed6473dd | 139 | svc_exit_thread(rqstp); |
a43cde94 RL |
140 | return 0; |
141 | } | |
142 | ||
a43cde94 | 143 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, |
691c457a | 144 | struct svc_serv *serv) |
a43cde94 RL |
145 | { |
146 | if (minorversion) | |
691c457a SK |
147 | /* |
148 | * Save the svc_serv in the transport so that it can | |
149 | * be referenced when the session backchannel is initialized | |
150 | */ | |
151 | xprt->bc_serv = serv; | |
a43cde94 RL |
152 | } |
153 | #else | |
a43cde94 | 154 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, |
691c457a | 155 | struct svc_serv *serv) |
a43cde94 RL |
156 | { |
157 | } | |
158 | #endif /* CONFIG_NFS_V4_1 */ | |
159 | ||
8e246144 SK |
160 | static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt, |
161 | struct svc_serv *serv) | |
162 | { | |
5405fc44 | 163 | int nrservs = nfs_callback_nr_threads; |
8e246144 | 164 | int ret; |
8e246144 SK |
165 | |
166 | nfs_callback_bc_serv(minorversion, xprt, serv); | |
167 | ||
5405fc44 TM |
168 | if (nrservs < NFS4_MIN_NR_CALLBACK_THREADS) |
169 | nrservs = NFS4_MIN_NR_CALLBACK_THREADS; | |
170 | ||
ec52361d | 171 | if (serv->sv_nrthreads == nrservs) |
23c20ecd SK |
172 | return 0; |
173 | ||
3ebdbe52 | 174 | ret = svc_set_num_threads(serv, NULL, nrservs); |
bb6aeba7 | 175 | if (ret) { |
3ebdbe52 | 176 | svc_set_num_threads(serv, NULL, 0); |
e9b7e917 | 177 | return ret; |
8e246144 SK |
178 | } |
179 | dprintk("nfs_callback_up: service started\n"); | |
180 | return 0; | |
181 | } | |
182 | ||
b3d19c51 SK |
183 | static void nfs_callback_down_net(u32 minorversion, struct svc_serv *serv, struct net *net) |
184 | { | |
185 | struct nfs_net *nn = net_generic(net, nfs_net_id); | |
186 | ||
187 | if (--nn->cb_users[minorversion]) | |
188 | return; | |
189 | ||
e4949e4b | 190 | dprintk("NFS: destroy per-net callback data; net=%x\n", net->ns.inum); |
c7d7ec8f | 191 | svc_xprt_destroy_all(serv, net); |
b3d19c51 SK |
192 | } |
193 | ||
76566773 CL |
194 | static int nfs_callback_up_net(int minorversion, struct svc_serv *serv, |
195 | struct net *net, struct rpc_xprt *xprt) | |
c946556b | 196 | { |
b3d19c51 | 197 | struct nfs_net *nn = net_generic(net, nfs_net_id); |
c946556b SK |
198 | int ret; |
199 | ||
b3d19c51 SK |
200 | if (nn->cb_users[minorversion]++) |
201 | return 0; | |
202 | ||
e4949e4b | 203 | dprintk("NFS: create per-net callback data; net=%x\n", net->ns.inum); |
c946556b SK |
204 | |
205 | ret = svc_bind(serv, net); | |
206 | if (ret < 0) { | |
207 | printk(KERN_WARNING "NFS: bind callback service failed\n"); | |
208 | goto err_bind; | |
209 | } | |
210 | ||
a289ce53 | 211 | ret = 0; |
d55b352b | 212 | if (!IS_ENABLED(CONFIG_NFS_V4_1) || minorversion == 0) |
76566773 | 213 | ret = nfs4_callback_up_net(serv, net); |
a289ce53 | 214 | else if (xprt->ops->bc_setup) |
0ad30ff6 | 215 | set_bc_enabled(serv); |
a289ce53 VA |
216 | else |
217 | ret = -EPROTONOSUPPORT; | |
c946556b SK |
218 | |
219 | if (ret < 0) { | |
220 | printk(KERN_ERR "NFS: callback service start failed\n"); | |
221 | goto err_socks; | |
222 | } | |
223 | return 0; | |
224 | ||
225 | err_socks: | |
226 | svc_rpcb_cleanup(serv, net); | |
227 | err_bind: | |
98b0f80c | 228 | nn->cb_users[minorversion]--; |
23c20ecd | 229 | dprintk("NFS: Couldn't create callback socket: err = %d; " |
e4949e4b | 230 | "net = %x\n", ret, net->ns.inum); |
c946556b SK |
231 | return ret; |
232 | } | |
233 | ||
dd018428 SK |
234 | static struct svc_serv *nfs_callback_create_svc(int minorversion) |
235 | { | |
236 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; | |
37902c63 | 237 | int (*threadfn)(void *data); |
dd018428 SK |
238 | struct svc_serv *serv; |
239 | ||
240 | /* | |
241 | * Check whether we're already up and running. | |
242 | */ | |
df5e49c8 | 243 | if (cb_info->serv) |
df5e49c8 | 244 | return svc_get(cb_info->serv); |
dd018428 SK |
245 | |
246 | /* | |
247 | * Sanity check: if there's no task, | |
248 | * we should be the first user ... | |
249 | */ | |
250 | if (cb_info->users) | |
251 | printk(KERN_WARNING "nfs_callback_create_svc: no kthread, %d users??\n", | |
252 | cb_info->users); | |
253 | ||
37902c63 CL |
254 | threadfn = nfs4_callback_svc; |
255 | #if defined(CONFIG_NFS_V4_1) | |
256 | if (minorversion) | |
257 | threadfn = nfs41_callback_svc; | |
258 | #else | |
259 | if (minorversion) | |
260 | return ERR_PTR(-ENOTSUPP); | |
261 | #endif | |
262 | serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, | |
263 | threadfn); | |
dd018428 SK |
264 | if (!serv) { |
265 | printk(KERN_ERR "nfs_callback_create_svc: create service failed\n"); | |
266 | return ERR_PTR(-ENOMEM); | |
267 | } | |
3b01c11e | 268 | cb_info->serv = serv; |
dd018428 SK |
269 | /* As there is only one thread we need to over-ride the |
270 | * default maximum of 80 connections | |
271 | */ | |
272 | serv->sv_maxconn = 1024; | |
273 | dprintk("nfs_callback_create_svc: service created\n"); | |
274 | return serv; | |
275 | } | |
276 | ||
71468513 BH |
277 | /* |
278 | * Bring up the callback thread if it is not already up. | |
279 | */ | |
280 | int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) | |
281 | { | |
dd018428 | 282 | struct svc_serv *serv; |
e82dc22d | 283 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; |
23c20ecd | 284 | int ret; |
c946556b | 285 | struct net *net = xprt->xprt_net; |
71468513 BH |
286 | |
287 | mutex_lock(&nfs_callback_mutex); | |
dd018428 SK |
288 | |
289 | serv = nfs_callback_create_svc(minorversion); | |
290 | if (IS_ERR(serv)) { | |
291 | ret = PTR_ERR(serv); | |
292 | goto err_create; | |
293 | } | |
294 | ||
76566773 | 295 | ret = nfs_callback_up_net(minorversion, serv, net, xprt); |
c946556b SK |
296 | if (ret < 0) |
297 | goto err_net; | |
9793f7c8 | 298 | |
8e246144 SK |
299 | ret = nfs_callback_start_svc(minorversion, xprt, serv); |
300 | if (ret < 0) | |
301 | goto err_start; | |
a277e33c | 302 | |
23c20ecd | 303 | cb_info->users++; |
23c20ecd | 304 | err_net: |
3b01c11e TM |
305 | if (!cb_info->users) |
306 | cb_info->serv = NULL; | |
8c62d127 | 307 | svc_put(serv); |
dd018428 | 308 | err_create: |
353ab6e9 | 309 | mutex_unlock(&nfs_callback_mutex); |
1da177e4 | 310 | return ret; |
8e246144 SK |
311 | |
312 | err_start: | |
b3d19c51 | 313 | nfs_callback_down_net(minorversion, serv, net); |
23c20ecd SK |
314 | dprintk("NFS: Couldn't create server thread; err = %d\n", ret); |
315 | goto err_net; | |
1da177e4 LT |
316 | } |
317 | ||
318 | /* | |
5afc597c | 319 | * Kill the callback thread if it's no longer being used. |
1da177e4 | 320 | */ |
c8ceb412 | 321 | void nfs_callback_down(int minorversion, struct net *net) |
1da177e4 | 322 | { |
e82dc22d | 323 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; |
bb6aeba7 | 324 | struct svc_serv *serv; |
e82dc22d | 325 | |
353ab6e9 | 326 | mutex_lock(&nfs_callback_mutex); |
bb6aeba7 TM |
327 | serv = cb_info->serv; |
328 | nfs_callback_down_net(minorversion, serv, net); | |
e82dc22d | 329 | cb_info->users--; |
3b01c11e | 330 | if (cb_info->users == 0) { |
bb6aeba7 | 331 | svc_get(serv); |
3ebdbe52 | 332 | svc_set_num_threads(serv, NULL, 0); |
8c62d127 | 333 | svc_put(serv); |
1dc42e04 | 334 | dprintk("nfs_callback_down: service destroyed\n"); |
e82dc22d | 335 | cb_info->serv = NULL; |
5afc597c | 336 | } |
353ab6e9 | 337 | mutex_unlock(&nfs_callback_mutex); |
1da177e4 LT |
338 | } |
339 | ||
778be232 AA |
340 | /* Boolean check of RPC_AUTH_GSS principal */ |
341 | int | |
342 | check_gss_callback_principal(struct nfs_client *clp, struct svc_rqst *rqstp) | |
945b34a7 | 343 | { |
03a4e1f6 | 344 | char *p = rqstp->rq_cred.cr_principal; |
945b34a7 | 345 | |
778be232 AA |
346 | if (rqstp->rq_authop->flavour != RPC_AUTH_GSS) |
347 | return 1; | |
348 | ||
ece0de63 AA |
349 | /* No RPC_AUTH_GSS on NFSv4.1 back channel yet */ |
350 | if (clp->cl_minorversion != 0) | |
778be232 | 351 | return 0; |
945b34a7 OK |
352 | /* |
353 | * It might just be a normal user principal, in which case | |
354 | * userspace won't bother to tell us the name at all. | |
355 | */ | |
356 | if (p == NULL) | |
778be232 | 357 | return 0; |
945b34a7 | 358 | |
f11b2a1c JL |
359 | /* |
360 | * Did we get the acceptor from userland during the SETCLIENID | |
361 | * negotiation? | |
362 | */ | |
363 | if (clp->cl_acceptor) | |
364 | return !strcmp(p, clp->cl_acceptor); | |
365 | ||
366 | /* | |
367 | * Otherwise try to verify it using the cl_hostname. Note that this | |
368 | * doesn't work if a non-canonical hostname was used in the devname. | |
369 | */ | |
370 | ||
945b34a7 OK |
371 | /* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */ |
372 | ||
373 | if (memcmp(p, "nfs@", 4) != 0) | |
778be232 | 374 | return 0; |
945b34a7 | 375 | p += 4; |
4e0038b6 | 376 | if (strcmp(p, clp->cl_hostname) != 0) |
778be232 AA |
377 | return 0; |
378 | return 1; | |
945b34a7 OK |
379 | } |
380 | ||
778be232 AA |
381 | /* |
382 | * pg_authenticate method for nfsv4 callback threads. | |
383 | * | |
384 | * The authflavor has been negotiated, so an incorrect flavor is a server | |
6f02dc88 | 385 | * bug. Deny packets with incorrect authflavor. |
778be232 AA |
386 | * |
387 | * All other checking done after NFS decoding where the nfs_client can be | |
388 | * found in nfs4_callback_compound | |
389 | */ | |
1da177e4 LT |
390 | static int nfs_callback_authenticate(struct svc_rqst *rqstp) |
391 | { | |
5c2465df CL |
392 | rqstp->rq_auth_stat = rpc_autherr_badcred; |
393 | ||
1da177e4 | 394 | switch (rqstp->rq_authop->flavour) { |
778be232 AA |
395 | case RPC_AUTH_NULL: |
396 | if (rqstp->rq_proc != CB_NULL) | |
6f02dc88 | 397 | return SVC_DENIED; |
778be232 AA |
398 | break; |
399 | case RPC_AUTH_GSS: | |
400 | /* No RPC_AUTH_GSS support yet in NFSv4.1 */ | |
401 | if (svc_is_backchannel(rqstp)) | |
6f02dc88 | 402 | return SVC_DENIED; |
1da177e4 | 403 | } |
5c2465df CL |
404 | |
405 | rqstp->rq_auth_stat = rpc_auth_ok; | |
778be232 | 406 | return SVC_OK; |
1da177e4 LT |
407 | } |
408 | ||
409 | /* | |
410 | * Define NFS4 callback program | |
411 | */ | |
e9679189 | 412 | static const struct svc_version *nfs4_callback_version[] = { |
1da177e4 | 413 | [1] = &nfs4_callback_version1, |
07bccc2d | 414 | [4] = &nfs4_callback_version4, |
1da177e4 LT |
415 | }; |
416 | ||
417 | static struct svc_stat nfs4_callback_stats; | |
418 | ||
419 | static struct svc_program nfs4_callback_program = { | |
420 | .pg_prog = NFS4_CALLBACK, /* RPC service number */ | |
421 | .pg_nvers = ARRAY_SIZE(nfs4_callback_version), /* Number of entries */ | |
422 | .pg_vers = nfs4_callback_version, /* version table */ | |
423 | .pg_name = "NFSv4 callback", /* service name */ | |
424 | .pg_class = "nfs", /* authentication class */ | |
425 | .pg_stats = &nfs4_callback_stats, | |
426 | .pg_authenticate = nfs_callback_authenticate, | |
8e5b6773 | 427 | .pg_init_request = svc_generic_init_request, |
642ee6b2 | 428 | .pg_rpcbind_set = svc_generic_rpcbind_set, |
1da177e4 | 429 | }; |