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 | { |
a277e33c | 77 | struct svc_rqst *rqstp = vrqstp; |
1da177e4 | 78 | |
3391fc92 N |
79 | svc_thread_init_status(rqstp, 0); |
80 | ||
83144186 | 81 | set_freezable(); |
1da177e4 | 82 | |
fa341560 | 83 | while (!svc_thread_should_stop(rqstp)) |
c743b425 | 84 | svc_recv(rqstp); |
f49169c9 | 85 | |
ed6473dd | 86 | svc_exit_thread(rqstp); |
a277e33c | 87 | return 0; |
1da177e4 LT |
88 | } |
89 | ||
a43cde94 | 90 | #if defined(CONFIG_NFS_V4_1) |
a43cde94 | 91 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, |
691c457a | 92 | struct svc_serv *serv) |
a43cde94 RL |
93 | { |
94 | if (minorversion) | |
691c457a SK |
95 | /* |
96 | * Save the svc_serv in the transport so that it can | |
97 | * be referenced when the session backchannel is initialized | |
98 | */ | |
99 | xprt->bc_serv = serv; | |
a43cde94 RL |
100 | } |
101 | #else | |
a43cde94 | 102 | static inline void nfs_callback_bc_serv(u32 minorversion, struct rpc_xprt *xprt, |
691c457a | 103 | struct svc_serv *serv) |
a43cde94 RL |
104 | { |
105 | } | |
106 | #endif /* CONFIG_NFS_V4_1 */ | |
107 | ||
8e246144 SK |
108 | static int nfs_callback_start_svc(int minorversion, struct rpc_xprt *xprt, |
109 | struct svc_serv *serv) | |
110 | { | |
5405fc44 | 111 | int nrservs = nfs_callback_nr_threads; |
8e246144 | 112 | int ret; |
8e246144 SK |
113 | |
114 | nfs_callback_bc_serv(minorversion, xprt, serv); | |
115 | ||
5405fc44 TM |
116 | if (nrservs < NFS4_MIN_NR_CALLBACK_THREADS) |
117 | nrservs = NFS4_MIN_NR_CALLBACK_THREADS; | |
118 | ||
ec52361d | 119 | if (serv->sv_nrthreads == nrservs) |
23c20ecd SK |
120 | return 0; |
121 | ||
3ebdbe52 | 122 | ret = svc_set_num_threads(serv, NULL, nrservs); |
bb6aeba7 | 123 | if (ret) { |
3ebdbe52 | 124 | svc_set_num_threads(serv, NULL, 0); |
e9b7e917 | 125 | return ret; |
8e246144 SK |
126 | } |
127 | dprintk("nfs_callback_up: service started\n"); | |
128 | return 0; | |
129 | } | |
130 | ||
b3d19c51 SK |
131 | static void nfs_callback_down_net(u32 minorversion, struct svc_serv *serv, struct net *net) |
132 | { | |
133 | struct nfs_net *nn = net_generic(net, nfs_net_id); | |
134 | ||
135 | if (--nn->cb_users[minorversion]) | |
136 | return; | |
137 | ||
e4949e4b | 138 | dprintk("NFS: destroy per-net callback data; net=%x\n", net->ns.inum); |
c7d7ec8f | 139 | svc_xprt_destroy_all(serv, net); |
b3d19c51 SK |
140 | } |
141 | ||
76566773 CL |
142 | static int nfs_callback_up_net(int minorversion, struct svc_serv *serv, |
143 | struct net *net, struct rpc_xprt *xprt) | |
c946556b | 144 | { |
b3d19c51 | 145 | struct nfs_net *nn = net_generic(net, nfs_net_id); |
c946556b SK |
146 | int ret; |
147 | ||
b3d19c51 SK |
148 | if (nn->cb_users[minorversion]++) |
149 | return 0; | |
150 | ||
e4949e4b | 151 | dprintk("NFS: create per-net callback data; net=%x\n", net->ns.inum); |
c946556b SK |
152 | |
153 | ret = svc_bind(serv, net); | |
154 | if (ret < 0) { | |
155 | printk(KERN_WARNING "NFS: bind callback service failed\n"); | |
156 | goto err_bind; | |
157 | } | |
158 | ||
a289ce53 | 159 | ret = 0; |
d55b352b | 160 | if (!IS_ENABLED(CONFIG_NFS_V4_1) || minorversion == 0) |
76566773 | 161 | ret = nfs4_callback_up_net(serv, net); |
a289ce53 | 162 | else if (xprt->ops->bc_setup) |
0ad30ff6 | 163 | set_bc_enabled(serv); |
a289ce53 VA |
164 | else |
165 | ret = -EPROTONOSUPPORT; | |
c946556b SK |
166 | |
167 | if (ret < 0) { | |
168 | printk(KERN_ERR "NFS: callback service start failed\n"); | |
169 | goto err_socks; | |
170 | } | |
171 | return 0; | |
172 | ||
173 | err_socks: | |
174 | svc_rpcb_cleanup(serv, net); | |
175 | err_bind: | |
98b0f80c | 176 | nn->cb_users[minorversion]--; |
23c20ecd | 177 | dprintk("NFS: Couldn't create callback socket: err = %d; " |
e4949e4b | 178 | "net = %x\n", ret, net->ns.inum); |
c946556b SK |
179 | return ret; |
180 | } | |
181 | ||
dd018428 SK |
182 | static struct svc_serv *nfs_callback_create_svc(int minorversion) |
183 | { | |
184 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; | |
37902c63 | 185 | int (*threadfn)(void *data); |
dd018428 SK |
186 | struct svc_serv *serv; |
187 | ||
188 | /* | |
189 | * Check whether we're already up and running. | |
190 | */ | |
df5e49c8 | 191 | if (cb_info->serv) |
1e3577a4 | 192 | return cb_info->serv; |
dd018428 SK |
193 | |
194 | /* | |
195 | * Sanity check: if there's no task, | |
196 | * we should be the first user ... | |
197 | */ | |
198 | if (cb_info->users) | |
199 | printk(KERN_WARNING "nfs_callback_create_svc: no kthread, %d users??\n", | |
200 | cb_info->users); | |
201 | ||
37902c63 | 202 | threadfn = nfs4_callback_svc; |
063ab935 | 203 | #if !defined(CONFIG_NFS_V4_1) |
37902c63 CL |
204 | if (minorversion) |
205 | return ERR_PTR(-ENOTSUPP); | |
206 | #endif | |
207 | serv = svc_create(&nfs4_callback_program, NFS4_CALLBACK_BUFSIZE, | |
208 | threadfn); | |
dd018428 SK |
209 | if (!serv) { |
210 | printk(KERN_ERR "nfs_callback_create_svc: create service failed\n"); | |
211 | return ERR_PTR(-ENOMEM); | |
212 | } | |
3b01c11e | 213 | cb_info->serv = serv; |
dd018428 SK |
214 | dprintk("nfs_callback_create_svc: service created\n"); |
215 | return serv; | |
216 | } | |
217 | ||
71468513 BH |
218 | /* |
219 | * Bring up the callback thread if it is not already up. | |
220 | */ | |
221 | int nfs_callback_up(u32 minorversion, struct rpc_xprt *xprt) | |
222 | { | |
dd018428 | 223 | struct svc_serv *serv; |
e82dc22d | 224 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; |
23c20ecd | 225 | int ret; |
c946556b | 226 | struct net *net = xprt->xprt_net; |
71468513 BH |
227 | |
228 | mutex_lock(&nfs_callback_mutex); | |
dd018428 SK |
229 | |
230 | serv = nfs_callback_create_svc(minorversion); | |
231 | if (IS_ERR(serv)) { | |
232 | ret = PTR_ERR(serv); | |
233 | goto err_create; | |
234 | } | |
235 | ||
76566773 | 236 | ret = nfs_callback_up_net(minorversion, serv, net, xprt); |
c946556b SK |
237 | if (ret < 0) |
238 | goto err_net; | |
9793f7c8 | 239 | |
8e246144 SK |
240 | ret = nfs_callback_start_svc(minorversion, xprt, serv); |
241 | if (ret < 0) | |
242 | goto err_start; | |
a277e33c | 243 | |
23c20ecd | 244 | cb_info->users++; |
23c20ecd | 245 | err_net: |
1e3577a4 N |
246 | if (!cb_info->users) { |
247 | svc_set_num_threads(cb_info->serv, NULL, 0); | |
248 | svc_destroy(&cb_info->serv); | |
249 | } | |
dd018428 | 250 | err_create: |
353ab6e9 | 251 | mutex_unlock(&nfs_callback_mutex); |
1da177e4 | 252 | return ret; |
8e246144 SK |
253 | |
254 | err_start: | |
b3d19c51 | 255 | nfs_callback_down_net(minorversion, serv, net); |
23c20ecd SK |
256 | dprintk("NFS: Couldn't create server thread; err = %d\n", ret); |
257 | goto err_net; | |
1da177e4 LT |
258 | } |
259 | ||
260 | /* | |
5afc597c | 261 | * Kill the callback thread if it's no longer being used. |
1da177e4 | 262 | */ |
c8ceb412 | 263 | void nfs_callback_down(int minorversion, struct net *net) |
1da177e4 | 264 | { |
e82dc22d | 265 | struct nfs_callback_data *cb_info = &nfs_callback_info[minorversion]; |
bb6aeba7 | 266 | struct svc_serv *serv; |
e82dc22d | 267 | |
353ab6e9 | 268 | mutex_lock(&nfs_callback_mutex); |
bb6aeba7 TM |
269 | serv = cb_info->serv; |
270 | nfs_callback_down_net(minorversion, serv, net); | |
e82dc22d | 271 | cb_info->users--; |
3b01c11e | 272 | if (cb_info->users == 0) { |
3ebdbe52 | 273 | svc_set_num_threads(serv, NULL, 0); |
1dc42e04 | 274 | dprintk("nfs_callback_down: service destroyed\n"); |
1e3577a4 | 275 | svc_destroy(&cb_info->serv); |
5afc597c | 276 | } |
353ab6e9 | 277 | mutex_unlock(&nfs_callback_mutex); |
1da177e4 LT |
278 | } |
279 | ||
778be232 AA |
280 | /* Boolean check of RPC_AUTH_GSS principal */ |
281 | int | |
282 | check_gss_callback_principal(struct nfs_client *clp, struct svc_rqst *rqstp) | |
945b34a7 | 283 | { |
03a4e1f6 | 284 | char *p = rqstp->rq_cred.cr_principal; |
945b34a7 | 285 | |
778be232 AA |
286 | if (rqstp->rq_authop->flavour != RPC_AUTH_GSS) |
287 | return 1; | |
288 | ||
ece0de63 AA |
289 | /* No RPC_AUTH_GSS on NFSv4.1 back channel yet */ |
290 | if (clp->cl_minorversion != 0) | |
778be232 | 291 | return 0; |
945b34a7 OK |
292 | /* |
293 | * It might just be a normal user principal, in which case | |
294 | * userspace won't bother to tell us the name at all. | |
295 | */ | |
296 | if (p == NULL) | |
778be232 | 297 | return 0; |
945b34a7 | 298 | |
f11b2a1c JL |
299 | /* |
300 | * Did we get the acceptor from userland during the SETCLIENID | |
301 | * negotiation? | |
302 | */ | |
303 | if (clp->cl_acceptor) | |
304 | return !strcmp(p, clp->cl_acceptor); | |
305 | ||
306 | /* | |
307 | * Otherwise try to verify it using the cl_hostname. Note that this | |
308 | * doesn't work if a non-canonical hostname was used in the devname. | |
309 | */ | |
310 | ||
945b34a7 OK |
311 | /* Expect a GSS_C_NT_HOSTBASED_NAME like "nfs@serverhostname" */ |
312 | ||
313 | if (memcmp(p, "nfs@", 4) != 0) | |
778be232 | 314 | return 0; |
945b34a7 | 315 | p += 4; |
4e0038b6 | 316 | if (strcmp(p, clp->cl_hostname) != 0) |
778be232 AA |
317 | return 0; |
318 | return 1; | |
945b34a7 OK |
319 | } |
320 | ||
778be232 AA |
321 | /* |
322 | * pg_authenticate method for nfsv4 callback threads. | |
323 | * | |
324 | * The authflavor has been negotiated, so an incorrect flavor is a server | |
6f02dc88 | 325 | * bug. Deny packets with incorrect authflavor. |
778be232 AA |
326 | * |
327 | * All other checking done after NFS decoding where the nfs_client can be | |
328 | * found in nfs4_callback_compound | |
329 | */ | |
78c542f9 | 330 | static enum svc_auth_status nfs_callback_authenticate(struct svc_rqst *rqstp) |
1da177e4 | 331 | { |
5c2465df CL |
332 | rqstp->rq_auth_stat = rpc_autherr_badcred; |
333 | ||
1da177e4 | 334 | switch (rqstp->rq_authop->flavour) { |
778be232 AA |
335 | case RPC_AUTH_NULL: |
336 | if (rqstp->rq_proc != CB_NULL) | |
6f02dc88 | 337 | return SVC_DENIED; |
778be232 AA |
338 | break; |
339 | case RPC_AUTH_GSS: | |
340 | /* No RPC_AUTH_GSS support yet in NFSv4.1 */ | |
341 | if (svc_is_backchannel(rqstp)) | |
6f02dc88 | 342 | return SVC_DENIED; |
1da177e4 | 343 | } |
5c2465df CL |
344 | |
345 | rqstp->rq_auth_stat = rpc_auth_ok; | |
778be232 | 346 | return SVC_OK; |
1da177e4 LT |
347 | } |
348 | ||
349 | /* | |
350 | * Define NFS4 callback program | |
351 | */ | |
e9679189 | 352 | static const struct svc_version *nfs4_callback_version[] = { |
1da177e4 | 353 | [1] = &nfs4_callback_version1, |
07bccc2d | 354 | [4] = &nfs4_callback_version4, |
1da177e4 LT |
355 | }; |
356 | ||
1da177e4 LT |
357 | static struct svc_program nfs4_callback_program = { |
358 | .pg_prog = NFS4_CALLBACK, /* RPC service number */ | |
359 | .pg_nvers = ARRAY_SIZE(nfs4_callback_version), /* Number of entries */ | |
360 | .pg_vers = nfs4_callback_version, /* version table */ | |
361 | .pg_name = "NFSv4 callback", /* service name */ | |
362 | .pg_class = "nfs", /* authentication class */ | |
1da177e4 | 363 | .pg_authenticate = nfs_callback_authenticate, |
8e5b6773 | 364 | .pg_init_request = svc_generic_init_request, |
642ee6b2 | 365 | .pg_rpcbind_set = svc_generic_rpcbind_set, |
1da177e4 | 366 | }; |