Commit 4d4b69dd authored by Weston Andros Adamson's avatar Weston Andros Adamson Committed by Trond Myklebust

NFS: add support for multiple sec= mount options

This patch adds support for multiple security options which can be
specified using a colon-delimited list of security flavors (the same
syntax as nfsd's exports file).

This is useful, for instance, when NFSv4.x mounts cross SECINFO
boundaries. With this patch a user can use "sec=krb5i,krb5p"
to mount a remote filesystem using krb5i, but can still cross
into krb5p-only exports.

New mounts will try all security options before failing.  NFSv4.x
SECINFO results will be compared against the sec= flavors to
find the first flavor in both lists or if no match is found will
return -EPERM.
Signed-off-by: default avatarWeston Andros Adamson <dros@netapp.com>
Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent 5837f6df
...@@ -326,6 +326,7 @@ extern struct file_system_type nfs_xdev_fs_type; ...@@ -326,6 +326,7 @@ extern struct file_system_type nfs_xdev_fs_type;
extern struct file_system_type nfs4_xdev_fs_type; extern struct file_system_type nfs4_xdev_fs_type;
extern struct file_system_type nfs4_referral_fs_type; extern struct file_system_type nfs4_referral_fs_type;
#endif #endif
bool nfs_auth_info_match(const struct nfs_auth_info *, rpc_authflavor_t);
struct dentry *nfs_try_mount(int, const char *, struct nfs_mount_info *, struct dentry *nfs_try_mount(int, const char *, struct nfs_mount_info *,
struct nfs_subversion *); struct nfs_subversion *);
void nfs_initialise_sb(struct super_block *); void nfs_initialise_sb(struct super_block *);
......
...@@ -964,6 +964,9 @@ static int nfs4_init_server(struct nfs_server *server, ...@@ -964,6 +964,9 @@ static int nfs4_init_server(struct nfs_server *server,
server->options = data->options; server->options = data->options;
server->auth_info = data->auth_info; server->auth_info = data->auth_info;
/* Use the first specified auth flavor. If this flavor isn't
* allowed by the server, use the SECINFO path to try the
* other specified flavors */
if (data->auth_info.flavor_len >= 1) if (data->auth_info.flavor_len >= 1)
data->selected_flavor = data->auth_info.flavors[0]; data->selected_flavor = data->auth_info.flavors[0];
else else
......
...@@ -137,6 +137,7 @@ static size_t nfs_parse_server_name(char *string, size_t len, ...@@ -137,6 +137,7 @@ static size_t nfs_parse_server_name(char *string, size_t len,
/** /**
* nfs_find_best_sec - Find a security mechanism supported locally * nfs_find_best_sec - Find a security mechanism supported locally
* @server: NFS server struct
* @flavors: List of security tuples returned by SECINFO procedure * @flavors: List of security tuples returned by SECINFO procedure
* *
* Return the pseudoflavor of the first security mechanism in * Return the pseudoflavor of the first security mechanism in
...@@ -145,7 +146,8 @@ static size_t nfs_parse_server_name(char *string, size_t len, ...@@ -145,7 +146,8 @@ static size_t nfs_parse_server_name(char *string, size_t len,
* is searched in the order returned from the server, per RFC 3530 * is searched in the order returned from the server, per RFC 3530
* recommendation. * recommendation.
*/ */
static rpc_authflavor_t nfs_find_best_sec(struct nfs4_secinfo_flavors *flavors) static rpc_authflavor_t nfs_find_best_sec(struct nfs_server *server,
struct nfs4_secinfo_flavors *flavors)
{ {
rpc_authflavor_t pseudoflavor; rpc_authflavor_t pseudoflavor;
struct nfs4_secinfo4 *secinfo; struct nfs4_secinfo4 *secinfo;
...@@ -160,12 +162,19 @@ static rpc_authflavor_t nfs_find_best_sec(struct nfs4_secinfo_flavors *flavors) ...@@ -160,12 +162,19 @@ static rpc_authflavor_t nfs_find_best_sec(struct nfs4_secinfo_flavors *flavors)
case RPC_AUTH_GSS: case RPC_AUTH_GSS:
pseudoflavor = rpcauth_get_pseudoflavor(secinfo->flavor, pseudoflavor = rpcauth_get_pseudoflavor(secinfo->flavor,
&secinfo->flavor_info); &secinfo->flavor_info);
if (pseudoflavor != RPC_AUTH_MAXFLAVOR) /* make sure pseudoflavor matches sec= mount opt */
if (pseudoflavor != RPC_AUTH_MAXFLAVOR &&
nfs_auth_info_match(&server->auth_info,
pseudoflavor))
return pseudoflavor; return pseudoflavor;
break; break;
} }
} }
/* if there were any sec= options then nothing matched */
if (server->auth_info.flavor_len > 0)
return -EPERM;
return RPC_AUTH_UNIX; return RPC_AUTH_UNIX;
} }
...@@ -187,7 +196,7 @@ static rpc_authflavor_t nfs4_negotiate_security(struct inode *inode, struct qstr ...@@ -187,7 +196,7 @@ static rpc_authflavor_t nfs4_negotiate_security(struct inode *inode, struct qstr
goto out; goto out;
} }
flavor = nfs_find_best_sec(flavors); flavor = nfs_find_best_sec(NFS_SERVER(inode), flavors);
out: out:
put_page(page); put_page(page);
...@@ -390,7 +399,7 @@ struct vfsmount *nfs4_submount(struct nfs_server *server, struct dentry *dentry, ...@@ -390,7 +399,7 @@ struct vfsmount *nfs4_submount(struct nfs_server *server, struct dentry *dentry,
if (client->cl_auth->au_flavor != flavor) if (client->cl_auth->au_flavor != flavor)
flavor = client->cl_auth->au_flavor; flavor = client->cl_auth->au_flavor;
else if (server->auth_info.flavor_len == 0) { else {
rpc_authflavor_t new = nfs4_negotiate_security(dir, name); rpc_authflavor_t new = nfs4_negotiate_security(dir, name);
if ((int)new >= 0) if ((int)new >= 0)
flavor = new; flavor = new;
......
...@@ -2873,11 +2873,24 @@ static int nfs4_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle, ...@@ -2873,11 +2873,24 @@ static int nfs4_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
int status = -EPERM; int status = -EPERM;
size_t i; size_t i;
for (i = 0; i < ARRAY_SIZE(flav_array); i++) { if (server->auth_info.flavor_len > 0) {
status = nfs4_lookup_root_sec(server, fhandle, info, flav_array[i]); /* try each flavor specified by user */
if (status == -NFS4ERR_WRONGSEC || status == -EACCES) for (i = 0; i < server->auth_info.flavor_len; i++) {
continue; status = nfs4_lookup_root_sec(server, fhandle, info,
break; server->auth_info.flavors[i]);
if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
continue;
break;
}
} else {
/* no flavors specified by user, try default list */
for (i = 0; i < ARRAY_SIZE(flav_array); i++) {
status = nfs4_lookup_root_sec(server, fhandle, info,
flav_array[i]);
if (status == -NFS4ERR_WRONGSEC || status == -EACCES)
continue;
break;
}
} }
/* /*
...@@ -2919,9 +2932,6 @@ int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle, ...@@ -2919,9 +2932,6 @@ int nfs4_proc_get_rootfh(struct nfs_server *server, struct nfs_fh *fhandle,
status = nfs4_lookup_root(server, fhandle, info); status = nfs4_lookup_root(server, fhandle, info);
if (status != -NFS4ERR_WRONGSEC) if (status != -NFS4ERR_WRONGSEC)
break; break;
/* Did user force a 'sec=' mount option? */
if (server->auth_info.flavor_len > 0)
break;
default: default:
status = nfs4_do_find_root_sec(server, fhandle, info); status = nfs4_do_find_root_sec(server, fhandle, info);
} }
...@@ -3179,9 +3189,6 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir, ...@@ -3179,9 +3189,6 @@ static int nfs4_proc_lookup_common(struct rpc_clnt **clnt, struct inode *dir,
err = -EPERM; err = -EPERM;
if (client != *clnt) if (client != *clnt)
goto out; goto out;
/* No security negotiation if the user specified 'sec=' */
if (NFS_SERVER(dir)->auth_info.flavor_len > 0)
goto out;
client = nfs4_create_sec_client(client, dir, name); client = nfs4_create_sec_client(client, dir, name);
if (IS_ERR(client)) if (IS_ERR(client))
return PTR_ERR(client); return PTR_ERR(client);
...@@ -7942,6 +7949,9 @@ nfs41_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle, ...@@ -7942,6 +7949,9 @@ nfs41_find_root_sec(struct nfs_server *server, struct nfs_fh *fhandle,
break; break;
} }
if (!nfs_auth_info_match(&server->auth_info, flavor))
flavor = RPC_AUTH_MAXFLAVOR;
if (flavor != RPC_AUTH_MAXFLAVOR) { if (flavor != RPC_AUTH_MAXFLAVOR) {
err = nfs4_lookup_root_sec(server, fhandle, err = nfs4_lookup_root_sec(server, fhandle,
info, flavor); info, flavor);
......
...@@ -497,7 +497,8 @@ static const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour) ...@@ -497,7 +497,8 @@ static const char *nfs_pseudoflavour_to_name(rpc_authflavor_t flavour)
static const struct { static const struct {
rpc_authflavor_t flavour; rpc_authflavor_t flavour;
const char *str; const char *str;
} sec_flavours[] = { } sec_flavours[NFS_AUTH_INFO_MAX_FLAVORS] = {
/* update NFS_AUTH_INFO_MAX_FLAVORS when this list changes! */
{ RPC_AUTH_NULL, "null" }, { RPC_AUTH_NULL, "null" },
{ RPC_AUTH_UNIX, "sys" }, { RPC_AUTH_UNIX, "sys" },
{ RPC_AUTH_GSS_KRB5, "krb5" }, { RPC_AUTH_GSS_KRB5, "krb5" },
...@@ -1018,6 +1019,52 @@ static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt) ...@@ -1018,6 +1019,52 @@ static void nfs_set_mount_transport_protocol(struct nfs_parsed_mount_data *mnt)
} }
} }
/*
* Add 'flavor' to 'auth_info' if not already present.
* Returns true if 'flavor' ends up in the list, false otherwise
*/
static bool nfs_auth_info_add(struct nfs_auth_info *auth_info,
rpc_authflavor_t flavor)
{
unsigned int i;
unsigned int max_flavor_len = (sizeof(auth_info->flavors) /
sizeof(auth_info->flavors[0]));
/* make sure this flavor isn't already in the list */
for (i = 0; i < auth_info->flavor_len; i++) {
if (flavor == auth_info->flavors[i])
return true;
}
if (auth_info->flavor_len + 1 >= max_flavor_len) {
dfprintk(MOUNT, "NFS: too many sec= flavors\n");
return false;
}
auth_info->flavors[auth_info->flavor_len++] = flavor;
return true;
}
/*
* Return true if 'match' is in auth_info or auth_info is empty.
* Return false otherwise.
*/
bool nfs_auth_info_match(const struct nfs_auth_info *auth_info,
rpc_authflavor_t match)
{
int i;
if (!auth_info->flavor_len)
return true;
for (i = 0; i < auth_info->flavor_len; i++) {
if (auth_info->flavors[i] == match)
return true;
}
return false;
}
EXPORT_SYMBOL_GPL(nfs_auth_info_match);
/* /*
* Parse the value of the 'sec=' option. * Parse the value of the 'sec=' option.
*/ */
...@@ -1026,49 +1073,55 @@ static int nfs_parse_security_flavors(char *value, ...@@ -1026,49 +1073,55 @@ static int nfs_parse_security_flavors(char *value,
{ {
substring_t args[MAX_OPT_ARGS]; substring_t args[MAX_OPT_ARGS];
rpc_authflavor_t pseudoflavor; rpc_authflavor_t pseudoflavor;
char *p;
dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value); dfprintk(MOUNT, "NFS: parsing sec=%s option\n", value);
switch (match_token(value, nfs_secflavor_tokens, args)) { while ((p = strsep(&value, ":")) != NULL) {
case Opt_sec_none: switch (match_token(p, nfs_secflavor_tokens, args)) {
pseudoflavor = RPC_AUTH_NULL; case Opt_sec_none:
break; pseudoflavor = RPC_AUTH_NULL;
case Opt_sec_sys: break;
pseudoflavor = RPC_AUTH_UNIX; case Opt_sec_sys:
break; pseudoflavor = RPC_AUTH_UNIX;
case Opt_sec_krb5: break;
pseudoflavor = RPC_AUTH_GSS_KRB5; case Opt_sec_krb5:
break; pseudoflavor = RPC_AUTH_GSS_KRB5;
case Opt_sec_krb5i: break;
pseudoflavor = RPC_AUTH_GSS_KRB5I; case Opt_sec_krb5i:
break; pseudoflavor = RPC_AUTH_GSS_KRB5I;
case Opt_sec_krb5p: break;
pseudoflavor = RPC_AUTH_GSS_KRB5P; case Opt_sec_krb5p:
break; pseudoflavor = RPC_AUTH_GSS_KRB5P;
case Opt_sec_lkey: break;
pseudoflavor = RPC_AUTH_GSS_LKEY; case Opt_sec_lkey:
break; pseudoflavor = RPC_AUTH_GSS_LKEY;
case Opt_sec_lkeyi: break;
pseudoflavor = RPC_AUTH_GSS_LKEYI; case Opt_sec_lkeyi:
break; pseudoflavor = RPC_AUTH_GSS_LKEYI;
case Opt_sec_lkeyp: break;
pseudoflavor = RPC_AUTH_GSS_LKEYP; case Opt_sec_lkeyp:
break; pseudoflavor = RPC_AUTH_GSS_LKEYP;
case Opt_sec_spkm: break;
pseudoflavor = RPC_AUTH_GSS_SPKM; case Opt_sec_spkm:
break; pseudoflavor = RPC_AUTH_GSS_SPKM;
case Opt_sec_spkmi: break;
pseudoflavor = RPC_AUTH_GSS_SPKMI; case Opt_sec_spkmi:
break; pseudoflavor = RPC_AUTH_GSS_SPKMI;
case Opt_sec_spkmp: break;
pseudoflavor = RPC_AUTH_GSS_SPKMP; case Opt_sec_spkmp:
break; pseudoflavor = RPC_AUTH_GSS_SPKMP;
default: break;
return 0; default:
dfprintk(MOUNT,
"NFS: sec= option '%s' not recognized\n", p);
return 0;
}
if (!nfs_auth_info_add(&mnt->auth_info, pseudoflavor))
return 0;
} }
mnt->auth_info.flavors[0] = pseudoflavor;
mnt->auth_info.flavor_len = 1;
return 1; return 1;
} }
...@@ -1615,12 +1668,14 @@ static int nfs_parse_mount_options(char *raw, ...@@ -1615,12 +1668,14 @@ static int nfs_parse_mount_options(char *raw,
} }
/* /*
* Ensure that the specified authtype in args->auth_info is supported by * Ensure that a specified authtype in args->auth_info is supported by
* the server. Returns 0 if it's ok, and -EACCES if not. * the server. Returns 0 and sets args->selected_flavor if it's ok, and
* -EACCES if not.
*/ */
static int nfs_verify_authflavor(struct nfs_parsed_mount_data *args, static int nfs_verify_authflavors(struct nfs_parsed_mount_data *args,
rpc_authflavor_t *server_authlist, unsigned int count) rpc_authflavor_t *server_authlist, unsigned int count)
{ {
rpc_authflavor_t flavor = RPC_AUTH_MAXFLAVOR;
unsigned int i; unsigned int i;
/* /*
...@@ -1632,17 +1687,19 @@ static int nfs_verify_authflavor(struct nfs_parsed_mount_data *args, ...@@ -1632,17 +1687,19 @@ static int nfs_verify_authflavor(struct nfs_parsed_mount_data *args,
* can be used. * can be used.
*/ */
for (i = 0; i < count; i++) { for (i = 0; i < count; i++) {
if (args->auth_info.flavors[0] == server_authlist[i] || flavor = server_authlist[i];
server_authlist[i] == RPC_AUTH_NULL)
if (nfs_auth_info_match(&args->auth_info, flavor) ||
flavor == RPC_AUTH_NULL)
goto out; goto out;
} }
dfprintk(MOUNT, "NFS: auth flavor %u not supported by server\n", dfprintk(MOUNT,
args->auth_info.flavors[0]); "NFS: specified auth flavors not supported by server\n");
return -EACCES; return -EACCES;
out: out:
args->selected_flavor = args->auth_info.flavors[0]; args->selected_flavor = flavor;
dfprintk(MOUNT, "NFS: using auth flavor %u\n", args->selected_flavor); dfprintk(MOUNT, "NFS: using auth flavor %u\n", args->selected_flavor);
return 0; return 0;
} }
...@@ -1732,7 +1789,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf ...@@ -1732,7 +1789,7 @@ static struct nfs_server *nfs_try_mount_request(struct nfs_mount_info *mount_inf
* whether the server supports it, and then just try to use it if so. * whether the server supports it, and then just try to use it if so.
*/ */
if (args->auth_info.flavor_len > 0) { if (args->auth_info.flavor_len > 0) {
status = nfs_verify_authflavor(args, authlist, authlist_len); status = nfs_verify_authflavors(args, authlist, authlist_len);
dfprintk(MOUNT, "NFS: using auth flavor %u\n", dfprintk(MOUNT, "NFS: using auth flavor %u\n",
args->selected_flavor); args->selected_flavor);
if (status) if (status)
...@@ -2102,9 +2159,6 @@ static int nfs_validate_text_mount_data(void *options, ...@@ -2102,9 +2159,6 @@ static int nfs_validate_text_mount_data(void *options,
nfs_set_port(sap, &args->nfs_server.port, port); nfs_set_port(sap, &args->nfs_server.port, port);
if (args->auth_info.flavor_len > 1)
goto out_bad_auth;
return nfs_parse_devname(dev_name, return nfs_parse_devname(dev_name,
&args->nfs_server.hostname, &args->nfs_server.hostname,
max_namelen, max_namelen,
...@@ -2124,10 +2178,6 @@ static int nfs_validate_text_mount_data(void *options, ...@@ -2124,10 +2178,6 @@ static int nfs_validate_text_mount_data(void *options,
out_no_address: out_no_address:
dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n"); dfprintk(MOUNT, "NFS: mount program didn't pass remote address\n");
return -EINVAL; return -EINVAL;
out_bad_auth:
dfprintk(MOUNT, "NFS: Too many RPC auth flavours specified\n");
return -EINVAL;
} }
static int static int
......
...@@ -592,9 +592,10 @@ struct nfs_renameres { ...@@ -592,9 +592,10 @@ struct nfs_renameres {
}; };
/* parsed sec= options */ /* parsed sec= options */
#define NFS_AUTH_INFO_MAX_FLAVORS 12 /* see fs/nfs/super.c */
struct nfs_auth_info { struct nfs_auth_info {
unsigned int flavor_len; unsigned int flavor_len;
rpc_authflavor_t flavors[1]; rpc_authflavor_t flavors[NFS_AUTH_INFO_MAX_FLAVORS];
}; };
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment