xref: /openbmc/linux/fs/nfs/nfs4namespace.c (revision cf0d7e7f)
1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2f7b422b1SDavid Howells /*
3f7b422b1SDavid Howells  * linux/fs/nfs/nfs4namespace.c
4f7b422b1SDavid Howells  *
5f7b422b1SDavid Howells  * Copyright (C) 2005 Trond Myklebust <Trond.Myklebust@netapp.com>
654ceac45SDavid Howells  * - Modified by David Howells <dhowells@redhat.com>
7f7b422b1SDavid Howells  *
8f7b422b1SDavid Howells  * NFSv4 namespace
9f7b422b1SDavid Howells  */
10f7b422b1SDavid Howells 
11f2aedb71SDavid Howells #include <linux/module.h>
12f7b422b1SDavid Howells #include <linux/dcache.h>
13f7b422b1SDavid Howells #include <linux/mount.h>
14f7b422b1SDavid Howells #include <linux/namei.h>
15f7b422b1SDavid Howells #include <linux/nfs_fs.h>
1647040da3STrond Myklebust #include <linux/nfs_mount.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
18f7b422b1SDavid Howells #include <linux/string.h>
19f7b422b1SDavid Howells #include <linux/sunrpc/clnt.h>
205976687aSJeff Layton #include <linux/sunrpc/addr.h>
21f7b422b1SDavid Howells #include <linux/vfs.h>
22f7b422b1SDavid Howells #include <linux/inet.h>
23f7b422b1SDavid Howells #include "internal.h"
24c228fd3aSTrond Myklebust #include "nfs4_fs.h"
25f2aedb71SDavid Howells #include "nfs.h"
267d7ea882STrond Myklebust #include "dns_resolve.h"
27f7b422b1SDavid Howells 
28f7b422b1SDavid Howells #define NFSDBG_FACILITY		NFSDBG_VFS
29f7b422b1SDavid Howells 
30f7b422b1SDavid Howells /*
31f2aedb71SDavid Howells  * Work out the length that an NFSv4 path would render to as a standard posix
32f2aedb71SDavid Howells  * path, with a leading slash but no terminating slash.
33f7b422b1SDavid Howells  */
nfs4_pathname_len(const struct nfs4_pathname * pathname)34f2aedb71SDavid Howells static ssize_t nfs4_pathname_len(const struct nfs4_pathname *pathname)
35f7b422b1SDavid Howells {
36f2aedb71SDavid Howells 	ssize_t len = 0;
37f2aedb71SDavid Howells 	int i;
38f7b422b1SDavid Howells 
39f2aedb71SDavid Howells 	for (i = 0; i < pathname->ncomponents; i++) {
40f2aedb71SDavid Howells 		const struct nfs4_string *component = &pathname->components[i];
41f7b422b1SDavid Howells 
42f2aedb71SDavid Howells 		if (component->len > NAME_MAX)
43f2aedb71SDavid Howells 			goto too_long;
44f2aedb71SDavid Howells 		len += 1 + component->len; /* Adding "/foo" */
45f2aedb71SDavid Howells 		if (len > PATH_MAX)
46f2aedb71SDavid Howells 			goto too_long;
47f7b422b1SDavid Howells 	}
48f2aedb71SDavid Howells 	return len;
49f2aedb71SDavid Howells 
50f2aedb71SDavid Howells too_long:
51f2aedb71SDavid Howells 	return -ENAMETOOLONG;
52f2aedb71SDavid Howells }
53f2aedb71SDavid Howells 
54f2aedb71SDavid Howells /*
55f2aedb71SDavid Howells  * Convert the NFSv4 pathname components into a standard posix path.
56f2aedb71SDavid Howells  */
nfs4_pathname_string(const struct nfs4_pathname * pathname,unsigned short * _len)57f2aedb71SDavid Howells static char *nfs4_pathname_string(const struct nfs4_pathname *pathname,
58f2aedb71SDavid Howells 				  unsigned short *_len)
59f2aedb71SDavid Howells {
60f2aedb71SDavid Howells 	ssize_t len;
61f2aedb71SDavid Howells 	char *buf, *p;
62f2aedb71SDavid Howells 	int i;
63f2aedb71SDavid Howells 
64f2aedb71SDavid Howells 	len = nfs4_pathname_len(pathname);
65f2aedb71SDavid Howells 	if (len < 0)
66f2aedb71SDavid Howells 		return ERR_PTR(len);
67f2aedb71SDavid Howells 	*_len = len;
68f2aedb71SDavid Howells 
69f2aedb71SDavid Howells 	p = buf = kmalloc(len + 1, GFP_KERNEL);
70f2aedb71SDavid Howells 	if (!buf)
71f2aedb71SDavid Howells 		return ERR_PTR(-ENOMEM);
72f2aedb71SDavid Howells 
73f2aedb71SDavid Howells 	for (i = 0; i < pathname->ncomponents; i++) {
74f2aedb71SDavid Howells 		const struct nfs4_string *component = &pathname->components[i];
75f2aedb71SDavid Howells 
76f2aedb71SDavid Howells 		*p++ = '/';
77f2aedb71SDavid Howells 		memcpy(p, component->data, component->len);
78f2aedb71SDavid Howells 		p += component->len;
79f2aedb71SDavid Howells 	}
80f2aedb71SDavid Howells 
81f2aedb71SDavid Howells 	*p = 0;
82f2aedb71SDavid Howells 	return buf;
83f7b422b1SDavid Howells }
84f7b422b1SDavid Howells 
8554ceac45SDavid Howells /*
861aba1567SWeston Andros Adamson  * return the path component of "<server>:<path>"
871aba1567SWeston Andros Adamson  *  nfspath - the "<server>:<path>" string
881aba1567SWeston Andros Adamson  *  end - one past the last char that could contain "<server>:"
891aba1567SWeston Andros Adamson  * returns NULL on failure
901aba1567SWeston Andros Adamson  */
nfs_path_component(const char * nfspath,const char * end)911aba1567SWeston Andros Adamson static char *nfs_path_component(const char *nfspath, const char *end)
921aba1567SWeston Andros Adamson {
931aba1567SWeston Andros Adamson 	char *p;
941aba1567SWeston Andros Adamson 
951aba1567SWeston Andros Adamson 	if (*nfspath == '[') {
961aba1567SWeston Andros Adamson 		/* parse [] escaped IPv6 addrs */
971aba1567SWeston Andros Adamson 		p = strchr(nfspath, ']');
981aba1567SWeston Andros Adamson 		if (p != NULL && ++p < end && *p == ':')
991aba1567SWeston Andros Adamson 			return p + 1;
1001aba1567SWeston Andros Adamson 	} else {
1011aba1567SWeston Andros Adamson 		/* otherwise split on first colon */
1021aba1567SWeston Andros Adamson 		p = strchr(nfspath, ':');
1031aba1567SWeston Andros Adamson 		if (p != NULL && p < end)
1041aba1567SWeston Andros Adamson 			return p + 1;
1051aba1567SWeston Andros Adamson 	}
1061aba1567SWeston Andros Adamson 	return NULL;
1071aba1567SWeston Andros Adamson }
1081aba1567SWeston Andros Adamson 
1091aba1567SWeston Andros Adamson /*
11054ceac45SDavid Howells  * Determine the mount path as a string
11154ceac45SDavid Howells  */
nfs4_path(struct dentry * dentry,char * buffer,ssize_t buflen)112b514f872SAl Viro static char *nfs4_path(struct dentry *dentry, char *buffer, ssize_t buflen)
11354ceac45SDavid Howells {
114b514f872SAl Viro 	char *limit;
11597a54868SBen Hutchings 	char *path = nfs_path(&limit, dentry, buffer, buflen,
11697a54868SBen Hutchings 			      NFS_PATH_CANONICAL);
117b514f872SAl Viro 	if (!IS_ERR(path)) {
1181aba1567SWeston Andros Adamson 		char *path_component = nfs_path_component(path, limit);
1191aba1567SWeston Andros Adamson 		if (path_component)
1201aba1567SWeston Andros Adamson 			return path_component;
121b514f872SAl Viro 	}
122b514f872SAl Viro 	return path;
12354ceac45SDavid Howells }
12454ceac45SDavid Howells 
12554ceac45SDavid Howells /*
12654ceac45SDavid Howells  * Check that fs_locations::fs_root [RFC3530 6.3] is a prefix for what we
12754ceac45SDavid Howells  * believe to be the server path to this dentry
12854ceac45SDavid Howells  */
nfs4_validate_fspath(struct dentry * dentry,const struct nfs4_fs_locations * locations,struct nfs_fs_context * ctx)129b514f872SAl Viro static int nfs4_validate_fspath(struct dentry *dentry,
13054ceac45SDavid Howells 				const struct nfs4_fs_locations *locations,
131f2aedb71SDavid Howells 				struct nfs_fs_context *ctx)
13254ceac45SDavid Howells {
13362a55d08SScott Mayhew 	const char *path;
13462a55d08SScott Mayhew 	char *fs_path;
135f2aedb71SDavid Howells 	unsigned short len;
13662a55d08SScott Mayhew 	char *buf;
137f2aedb71SDavid Howells 	int n;
13854ceac45SDavid Howells 
139f2aedb71SDavid Howells 	buf = kmalloc(4096, GFP_KERNEL);
140e0b27d98SColin Ian King 	if (!buf)
141e0b27d98SColin Ian King 		return -ENOMEM;
142e0b27d98SColin Ian King 
143f2aedb71SDavid Howells 	path = nfs4_path(dentry, buf, 4096);
144f2aedb71SDavid Howells 	if (IS_ERR(path)) {
145f2aedb71SDavid Howells 		kfree(buf);
14654ceac45SDavid Howells 		return PTR_ERR(path);
147f2aedb71SDavid Howells 	}
14854ceac45SDavid Howells 
149f2aedb71SDavid Howells 	fs_path = nfs4_pathname_string(&locations->fs_path, &len);
150f2aedb71SDavid Howells 	if (IS_ERR(fs_path)) {
151f2aedb71SDavid Howells 		kfree(buf);
15254ceac45SDavid Howells 		return PTR_ERR(fs_path);
153f2aedb71SDavid Howells 	}
15454ceac45SDavid Howells 
155f2aedb71SDavid Howells 	n = strncmp(path, fs_path, len);
156f2aedb71SDavid Howells 	kfree(buf);
157f2aedb71SDavid Howells 	kfree(fs_path);
158f2aedb71SDavid Howells 	if (n != 0) {
15954ceac45SDavid Howells 		dprintk("%s: path %s does not begin with fsroot %s\n",
160f2aedb71SDavid Howells 			__func__, path, ctx->nfs_server.export_path);
16154ceac45SDavid Howells 		return -ENOENT;
16254ceac45SDavid Howells 	}
16354ceac45SDavid Howells 
16454ceac45SDavid Howells 	return 0;
16554ceac45SDavid Howells }
16654ceac45SDavid Howells 
nfs_parse_server_name(char * string,size_t len,struct sockaddr_storage * ss,size_t salen,struct net * net,int port)167*cf0d7e7fSKees Cook size_t nfs_parse_server_name(char *string, size_t len, struct sockaddr_storage *ss,
168a8d54babSOlga Kornievskaia 			     size_t salen, struct net *net, int port)
1697d7ea882STrond Myklebust {
170*cf0d7e7fSKees Cook 	struct sockaddr *sa = (struct sockaddr *)ss;
1717d7ea882STrond Myklebust 	ssize_t ret;
1727d7ea882STrond Myklebust 
17333faaa38SStanislav Kinsbursky 	ret = rpc_pton(net, string, len, sa, salen);
1747d7ea882STrond Myklebust 	if (ret == 0) {
175a8d54babSOlga Kornievskaia 		ret = rpc_uaddr2sockaddr(net, string, len, sa, salen);
176a8d54babSOlga Kornievskaia 		if (ret == 0) {
177*cf0d7e7fSKees Cook 			ret = nfs_dns_resolve_name(net, string, len, ss, salen);
1787d7ea882STrond Myklebust 			if (ret < 0)
1797d7ea882STrond Myklebust 				ret = 0;
1807d7ea882STrond Myklebust 		}
181a8d54babSOlga Kornievskaia 	} else if (port) {
182a8d54babSOlga Kornievskaia 		rpc_set_port(sa, port);
183a8d54babSOlga Kornievskaia 	}
1847d7ea882STrond Myklebust 	return ret;
1857d7ea882STrond Myklebust }
1867d7ea882STrond Myklebust 
1879568c5e9SChuck Lever /**
1889568c5e9SChuck Lever  * nfs_find_best_sec - Find a security mechanism supported locally
189302fad7bSTrond Myklebust  * @clnt: pointer to rpc_clnt
1904d4b69ddSWeston Andros Adamson  * @server: NFS server struct
1919568c5e9SChuck Lever  * @flavors: List of security tuples returned by SECINFO procedure
1929568c5e9SChuck Lever  *
19366b06860SAndy Adamson  * Return an rpc client that uses the first security mechanism in
1948445cd35SAndy Adamson  * "flavors" that is locally supported.  The "flavors" array
1959568c5e9SChuck Lever  * is searched in the order returned from the server, per RFC 3530
19666b06860SAndy Adamson  * recommendation and each flavor is checked for membership in the
19766b06860SAndy Adamson  * sec= mount option list if it exists.
1988445cd35SAndy Adamson  *
1998445cd35SAndy Adamson  * Return -EPERM if no matching flavor is found in the array.
20066b06860SAndy Adamson  *
20166b06860SAndy Adamson  * Please call rpc_shutdown_client() when you are done with this rpc client.
20266b06860SAndy Adamson  *
2039568c5e9SChuck Lever  */
nfs_find_best_sec(struct rpc_clnt * clnt,struct nfs_server * server,struct nfs4_secinfo_flavors * flavors)20466b06860SAndy Adamson static struct rpc_clnt *nfs_find_best_sec(struct rpc_clnt *clnt,
20566b06860SAndy Adamson 					  struct nfs_server *server,
2064d4b69ddSWeston Andros Adamson 					  struct nfs4_secinfo_flavors *flavors)
2072671bfc3SBryan Schumaker {
20866b06860SAndy Adamson 	rpc_authflavor_t pflavor;
2099568c5e9SChuck Lever 	struct nfs4_secinfo4 *secinfo;
210fb15b26fSChuck Lever 	unsigned int i;
2112671bfc3SBryan Schumaker 
2122671bfc3SBryan Schumaker 	for (i = 0; i < flavors->num_flavors; i++) {
2139568c5e9SChuck Lever 		secinfo = &flavors->flavors[i];
2142671bfc3SBryan Schumaker 
2159568c5e9SChuck Lever 		switch (secinfo->flavor) {
2169568c5e9SChuck Lever 		case RPC_AUTH_NULL:
2179568c5e9SChuck Lever 		case RPC_AUTH_UNIX:
2189568c5e9SChuck Lever 		case RPC_AUTH_GSS:
21966b06860SAndy Adamson 			pflavor = rpcauth_get_pseudoflavor(secinfo->flavor,
2209568c5e9SChuck Lever 							&secinfo->flavor_info);
22166b06860SAndy Adamson 			/* does the pseudoflavor match a sec= mount opt? */
22266b06860SAndy Adamson 			if (pflavor != RPC_AUTH_MAXFLAVOR &&
22366b06860SAndy Adamson 			    nfs_auth_info_match(&server->auth_info, pflavor)) {
22466b06860SAndy Adamson 				struct rpc_clnt *new;
22566b06860SAndy Adamson 				struct rpc_cred *cred;
22666b06860SAndy Adamson 
22766b06860SAndy Adamson 				/* Cloning creates an rpc_auth for the flavor */
22866b06860SAndy Adamson 				new = rpc_clone_client_set_auth(clnt, pflavor);
22966b06860SAndy Adamson 				if (IS_ERR(new))
23066b06860SAndy Adamson 					continue;
23166b06860SAndy Adamson 				/**
23266b06860SAndy Adamson 				* Check that the user actually can use the
23366b06860SAndy Adamson 				* flavor. This is mostly for RPC_AUTH_GSS
23466b06860SAndy Adamson 				* where cr_init obtains a gss context
23566b06860SAndy Adamson 				*/
23666b06860SAndy Adamson 				cred = rpcauth_lookupcred(new->cl_auth, 0);
23766b06860SAndy Adamson 				if (IS_ERR(cred)) {
23866b06860SAndy Adamson 					rpc_shutdown_client(new);
23966b06860SAndy Adamson 					continue;
2409568c5e9SChuck Lever 				}
24166b06860SAndy Adamson 				put_rpccred(cred);
24266b06860SAndy Adamson 				return new;
24366b06860SAndy Adamson 			}
24466b06860SAndy Adamson 		}
24566b06860SAndy Adamson 	}
24666b06860SAndy Adamson 	return ERR_PTR(-EPERM);
2479568c5e9SChuck Lever }
2489568c5e9SChuck Lever 
24966b06860SAndy Adamson /**
25066b06860SAndy Adamson  * nfs4_negotiate_security - in response to an NFS4ERR_WRONGSEC on lookup,
25166b06860SAndy Adamson  * return an rpc_clnt that uses the best available security flavor with
25266b06860SAndy Adamson  * respect to the secinfo flavor list and the sec= mount options.
25366b06860SAndy Adamson  *
25466b06860SAndy Adamson  * @clnt: RPC client to clone
25566b06860SAndy Adamson  * @inode: directory inode
25666b06860SAndy Adamson  * @name: lookup name
25766b06860SAndy Adamson  *
25866b06860SAndy Adamson  * Please call rpc_shutdown_client() when you are done with this rpc client.
25966b06860SAndy Adamson  */
26066b06860SAndy Adamson struct rpc_clnt *
nfs4_negotiate_security(struct rpc_clnt * clnt,struct inode * inode,const struct qstr * name)26166b06860SAndy Adamson nfs4_negotiate_security(struct rpc_clnt *clnt, struct inode *inode,
262beffb8feSAl Viro 					const struct qstr *name)
26372de53ecSBryan Schumaker {
26472de53ecSBryan Schumaker 	struct page *page;
26572de53ecSBryan Schumaker 	struct nfs4_secinfo_flavors *flavors;
26666b06860SAndy Adamson 	struct rpc_clnt *new;
26772de53ecSBryan Schumaker 	int err;
26872de53ecSBryan Schumaker 
26972de53ecSBryan Schumaker 	page = alloc_page(GFP_KERNEL);
27072de53ecSBryan Schumaker 	if (!page)
27166b06860SAndy Adamson 		return ERR_PTR(-ENOMEM);
27266b06860SAndy Adamson 
27372de53ecSBryan Schumaker 	flavors = page_address(page);
27472de53ecSBryan Schumaker 
27572de53ecSBryan Schumaker 	err = nfs4_proc_secinfo(inode, name, flavors);
27672de53ecSBryan Schumaker 	if (err < 0) {
27766b06860SAndy Adamson 		new = ERR_PTR(err);
27872de53ecSBryan Schumaker 		goto out;
27972de53ecSBryan Schumaker 	}
28072de53ecSBryan Schumaker 
28166b06860SAndy Adamson 	new = nfs_find_best_sec(clnt, NFS_SERVER(inode), flavors);
28272de53ecSBryan Schumaker 
28372de53ecSBryan Schumaker out:
28472de53ecSBryan Schumaker 	put_page(page);
28566b06860SAndy Adamson 	return new;
28672de53ecSBryan Schumaker }
28772de53ecSBryan Schumaker 
try_location(struct fs_context * fc,const struct nfs4_fs_location * location)288f2aedb71SDavid Howells static int try_location(struct fs_context *fc,
2894ada29d5SJ. Bruce Fields 			const struct nfs4_fs_location *location)
2904ada29d5SJ. Bruce Fields {
291f2aedb71SDavid Howells 	struct nfs_fs_context *ctx = nfs_fc2context(fc);
292f2aedb71SDavid Howells 	unsigned int len, s;
293f2aedb71SDavid Howells 	char *export_path, *source, *p;
294f2aedb71SDavid Howells 	int ret = -ENOENT;
2954ada29d5SJ. Bruce Fields 
296f2aedb71SDavid Howells 	/* Allocate a buffer big enough to hold any of the hostnames plus a
297f2aedb71SDavid Howells 	 * terminating char and also a buffer big enough to hold the hostname
298f2aedb71SDavid Howells 	 * plus a colon plus the path.
299f2aedb71SDavid Howells 	 */
300f2aedb71SDavid Howells 	len = 0;
301460cdbc8SJ. Bruce Fields 	for (s = 0; s < location->nservers; s++) {
302ea31a443SJ. Bruce Fields 		const struct nfs4_string *buf = &location->servers[s];
303f2aedb71SDavid Howells 		if (buf->len > len)
304f2aedb71SDavid Howells 			len = buf->len;
305f2aedb71SDavid Howells 	}
3064ada29d5SJ. Bruce Fields 
307f2aedb71SDavid Howells 	kfree(ctx->nfs_server.hostname);
308f2aedb71SDavid Howells 	ctx->nfs_server.hostname = kmalloc(len + 1, GFP_KERNEL);
309f2aedb71SDavid Howells 	if (!ctx->nfs_server.hostname)
310f2aedb71SDavid Howells 		return -ENOMEM;
311f2aedb71SDavid Howells 
312f2aedb71SDavid Howells 	export_path = nfs4_pathname_string(&location->rootpath,
313f2aedb71SDavid Howells 					   &ctx->nfs_server.export_path_len);
314f2aedb71SDavid Howells 	if (IS_ERR(export_path))
315f2aedb71SDavid Howells 		return PTR_ERR(export_path);
316f2aedb71SDavid Howells 
3174659ed7cSTom Rix 	kfree(ctx->nfs_server.export_path);
318f2aedb71SDavid Howells 	ctx->nfs_server.export_path = export_path;
319f2aedb71SDavid Howells 
320f2aedb71SDavid Howells 	source = kmalloc(len + 1 + ctx->nfs_server.export_path_len + 1,
321f2aedb71SDavid Howells 			 GFP_KERNEL);
322f2aedb71SDavid Howells 	if (!source)
323f2aedb71SDavid Howells 		return -ENOMEM;
324f2aedb71SDavid Howells 
325f2aedb71SDavid Howells 	kfree(fc->source);
326f2aedb71SDavid Howells 	fc->source = source;
327f2aedb71SDavid Howells 	for (s = 0; s < location->nservers; s++) {
328f2aedb71SDavid Howells 		const struct nfs4_string *buf = &location->servers[s];
3294ada29d5SJ. Bruce Fields 
330ea31a443SJ. Bruce Fields 		if (memchr(buf->data, IPV6_SCOPE_DELIMITER, buf->len))
331ea31a443SJ. Bruce Fields 			continue;
332517be09dSTrond Myklebust 
33362a55d08SScott Mayhew 		ctx->nfs_server.addrlen =
334f2aedb71SDavid Howells 			nfs_parse_server_name(buf->data, buf->len,
335*cf0d7e7fSKees Cook 					      &ctx->nfs_server._address,
33662a55d08SScott Mayhew 					      sizeof(ctx->nfs_server._address),
337a8d54babSOlga Kornievskaia 					      fc->net_ns, 0);
33862a55d08SScott Mayhew 		if (ctx->nfs_server.addrlen == 0)
339ea31a443SJ. Bruce Fields 			continue;
340517be09dSTrond Myklebust 
34162a55d08SScott Mayhew 		rpc_set_port(&ctx->nfs_server.address, NFS_PORT);
3424ada29d5SJ. Bruce Fields 
343f2aedb71SDavid Howells 		memcpy(ctx->nfs_server.hostname, buf->data, buf->len);
344f2aedb71SDavid Howells 		ctx->nfs_server.hostname[buf->len] = '\0';
3454ada29d5SJ. Bruce Fields 
346f2aedb71SDavid Howells 		p = source;
347f2aedb71SDavid Howells 		memcpy(p, buf->data, buf->len);
348f2aedb71SDavid Howells 		p += buf->len;
349f2aedb71SDavid Howells 		*p++ = ':';
350f2aedb71SDavid Howells 		memcpy(p, ctx->nfs_server.export_path, ctx->nfs_server.export_path_len);
351f2aedb71SDavid Howells 		p += ctx->nfs_server.export_path_len;
352f2aedb71SDavid Howells 		*p = 0;
353f2aedb71SDavid Howells 
354f2aedb71SDavid Howells 		ret = nfs4_get_referral_tree(fc);
355f2aedb71SDavid Howells 		if (ret == 0)
356f2aedb71SDavid Howells 			return 0;
3574ada29d5SJ. Bruce Fields 	}
358f2aedb71SDavid Howells 
359f2aedb71SDavid Howells 	return ret;
3604ada29d5SJ. Bruce Fields }
3614ada29d5SJ. Bruce Fields 
362f7b422b1SDavid Howells /**
363f7b422b1SDavid Howells  * nfs_follow_referral - set up mountpoint when hitting a referral on moved error
3643cab1854STrond Myklebust  * @fc: pointer to struct nfs_fs_context
365302fad7bSTrond Myklebust  * @locations: array of NFSv4 server location information
366f7b422b1SDavid Howells  *
367f7b422b1SDavid Howells  */
nfs_follow_referral(struct fs_context * fc,const struct nfs4_fs_locations * locations)368f2aedb71SDavid Howells static int nfs_follow_referral(struct fs_context *fc,
369509de811SDavid Howells 			       const struct nfs4_fs_locations *locations)
370f7b422b1SDavid Howells {
371f2aedb71SDavid Howells 	struct nfs_fs_context *ctx = nfs_fc2context(fc);
3723f43c666SChuck Lever 	int loc, error;
373f7b422b1SDavid Howells 
374f7b422b1SDavid Howells 	if (locations == NULL || locations->nlocations <= 0)
375f2aedb71SDavid Howells 		return -ENOENT;
376f7b422b1SDavid Howells 
377f2aedb71SDavid Howells 	dprintk("%s: referral at %pd2\n", __func__, ctx->clone_data.dentry);
378f7b422b1SDavid Howells 
37954ceac45SDavid Howells 	/* Ensure fs path is a prefix of current dentry path */
380f2aedb71SDavid Howells 	error = nfs4_validate_fspath(ctx->clone_data.dentry, locations, ctx);
381f2aedb71SDavid Howells 	if (error < 0)
382f2aedb71SDavid Howells 		return error;
383f7b422b1SDavid Howells 
384f2aedb71SDavid Howells 	error = -ENOENT;
385460cdbc8SJ. Bruce Fields 	for (loc = 0; loc < locations->nlocations; loc++) {
386509de811SDavid Howells 		const struct nfs4_fs_location *location = &locations->locations[loc];
387f7b422b1SDavid Howells 
388f7b422b1SDavid Howells 		if (location == NULL || location->nservers <= 0 ||
389460cdbc8SJ. Bruce Fields 		    location->rootpath.ncomponents == 0)
390f7b422b1SDavid Howells 			continue;
391f7b422b1SDavid Howells 
392f2aedb71SDavid Howells 		error = try_location(fc, location);
393f2aedb71SDavid Howells 		if (error == 0)
394f2aedb71SDavid Howells 			return 0;
395f7b422b1SDavid Howells 	}
396f7b422b1SDavid Howells 
397f2aedb71SDavid Howells 	return error;
398f7b422b1SDavid Howells }
399f7b422b1SDavid Howells 
400f7b422b1SDavid Howells /*
401f7b422b1SDavid Howells  * nfs_do_refmount - handle crossing a referral on server
402f7b422b1SDavid Howells  * @dentry - dentry of referral
403f7b422b1SDavid Howells  *
404f7b422b1SDavid Howells  */
nfs_do_refmount(struct fs_context * fc,struct rpc_clnt * client)405f2aedb71SDavid Howells static int nfs_do_refmount(struct fs_context *fc, struct rpc_clnt *client)
406f7b422b1SDavid Howells {
407f2aedb71SDavid Howells 	struct nfs_fs_context *ctx = nfs_fc2context(fc);
408f2aedb71SDavid Howells 	struct dentry *dentry, *parent;
409f7b422b1SDavid Howells 	struct nfs4_fs_locations *fs_locations = NULL;
410f7b422b1SDavid Howells 	struct page *page;
411f2aedb71SDavid Howells 	int err = -ENOMEM;
412f7b422b1SDavid Howells 
413f7b422b1SDavid Howells 	/* BUG_ON(IS_ROOT(dentry)); */
414f7b422b1SDavid Howells 	page = alloc_page(GFP_KERNEL);
415f2aedb71SDavid Howells 	if (!page)
416f2aedb71SDavid Howells 		return -ENOMEM;
417f7b422b1SDavid Howells 
418f7b422b1SDavid Howells 	fs_locations = kmalloc(sizeof(struct nfs4_fs_locations), GFP_KERNEL);
419f2aedb71SDavid Howells 	if (!fs_locations)
420f7b422b1SDavid Howells 		goto out_free;
421c3ed2227SBenjamin Coddington 	fs_locations->fattr = nfs_alloc_fattr();
422c3ed2227SBenjamin Coddington 	if (!fs_locations->fattr)
423c3ed2227SBenjamin Coddington 		goto out_free_2;
424f7b422b1SDavid Howells 
425f7b422b1SDavid Howells 	/* Get locations */
426f2aedb71SDavid Howells 	dentry = ctx->clone_data.dentry;
427f7b422b1SDavid Howells 	parent = dget_parent(dentry);
4286de1472fSAl Viro 	dprintk("%s: getting locations for %pd2\n",
4296de1472fSAl Viro 		__func__, dentry);
43054ceac45SDavid Howells 
4312b0143b5SDavid Howells 	err = nfs4_proc_fs_locations(client, d_inode(parent), &dentry->d_name, fs_locations, page);
432f7b422b1SDavid Howells 	dput(parent);
433f2aedb71SDavid Howells 	if (err != 0)
434c3ed2227SBenjamin Coddington 		goto out_free_3;
435f7b422b1SDavid Howells 
436f2aedb71SDavid Howells 	err = -ENOENT;
437f2aedb71SDavid Howells 	if (fs_locations->nlocations <= 0 ||
438f2aedb71SDavid Howells 	    fs_locations->fs_path.ncomponents <= 0)
439c3ed2227SBenjamin Coddington 		goto out_free_3;
440f2aedb71SDavid Howells 
441f2aedb71SDavid Howells 	err = nfs_follow_referral(fc, fs_locations);
442c3ed2227SBenjamin Coddington out_free_3:
443c3ed2227SBenjamin Coddington 	kfree(fs_locations->fattr);
444f2aedb71SDavid Howells out_free_2:
445f2aedb71SDavid Howells 	kfree(fs_locations);
446f7b422b1SDavid Howells out_free:
447f7b422b1SDavid Howells 	__free_page(page);
448f2aedb71SDavid Howells 	return err;
449f7b422b1SDavid Howells }
450281cad46SBryan Schumaker 
nfs4_submount(struct fs_context * fc,struct nfs_server * server)451f2aedb71SDavid Howells int nfs4_submount(struct fs_context *fc, struct nfs_server *server)
452281cad46SBryan Schumaker {
453f2aedb71SDavid Howells 	struct nfs_fs_context *ctx = nfs_fc2context(fc);
454f2aedb71SDavid Howells 	struct dentry *dentry = ctx->clone_data.dentry;
455281cad46SBryan Schumaker 	struct dentry *parent = dget_parent(dentry);
4562b0143b5SDavid Howells 	struct inode *dir = d_inode(parent);
457281cad46SBryan Schumaker 	struct rpc_clnt *client;
458f2aedb71SDavid Howells 	int ret;
459281cad46SBryan Schumaker 
460281cad46SBryan Schumaker 	/* Look it up again to get its attributes and sec flavor */
461f7b37b8bSTrond Myklebust 	client = nfs4_proc_lookup_mountpoint(dir, dentry, ctx->mntfh,
462f2aedb71SDavid Howells 					     ctx->clone_data.fattr);
463281cad46SBryan Schumaker 	dput(parent);
464281cad46SBryan Schumaker 	if (IS_ERR(client))
465f2aedb71SDavid Howells 		return PTR_ERR(client);
466281cad46SBryan Schumaker 
467f2aedb71SDavid Howells 	ctx->selected_flavor = client->cl_auth->au_flavor;
468f2aedb71SDavid Howells 	if (ctx->clone_data.fattr->valid & NFS_ATTR_FATTR_V4_REFERRAL) {
469f2aedb71SDavid Howells 		ret = nfs_do_refmount(fc, client);
470f2aedb71SDavid Howells 	} else {
471f2aedb71SDavid Howells 		ret = nfs_do_submount(fc);
47247040da3STrond Myklebust 	}
473281cad46SBryan Schumaker 
474281cad46SBryan Schumaker 	rpc_shutdown_client(client);
475f2aedb71SDavid Howells 	return ret;
476281cad46SBryan Schumaker }
477800c06a5SChuck Lever 
478800c06a5SChuck Lever /*
479800c06a5SChuck Lever  * Try one location from the fs_locations array.
480800c06a5SChuck Lever  *
481800c06a5SChuck Lever  * Returns zero on success, or a negative errno value.
482800c06a5SChuck Lever  */
nfs4_try_replacing_one_location(struct nfs_server * server,char * page,char * page2,const struct nfs4_fs_location * location)483800c06a5SChuck Lever static int nfs4_try_replacing_one_location(struct nfs_server *server,
484800c06a5SChuck Lever 		char *page, char *page2,
485800c06a5SChuck Lever 		const struct nfs4_fs_location *location)
486800c06a5SChuck Lever {
487292f503cSTrond Myklebust 	struct net *net = rpc_net_ns(server->client);
488*cf0d7e7fSKees Cook 	struct sockaddr_storage *sap;
489800c06a5SChuck Lever 	unsigned int s;
490800c06a5SChuck Lever 	size_t salen;
491800c06a5SChuck Lever 	int error;
492800c06a5SChuck Lever 
493*cf0d7e7fSKees Cook 	sap = kmalloc(sizeof(*sap), GFP_KERNEL);
494800c06a5SChuck Lever 	if (sap == NULL)
495800c06a5SChuck Lever 		return -ENOMEM;
496800c06a5SChuck Lever 
497800c06a5SChuck Lever 	error = -ENOENT;
498800c06a5SChuck Lever 	for (s = 0; s < location->nservers; s++) {
499800c06a5SChuck Lever 		const struct nfs4_string *buf = &location->servers[s];
500800c06a5SChuck Lever 		char *hostname;
501800c06a5SChuck Lever 
502800c06a5SChuck Lever 		if (buf->len <= 0 || buf->len > PAGE_SIZE)
503800c06a5SChuck Lever 			continue;
504800c06a5SChuck Lever 
505800c06a5SChuck Lever 		if (memchr(buf->data, IPV6_SCOPE_DELIMITER, buf->len) != NULL)
506800c06a5SChuck Lever 			continue;
507800c06a5SChuck Lever 
508800c06a5SChuck Lever 		salen = nfs_parse_server_name(buf->data, buf->len,
509*cf0d7e7fSKees Cook 					      sap, sizeof(*sap), net, 0);
510800c06a5SChuck Lever 		if (salen == 0)
511800c06a5SChuck Lever 			continue;
512*cf0d7e7fSKees Cook 		rpc_set_port((struct sockaddr *)sap, NFS_PORT);
513800c06a5SChuck Lever 
514800c06a5SChuck Lever 		error = -ENOMEM;
515a8bd9ddfSTrond Myklebust 		hostname = kmemdup_nul(buf->data, buf->len, GFP_KERNEL);
516800c06a5SChuck Lever 		if (hostname == NULL)
517800c06a5SChuck Lever 			break;
518800c06a5SChuck Lever 
519292f503cSTrond Myklebust 		error = nfs4_update_server(server, hostname, sap, salen, net);
520800c06a5SChuck Lever 		kfree(hostname);
521800c06a5SChuck Lever 		if (error == 0)
522800c06a5SChuck Lever 			break;
523800c06a5SChuck Lever 	}
524800c06a5SChuck Lever 
525800c06a5SChuck Lever 	kfree(sap);
526800c06a5SChuck Lever 	return error;
527800c06a5SChuck Lever }
528800c06a5SChuck Lever 
529800c06a5SChuck Lever /**
530800c06a5SChuck Lever  * nfs4_replace_transport - set up transport to destination server
531800c06a5SChuck Lever  *
532800c06a5SChuck Lever  * @server: export being migrated
533800c06a5SChuck Lever  * @locations: fs_locations array
534800c06a5SChuck Lever  *
535800c06a5SChuck Lever  * Returns zero on success, or a negative errno value.
536800c06a5SChuck Lever  *
537800c06a5SChuck Lever  * The client tries all the entries in the "locations" array, in the
538800c06a5SChuck Lever  * order returned by the server, until one works or the end of the
539800c06a5SChuck Lever  * array is reached.
540800c06a5SChuck Lever  */
nfs4_replace_transport(struct nfs_server * server,const struct nfs4_fs_locations * locations)541800c06a5SChuck Lever int nfs4_replace_transport(struct nfs_server *server,
542800c06a5SChuck Lever 			   const struct nfs4_fs_locations *locations)
543800c06a5SChuck Lever {
544800c06a5SChuck Lever 	char *page = NULL, *page2 = NULL;
545800c06a5SChuck Lever 	int loc, error;
546800c06a5SChuck Lever 
547800c06a5SChuck Lever 	error = -ENOENT;
548800c06a5SChuck Lever 	if (locations == NULL || locations->nlocations <= 0)
549800c06a5SChuck Lever 		goto out;
550800c06a5SChuck Lever 
551800c06a5SChuck Lever 	error = -ENOMEM;
552800c06a5SChuck Lever 	page = (char *) __get_free_page(GFP_USER);
553800c06a5SChuck Lever 	if (!page)
554800c06a5SChuck Lever 		goto out;
555800c06a5SChuck Lever 	page2 = (char *) __get_free_page(GFP_USER);
556800c06a5SChuck Lever 	if (!page2)
557800c06a5SChuck Lever 		goto out;
558800c06a5SChuck Lever 
559800c06a5SChuck Lever 	for (loc = 0; loc < locations->nlocations; loc++) {
560800c06a5SChuck Lever 		const struct nfs4_fs_location *location =
561800c06a5SChuck Lever 						&locations->locations[loc];
562800c06a5SChuck Lever 
563800c06a5SChuck Lever 		if (location == NULL || location->nservers <= 0 ||
564800c06a5SChuck Lever 		    location->rootpath.ncomponents == 0)
565800c06a5SChuck Lever 			continue;
566800c06a5SChuck Lever 
567800c06a5SChuck Lever 		error = nfs4_try_replacing_one_location(server, page,
568800c06a5SChuck Lever 							page2, location);
569800c06a5SChuck Lever 		if (error == 0)
570800c06a5SChuck Lever 			break;
571800c06a5SChuck Lever 	}
572800c06a5SChuck Lever 
573800c06a5SChuck Lever out:
574800c06a5SChuck Lever 	free_page((unsigned long)page);
575800c06a5SChuck Lever 	free_page((unsigned long)page2);
576800c06a5SChuck Lever 	return error;
577800c06a5SChuck Lever }
578