diff options
author | Niels de Vos <ndevos@redhat.com> | 2015-01-01 13:15:45 +0100 |
---|---|---|
committer | Vijay Bellur <vbellur@redhat.com> | 2015-03-15 07:01:38 -0700 |
commit | aa66b8404f45712c45d75d6a2a37f32e2792cc83 (patch) | |
tree | 11d7a95bd6286204ec0ec33e4cdd8ba0c5b48028 | |
parent | aac1ec0a61d9267b6ae7a280b368dfd357b7dcdc (diff) |
gNFS: Export / Netgroup authentication on Gluster NFS mount
* Parses linux style export file/netgroups file into a structure that
can be lookedup.
* This parser turns each line into a structure called an "export
directory". Each of these has a dictionary of hosts and netgroups
which can be looked up during the mount authentication process.
(See Change-Id Ic060aac and I7e6aa6bc)
* A string beginning withan '@' is treated as a netgroup and a string
beginning without an @ is a host.
(See Change-Id Ie04800d)
* This parser does not currently support all the options in the man page
('man exports'), but we can easily add them.
BUG: 1143880
URL: http://www.gluster.org/community/documentation/index.php/Features/Exports_Netgroups_Authentication
Change-Id: I181e8c1814d6ef3cae5b4d88353622734f0c0f0b
Original-author: Shreyas Siravara <shreyas.siravara@gmail.com>
CC: Richard Wareing <rwareing@fb.com>
CC: Jiffin Tony Thottan <jthottan@redhat.com>
Signed-off-by: Niels de Vos <ndevos@redhat.com>
Reviewed-on: http://review.gluster.org/8758
Tested-by: Gluster Build System <jenkins@build.gluster.com>
Reviewed-by: Vijay Bellur <vbellur@redhat.com>
-rw-r--r-- | libglusterfs/src/common-utils.c | 112 | ||||
-rw-r--r-- | libglusterfs/src/common-utils.h | 3 | ||||
-rwxr-xr-x | tests/basic/mount-nfs-auth.t | 148 | ||||
-rw-r--r-- | tests/bugs/nfs/bug-1143880-fix-gNFSd-auth-crash.t | 20 | ||||
-rw-r--r-- | xlators/nfs/server/src/mount3.c | 852 | ||||
-rw-r--r-- | xlators/nfs/server/src/mount3.h | 56 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs.c | 9 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs.h | 9 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs3-fh.c | 13 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs3-fh.h | 5 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs3-helpers.c | 49 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs3-helpers.h | 3 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs3.c | 73 | ||||
-rw-r--r-- | xlators/nfs/server/src/nfs3.h | 2 |
14 files changed, 1301 insertions, 53 deletions
diff --git a/libglusterfs/src/common-utils.c b/libglusterfs/src/common-utils.c index 6dcfc098dc2..751dc8a2e50 100644 --- a/libglusterfs/src/common-utils.c +++ b/libglusterfs/src/common-utils.c @@ -34,6 +34,7 @@ #include <arpa/inet.h> #include <signal.h> #include <assert.h> +#include <libgen.h> /* for dirname() */ #if defined(GF_BSD_HOST_OS) || defined(GF_DARWIN_HOST_OS) #include <sys/sysctl.h> @@ -171,6 +172,86 @@ log_base2 (unsigned long x) return val; } +/** + * gf_rev_dns_lookup -- Perform a reverse DNS lookup on the IP address. + * + * @ip: The IP address to perform a reverse lookup on + * + * @return: success: Allocated string containing the hostname + * failure: NULL + */ +char * +gf_rev_dns_lookup (const char *ip) +{ + char *fqdn = NULL; + int ret = 0; + struct sockaddr_in sa = {0}; + char host_addr[256] = {0, }; + + GF_VALIDATE_OR_GOTO ("resolver", ip, out); + + sa.sin_family = AF_INET; + inet_pton (AF_INET, ip, &sa.sin_addr); + ret = getnameinfo ((struct sockaddr *)&sa, sizeof (sa), host_addr, + sizeof (host_addr), NULL, 0, 0); + + if (ret != 0) { + gf_log ("resolver", GF_LOG_INFO, "could not resolve hostname " + "for %s: %s", ip, strerror (errno)); + goto out; + } + + /* Get the FQDN */ + fqdn = gf_strdup (host_addr); + if (!fqdn) + gf_log ("resolver", GF_LOG_CRITICAL, "Allocation failed for " + "the host address"); + +out: + return fqdn; +} + +/** + * gf_resolve_parent_path -- Given a path, returns an allocated string + * containing the parent's path. + * @path: Path to parse + * @return: The parent path if found, NULL otherwise + */ +char * +gf_resolve_path_parent (const char *path) +{ + char *parent = NULL; + char *tmp = NULL; + char *pathc = NULL; + + GF_VALIDATE_OR_GOTO (THIS->name, path, out); + + if (strlen (path) <= 0) { + gf_log_callingfn (THIS->name, GF_LOG_DEBUG, + "invalid string for 'path'"); + goto out; + } + + /* dup the parameter, we don't want to modify it */ + pathc = strdupa (path); + if (!pathc) { + gf_log (THIS->name, GF_LOG_CRITICAL, + "Allocation failed for the parent"); + goto out; + } + + /* Get the parent directory */ + tmp = dirname (pathc); + if (strcmp (tmp, "/") == 0) + goto out; + + parent = gf_strdup (tmp); + if (!parent) + gf_log (THIS->name, GF_LOG_CRITICAL, + "Allocation failed for the parent"); +out: + return parent; +} int32_t gf_resolve_ip6 (const char *hostname, @@ -1689,6 +1770,37 @@ out: } /** + * get_file_mtime -- Given a path, get the mtime for the file + * + * @path: The filepath to check the mtime on + * @stamp: The parameter to set after we get the mtime + * + * @returns: success: 0 + * errors : Errors returned by the stat () call + */ +int +get_file_mtime (const char *path, time_t *stamp) +{ + struct stat f_stat = {0}; + int ret = -EINVAL; + + GF_VALIDATE_OR_GOTO (THIS->name, path, out); + GF_VALIDATE_OR_GOTO (THIS->name, stamp, out); + + ret = stat (path, &f_stat); + if (ret < 0) { + gf_log (THIS->name, GF_LOG_ERROR, "failed to stat %s: %s", + path, strerror (errno)); + goto out; + } + + /* Set the mtime */ + *stamp = f_stat.st_mtime; +out: + return ret; +} + +/** * gf_is_ip_in_net -- Checks if an IP Address is in a network. * A network should be specified by something like * '10.5.153.0/24' (in CIDR notation). diff --git a/libglusterfs/src/common-utils.h b/libglusterfs/src/common-utils.h index 64544126836..f76059b3082 100644 --- a/libglusterfs/src/common-utils.h +++ b/libglusterfs/src/common-utils.h @@ -609,6 +609,8 @@ int get_checksum_for_file (int fd, uint32_t *checksum); int log_base2 (unsigned long x); int get_checksum_for_path (char *path, uint32_t *checksum); +int get_file_mtime (const char *path, time_t *stamp); +char *gf_resolve_path_parent (const char *path); char *strtail (char *str, const char *pattern); void skipwhite (char **s); @@ -627,6 +629,7 @@ gf_boolean_t valid_mount_auth_address (char *address); gf_boolean_t valid_ipv4_subnetwork (const char *address); gf_boolean_t gf_sock_union_equal_addr (union gf_sock_union *a, union gf_sock_union *b); +char *gf_rev_dns_lookup (const char *ip); char *uuid_utoa (uuid_t uuid); char *uuid_utoa_r (uuid_t uuid, char *dst); diff --git a/tests/basic/mount-nfs-auth.t b/tests/basic/mount-nfs-auth.t new file mode 100755 index 00000000000..55fe9327d0e --- /dev/null +++ b/tests/basic/mount-nfs-auth.t @@ -0,0 +1,148 @@ +#!/bin/bash + +. $(dirname $0)/../include.rc +. $(dirname $0)/../nfs.rc + +cleanup; + +## Start and create a volume +TEST glusterd +TEST pidof glusterd +TEST $CLI volume info + +# Export variables for allow & deny +EXPORT_ALLOW="/$V0 $H0(sec=sys,rw,anonuid=0) @ngtop(sec=sys,rw,anonuid=0)" +EXPORT_ALLOW_SLASH="/$V0/ $H0(sec=sys,rw,anonuid=0) @ngtop(sec=sys,rw,anonuid=0)" +EXPORT_DENY="/$V0 1.2.3.4(sec=sys,rw,anonuid=0) @ngtop(sec=sys,rw,anonuid=0)" + +# Netgroup variables for allow & deny +NETGROUP_ALLOW="ngtop ng1000\nng1000 ng999\nng999 ng1\nng1 ng2\nng2 ($H0,,)" +NETGROUP_DENY="ngtop ng1000\nng1000 ng999\nng999 ng1\nng1 ng2\nng2 (1.2.3.4,,)" + +V0L1="$V0/L1" +V0L2="$V0L1/L2" +V0L3="$V0L2/L3" + +# Other variations for allow & deny +EXPORT_ALLOW_RO="/$V0 $H0(sec=sys,ro,anonuid=0) @ngtop(sec=sys,ro,anonuid=0)" +EXPORT_ALLOW_L1="/$V0L1 $H0(sec=sys,rw,anonuid=0) @ngtop(sec=sys,rw,anonuid=0)" +EXPORT_WILDCARD="/$V0 *(sec=sys,rw,anonuid=0) @ngtop(sec=sys,rw,anonuid=0)" + +function build_dirs () { + mkdir -p $B0/b{0,1,2}/L1/L2/L3 +} + +function export_allow_this_host () { + printf "$EXPORT_ALLOW\n" > /var/lib/glusterd/nfs/exports +} + +function export_allow_this_host_with_slash () { + printf "$EXPORT_ALLOW_SLASH\n" > /var/lib/glusterd/nfs/exports +} + +function export_deny_this_host () { + printf "$EXPORT_DENY\n" > /var/lib/glusterd/nfs/exports +} + +function export_allow_this_host_l1 () { + printf "$EXPORT_ALLOW_L1\n" >> /var/lib/glusterd/nfs/exports +} + +function export_allow_wildcard () { + printf "$EXPORT_WILDCARD\n" >> /var/lib/glusterd/nfs/exports +} + +function export_allow_this_host_ro () { + printf "$EXPORT_ALLOW_RO\n" > /var/lib/glusterd/nfs/exports +} + +function netgroup_allow_this_host () { + printf "$NETGROUP_ALLOW\n" > /var/lib/glusterd/nfs/netgroups +} + +function netgroup_deny_this_host () { + printf "$NETGROUP_DENY\n" > /var/lib/glusterd/nfs/netgroups +} + +function create_vol () { + TEST $CLI vol create $V0 replica 3 $H0:$B0/b0 $H0:$B0/b1 $H0:$B0/b2 +} + +function setup_cluster() { + build_dirs # Build directories + export_allow_this_host # Allow this host in the exports file + netgroup_allow_this_host # Allow this host in the netgroups file + + glusterd + create_vol # Create the volume +} + +function do_mount () { + mount_nfs $H0:/$1 $N0 nolock +} + +function small_write () { + dd if=/dev/zero of=$N0/test-small-write count=1 bs=1k 2>&1 +} + +function bg_write () { + dd if=/dev/zero of=$N0/test-bg-write count=1 bs=1k & + BG_WRITE_PID=$! +} + +function big_write() { + dd if=/dev/zero of=$N0/test-big-write count=500 bs=1M +} + +function create () { + touch $N0/create-test +} + +function stat_nfs () { + ls $N0/ +} + +setup_cluster + +# run preliminary tests +TEST $CLI vol set $V0 cluster.self-heal-daemon off +TEST $CLI vol set $V0 nfs.disable off +TEST $CLI vol set $V0 cluster.choose-local off +TEST $CLI vol start $V0 + +## Wait for volume to register with rpc.mountd +EXPECT_WITHIN $NFS_EXPORT_TIMEOUT "1" is_nfs_export_available + +## NFS server starts with auth disabled +## Do some tests to verify that. + +TEST do_mount $V0 +TEST umount $N0 + +## Disallow host +TEST export_deny_this_host +TEST netgroup_deny_this_host +sleep 2 + +## Technically deauthorized this host, but since auth is disabled we should be +## able to do mounts, writes, etc. +TEST do_mount $V0 +TEST small_write +TEST umount $N0 +TEST do_mount $V0 +TEST umount $N0 + +## Reauthorize this host +export_allow_this_host +netgroup_allow_this_host + +# +# Most functional tests will get added with http://review.gluster.org/9364 +# + +## Finish up +TEST $CLI volume stop $V0 +TEST $CLI volume delete $V0; +TEST ! $CLI volume info $V0; + +cleanup diff --git a/tests/bugs/nfs/bug-1143880-fix-gNFSd-auth-crash.t b/tests/bugs/nfs/bug-1143880-fix-gNFSd-auth-crash.t new file mode 100644 index 00000000000..1a9270286fb --- /dev/null +++ b/tests/bugs/nfs/bug-1143880-fix-gNFSd-auth-crash.t @@ -0,0 +1,20 @@ +#!/bin/bash + +. $(dirname $0)/../../include.rc +. $(dirname $0)/../../nfs.rc +cleanup; + +TEST glusterd +TEST pidof glusterd +TEST $CLI volume create $V0 replica 2 $H0:$B0/${V0}{1,2} +TEST $CLI volume set $V0 performance.open-behind off +TEST $CLI volume start $V0 + +EXPECT_WITHIN $NFS_EXPORT_TIMEOUT 1 is_nfs_export_available + +TEST mount_nfs $H0:/$V0 $N0 nolock +TEST mkdir -p $N0/foo +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 +TEST mount_nfs $H0:/$V0/foo $N0 nolock +EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" umount_nfs $N0 +cleanup diff --git a/xlators/nfs/server/src/mount3.c b/xlators/nfs/server/src/mount3.c index c7b90a5fb2c..16778ea0963 100644 --- a/xlators/nfs/server/src/mount3.c +++ b/xlators/nfs/server/src/mount3.c @@ -33,6 +33,8 @@ #include "store.h" #include "glfs-internal.h" #include "glfs.h" +#include "mount3-auth.h" +#include "hashfn.h" #include <errno.h> #include <sys/socket.h> @@ -55,6 +57,10 @@ exp->hostspec = NULL; \ } while (0) +/* Paths for export and netgroup files */ +const char *exports_file_path = GLUSTERD_DEFAULT_WORKDIR "/nfs/exports"; +const char *netgroups_file_path = GLUSTERD_DEFAULT_WORKDIR "/nfs/netgroups"; + typedef ssize_t (*mnt3_serializer) (struct iovec outmsg, void *args); extern void * @@ -69,6 +75,7 @@ mnt3_export_free (struct mnt3_export *exp) if (exp->exptype == MNT3_EXPTYPE_DIR) FREE_HOSTSPEC (exp); GF_FREE (exp->expname); + GF_FREE (exp->fullpath); GF_FREE (exp); } @@ -141,7 +148,64 @@ ret: return ret; } +/** + * __mountdict_insert -- Insert a mount entry into the mount state + * + * @ms: The mount state holding the entries + * @me: The mount entry to insert + * + * Not for external use. + */ +void +__mountdict_insert (struct mount3_state *ms, struct mountentry *me) +{ + char *exname = NULL; + char *fpath = NULL; + data_t *medata = NULL; + + GF_VALIDATE_OR_GOTO (GF_MNT, ms, out); + GF_VALIDATE_OR_GOTO (GF_MNT, me, out); + + /* We don't want export names with leading slashes */ + exname = me->exname; + while (exname[0] == '/') + exname++; + + /* Get the fullpath for the export */ + fpath = me->fullpath; + if (me->has_full_path) { + while (fpath[0] == '/') + fpath++; + + /* Export names can either be just volumes or paths inside that + * volume. */ + exname = fpath; + } + + snprintf (me->hashkey, sizeof (me->hashkey), "%s:%s", exname, + me->hostname); + + medata = bin_to_data (me, sizeof (*me)); + dict_set (ms->mountdict, me->hashkey, medata); + gf_log (GF_MNT, GF_LOG_TRACE, "Inserted into mountdict: %s", + me->hashkey); +out: + return; +} +/** + * __mountdict_remove -- Remove a mount entry from the mountstate. + * + * @ms: The mount state holding the entries + * @me: The mount entry to remove + * + * Not for external use. + */ +inline void +__mountdict_remove (struct mount3_state *ms, struct mountentry *me) +{ + dict_del (ms->mountdict, me->hashkey); +} /* Generic error reply function, just pass the err status * and it will do the rest, including transmission. @@ -487,7 +551,7 @@ free_sh: */ int mnt3svc_update_mountlist (struct mount3_state *ms, rpcsvc_request_t *req, - char *expname) + const char *expname, const char *fullpath) { struct mountentry *me = NULL; struct mountentry *cur = NULL; @@ -514,6 +578,16 @@ mnt3svc_update_mountlist (struct mount3_state *ms, rpcsvc_request_t *req, } strncpy (me->exname, expname, MNTPATHLEN); + /* Sometimes we don't care about the full path + * so a NULL value for fullpath is valid. + */ + if (fullpath) { + if (strlen (fullpath) < MNTPATHLEN) { + strcpy (me->fullpath, fullpath); + me->has_full_path = _gf_true; + } + } + INIT_LIST_HEAD (&me->mlist); /* Must get the IP or hostname of the client so we @@ -546,6 +620,7 @@ mnt3svc_update_mountlist (struct mount3_state *ms, rpcsvc_request_t *req, } } list_add_tail (&me->mlist, &ms->mountlist); + __mountdict_insert (ms, me); /* only write the rmtab in case it was locked */ if (gf_store_locked_local (sh)) @@ -592,6 +667,58 @@ out: return ret; } +int +__mnt3_build_mountid_from_path (const char *path, uuid_t mountid) +{ + uint32_t hashed_path = 0; + int ret = -1; + size_t length; + + length = sizeof(mountid); + while (strlen (path) > 0 && path[0] == '/') + path++; + + /* Clear the mountid */ + memset (mountid, 0, length); + + hashed_path = SuperFastHash (path, strlen (path)); + if (hashed_path == 1) { + gf_log (GF_MNT, GF_LOG_WARNING, "failed to hash path: %s", + path); + goto out; + } + + memcpy (mountid, &hashed_path, sizeof (hashed_path)); + ret = 0; +out: + return ret; +} + +int +__mnt3_get_mount_id (xlator_t *mntxl, uuid_t mountid) +{ + int ret = -1; + uint32_t hashed_path = 0; + size_t length; + + length = sizeof(mountid); + + /* first clear the mountid */ + memset (mountid, 0, length); + + hashed_path = SuperFastHash (mntxl->name, strlen (mntxl->name)); + if (hashed_path == 1) { + gf_log (GF_MNT, GF_LOG_WARNING, "failed to hash xlator name: %s", + mntxl->name); + goto out; + } + + memcpy (mountid, &hashed_path, sizeof (hashed_path)); + ret = 0; +out: + return ret; +} + int32_t mnt3svc_lookup_mount_cbk (call_frame_t *frame, void *cookie, @@ -609,7 +736,9 @@ mnt3svc_lookup_mount_cbk (call_frame_t *frame, void *cookie, rpcsvc_t *svc = NULL; xlator_t *mntxl = NULL; uuid_t volumeid = {0, }; - char fhstr[1024], *path = NULL; + char *path = NULL; + uuid_t mountid = {1, }; + char fhstr[1536]; req = (rpcsvc_request_t *)frame->local; @@ -638,15 +767,16 @@ mnt3svc_lookup_mount_cbk (call_frame_t *frame, void *cookie, } snprintf (path, PATH_MAX, "/%s", mntxl->name); - mnt3svc_update_mountlist (ms, req, path); + mnt3svc_update_mountlist (ms, req, path, NULL); GF_FREE (path); if (gf_nfs_dvm_off (nfs_state (ms->nfsx))) { fh = nfs3_fh_build_indexed_root_fh (ms->nfsx->children, mntxl); goto xmit_res; } + __mnt3_get_mount_id (mntxl, mountid); __mnt3_get_volume_id (ms, mntxl, volumeid); - fh = nfs3_fh_build_uuid_root_fh (volumeid); + fh = nfs3_fh_build_uuid_root_fh (volumeid, mountid); xmit_res: nfs3_fh_to_str (&fh, fhstr, sizeof (fhstr)); @@ -667,27 +797,49 @@ xmit_res: int -mnt3_match_dirpath_export (char *expname, char *dirpath) +mnt3_match_dirpath_export (const char *expname, const char *dirpath, + gf_boolean_t export_parsing_match) { int ret = 0; size_t dlen; + char *fullpath = NULL; + char *second_slash = NULL; + char *dirdup = NULL; if ((!expname) || (!dirpath)) return 0; + dirdup = strdupa (dirpath); + /* Some clients send a dirpath for mount that includes the slash at the * end. String compare for searching the export will fail because our * exports list does not include that slash. Remove the slash to * compare. */ - dlen = strlen (dirpath); - if (dlen && dirpath [dlen - 1] == '/') - dirpath [dlen - 1] = '\0'; + dlen = strlen (dirdup); + if (dlen && dirdup[dlen - 1] == '/') + dirdup[dlen - 1] = '\0'; + + /* Here we try to match fullpaths with export names */ + fullpath = dirdup; - if (dirpath[0] != '/') + if (export_parsing_match) { + if (dirdup[0] == '/') + fullpath = dirdup + 1; + + second_slash = strchr (fullpath, '/'); + if (second_slash) + *second_slash = '\0'; + } + + /* The export name begins with a slash so move it forward by one + * to ignore the slash when we want to compare the fullpath and + * export. + */ + if (fullpath[0] != '/') expname++; - if (strcmp (expname, dirpath) == 0) + if (strcmp (expname, fullpath) == 0) ret = 1; return ret; @@ -919,8 +1071,13 @@ mnt3_resolve_subdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, mountres3 res = {0, }; xlator_t *mntxl = NULL; char *path = NULL; + struct mount3_state *ms = NULL; + int authcode = 0; + char *authorized_host = NULL; + char *authorized_path = NULL; mres = frame->local; + ms = mres->mstate; mntxl = (xlator_t *)cookie; if (op_ret == -1) { gf_log (GF_NFS, GF_LOG_ERROR, "path=%s (%s)", @@ -934,18 +1091,52 @@ mnt3_resolve_subdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, nfs3_fh_build_child_fh (&mres->parentfh, buf, &fh); if (strlen (mres->remainingdir) <= 0) { + size_t alloclen; op_ret = -1; mntstat = MNT3_OK; + + /* Construct the full path */ + alloclen = strlen (mres->exp->expname) + + strlen (mres->resolveloc.path) + 1; + mres->exp->fullpath = GF_CALLOC (alloclen, sizeof (char), + gf_nfs_mt_char); + if (!mres->exp->fullpath) { + gf_log (GF_MNT, GF_LOG_CRITICAL, "Allocation failed."); + goto err; + } + snprintf (mres->exp->fullpath, alloclen, "%s%s", + mres->exp->expname, mres->resolveloc.path); + + /* Check if this path is authorized to be mounted */ + authcode = mnt3_authenticate_request (ms, mres->req, NULL, NULL, + mres->exp->fullpath, + &authorized_path, + &authorized_host, + FALSE); + if (authcode != 0) { + mntstat = MNT3ERR_ACCES; + gf_log (GF_MNT, GF_LOG_DEBUG, + "Client mount not allowed"); + op_ret = -1; + goto err; + } + path = GF_CALLOC (PATH_MAX, sizeof (char), gf_nfs_mt_char); if (!path) { gf_log (GF_MNT, GF_LOG_ERROR, "Memory allocation " "failed"); goto err; } + /* Build mountid from the authorized path and stick it in the + * filehandle that will get passed back to the client + */ + __mnt3_build_mountid_from_path (authorized_path, fh.mountid); + snprintf (path, PATH_MAX, "/%s%s", mres->exp->vol->name, mres->resolveloc.path); + mnt3svc_update_mountlist (mres->mstate, mres->req, - path); + path, mres->exp->fullpath); GF_FREE (path); } else { mres->parentfh = fh; @@ -967,6 +1158,9 @@ err: mnt3_resolve_state_wipe (mres); } + GF_FREE (authorized_path); + GF_FREE (authorized_host); + return 0; } @@ -1300,7 +1494,7 @@ mnt3_resolve_subdir (rpcsvc_request_t *req, struct mount3_state *ms, mres->mstate->nfsx->children, mres->exp->vol); else - pfh = nfs3_fh_build_uuid_root_fh (exp->volumeid); + pfh = nfs3_fh_build_uuid_root_fh (exp->volumeid, exp->mountid); mres->parentfh = pfh; ret = __mnt3_resolve_subdir (mres); @@ -1361,9 +1555,15 @@ mnt3svc_mount (rpcsvc_request_t *req, struct mount3_state *ms, /* mnt3_mntpath_to_xlator sets this to 1 if the mount is for a full * volume or 2 for a subdir in the volume. +* +* The parameter 'export_parsing_match' indicates whether this function +* is being called by an exports parser or whether it is being called +* during mount. The behavior is different since we don't have to resolve +* the path when doing the parse. */ struct mnt3_export * -mnt3_mntpath_to_export (struct mount3_state *ms, char *dirpath) +mnt3_mntpath_to_export (struct mount3_state *ms, const char *dirpath, + gf_boolean_t export_parsing_match) { struct mnt3_export *exp = NULL; struct mnt3_export *found = NULL; @@ -1375,7 +1575,8 @@ mnt3_mntpath_to_export (struct mount3_state *ms, char *dirpath) list_for_each_entry (exp, &ms->exportlist, explist) { /* Search for the an exact match with the volume */ - if (mnt3_match_dirpath_export (exp->expname, dirpath)) { + if (mnt3_match_dirpath_export (exp->expname, dirpath, + export_parsing_match)) { found = exp; gf_log (GF_MNT, GF_LOG_DEBUG, "Found export volume: " "%s", exp->vol->name); @@ -1507,7 +1708,7 @@ mnt3_parse_dir_exports (rpcsvc_request_t *req, struct mount3_state *ms, if (!subdir) goto err; - exp = mnt3_mntpath_to_export (ms, volname); + exp = mnt3_mntpath_to_export (ms, volname, _gf_false); if (!exp) goto err; @@ -1558,7 +1759,7 @@ mnt3_find_export (rpcsvc_request_t *req, char *path, struct mnt3_export **e) } gf_log (GF_MNT, GF_LOG_DEBUG, "dirpath: %s", path); - exp = mnt3_mntpath_to_export (ms, path); + exp = mnt3_mntpath_to_export (ms, path, _gf_false); if (exp) { ret = 0; *e = exp; @@ -1576,6 +1777,282 @@ err: return ret; } +/** + * _mnt3_get_peer_addr -- Take an rpc request object and return an allocated + * peer address. A peer address is host:port. + * + * @req: An rpc svc request object to extract the peer address from + * + * @return: success: Pointer to an allocated string containing the peer address + * failure: NULL + */ +char * +_mnt3_get_peer_addr (const rpcsvc_request_t *req) +{ + rpc_transport_t *trans = NULL; + struct sockaddr_storage sastorage = {0, }; + char peer[RPCSVC_PEER_STRLEN] = {0, }; + char *peerdup = NULL; + int ret = 0; + + GF_VALIDATE_OR_GOTO (GF_NFS, req, out); + + trans = rpcsvc_request_transport (req); + ret = rpcsvc_transport_peeraddr (trans, peer, RPCSVC_PEER_STRLEN, + &sastorage, sizeof (sastorage)); + if (ret != 0) + goto out; + + peerdup = gf_strdup (peer); +out: + return peerdup; +} + +/** + * _mnt3_get_host_from_peer -- Take a peer address and get an allocated + * hostname. The hostname is the string on the + * left side of the colon. + * + * @peer_addr: The peer address to get a hostname from + * + * @return: success: Allocated string containing the hostname + * failure: NULL + * + */ +char * +_mnt3_get_host_from_peer (const char *peer_addr) +{ + char *part = NULL; + size_t host_len = 0; + char *colon = NULL; + + colon = strchr (peer_addr, ':'); + if (!colon) { + gf_log (GF_MNT, GF_LOG_ERROR, "Bad peer %s", peer_addr); + goto out; + } + + host_len = colon - peer_addr; + if (host_len < RPCSVC_PEER_STRLEN) + part = gf_strndup (peer_addr, host_len); + else + gf_log (GF_MNT, GF_LOG_ERROR, "Peer too long %s", peer_addr); +out: + return part; +} + +/** + * mnt3_check_cached_fh -- Check if FH is cached. + * + * Calls auxiliary functions based on whether we are checking + * a write operation. + * + */ +inline int +mnt3_check_cached_fh (struct mount3_state *ms, struct nfs3_fh *fh, + const char *host_addr, gf_boolean_t is_write_op) +{ + if (!is_write_op) + return is_nfs_fh_cached (ms->authcache, fh, host_addr); + + return is_nfs_fh_cached_and_writeable (ms->authcache, fh, host_addr); +} + +/** + * _mnt3_authenticate_req -- Given an RPC request and a path OR a filehandle + * check if the host is authorized to make the + * request. Uses exports/netgroups auth model to + * do this check. + * + * @ms : The mount state + * @req : The RPC request + * @fh : The NFS FH to authenticate (set when authenticating an FOP) + * @path: The path to authenticate (set when authenticating a mount req) + * @authorized_export: Allocate and fill this value when an export is authorized + * @authorized_host: Allocate and fill this value when a host is authorized + * @is_write_op: Is this a write op that we are authenticating? + * + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) + */ +int +_mnt3_authenticate_req (struct mount3_state *ms, rpcsvc_request_t *req, + struct nfs3_fh *fh, const char *path, + char **authorized_export, char **authorized_host, + gf_boolean_t is_write_op) +{ + char *peer_addr = NULL; + char *host_addr_ip = NULL; + char *host_addr_fqdn = NULL; + int auth_status_code = -EACCES; + char *pathdup = NULL; + size_t dlen = 0; + char *auth_host = NULL; + gf_boolean_t fh_cached = _gf_false; + struct export_item *expitem = NULL; + + GF_VALIDATE_OR_GOTO (GF_MNT, ms, out); + GF_VALIDATE_OR_GOTO (GF_MNT, req, out); + + peer_addr = _mnt3_get_peer_addr (req); + host_addr_ip = _mnt3_get_host_from_peer (peer_addr); + + if (!host_addr_ip || !peer_addr) + goto free_and_out; + + if (path) { + /* Need to strip out trailing '/' */ + pathdup = strdupa (path); + dlen = strlen (pathdup); + if (dlen > 0 && pathdup[dlen-1] == '/') + pathdup[dlen-1] = '\0'; + } + + /* Check if the filehandle is cached */ + fh_cached = mnt3_check_cached_fh (ms, fh, host_addr_ip, is_write_op); + if (fh_cached) { + gf_log (GF_MNT, GF_LOG_TRACE, "Found cached FH for %s", + host_addr_ip); + auth_status_code = 0; + goto free_and_out; + } + + /* Check if the IP is authorized */ + auth_status_code = mnt3_auth_host (ms->auth_params, host_addr_ip, + fh, pathdup, is_write_op, &expitem); + if (auth_status_code != 0) { + /* If not, check if the FQDN is authorized */ + host_addr_fqdn = gf_rev_dns_lookup (host_addr_ip); + auth_status_code = mnt3_auth_host (ms->auth_params, + host_addr_fqdn, + fh, pathdup, is_write_op, + &expitem); + if (auth_status_code == 0) + auth_host = host_addr_fqdn; + } else + auth_host = host_addr_ip; + + /* Skip the lines that set authorized export & + * host if they are null. + */ + if (!authorized_export || !authorized_host) { + /* Cache the file handle if it was authorized */ + if (fh && auth_status_code == 0) + cache_nfs_fh (ms->authcache, fh, host_addr_ip, expitem); + + goto free_and_out; + } + + if (!fh && auth_status_code == 0) { + *authorized_export = gf_strdup (pathdup); + if (!*authorized_export) + gf_log (GF_MNT, GF_LOG_CRITICAL, + "Allocation error when copying " + "authorized path"); + + *authorized_host = gf_strdup (auth_host); + if (!*authorized_host) + gf_log (GF_MNT, GF_LOG_CRITICAL, + "Allocation error when copying " + "authorized host"); + } + +free_and_out: + /* Free allocated strings after doing the auth */ + GF_FREE (peer_addr); + GF_FREE (host_addr_fqdn); + GF_FREE (host_addr_ip); +out: + return auth_status_code; +} + +/** + * mnt3_authenticate_request -- Given an RPC request and a path, check if the + * host is authorized to make the request. This + * function calls _mnt3_authenticate_req_path () + * in a loop for the parent of each path while + * the authentication check for that path is + * failing. + * + * E.g. If the requested path is /patchy/L1, and /patchy is authorized, but + * /patchy/L1 is not, it follows this code path : + * + * _mnt3_authenticate_req ("/patchy/L1") -> F + * _mnt3_authenticate_req ("/patchy"); -> T + * return T; + * + * @ms : The mount state + * @req : The RPC request + * @path: The requested path + * @authorized_path: This gets allocated and populated with the authorized path + * @authorized_host: This gets allocated and populated with the authorized host + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) + */ +int +mnt3_authenticate_request (struct mount3_state *ms, rpcsvc_request_t *req, + struct nfs3_fh *fh, const char *volname, + const char *path, char **authorized_path, + char **authorized_host, gf_boolean_t is_write_op) +{ + int auth_status_code = -EACCES; + char *parent_path = NULL; + const char *parent_old = NULL; + + GF_VALIDATE_OR_GOTO (GF_MNT, ms, out); + GF_VALIDATE_OR_GOTO (GF_MNT, req, out); + + /* If this option is not set, just allow it through */ + if (!ms->nfs->exports_auth) { + /* This function is called in a variety of use-cases (mount + * + each fop) so path/authorized_path are not always present. + * For the cases which it _is_ present we need to populate the + * authorized_path. */ + if (path && authorized_path) + *authorized_path = gf_strdup (path); + + auth_status_code = 0; + goto out; + } + + /* First check if the path is allowed */ + auth_status_code = _mnt3_authenticate_req (ms, req, fh, path, + authorized_path, + authorized_host, + is_write_op); + + /* If the filehandle is set, just exit since we have to make only + * one call to the function above + */ + if (fh) + goto out; + + parent_old = path; + while (auth_status_code != 0) { + /* Get the path's parent */ + parent_path = gf_resolve_path_parent (parent_old); + if (!parent_path) /* Nothing left in the path to resolve */ + goto out; + + /* Authenticate it */ + auth_status_code = _mnt3_authenticate_req (ms, req, fh, + parent_path, + authorized_path, + authorized_host, + is_write_op); + + parent_old = strdupa (parent_path); /* Copy the parent onto the + * stack. + */ + + GF_FREE (parent_path); /* Free the allocated parent string */ + } + +out: + return auth_status_code; +} int mnt3svc_mnt (rpcsvc_request_t *req) @@ -1587,6 +2064,7 @@ mnt3svc_mnt (rpcsvc_request_t *req) mountstat3 mntstat = MNT3ERR_SERVERFAULT; struct mnt3_export *exp = NULL; struct nfs_state *nfs = NULL; + int authcode = 0; if (!req) return -1; @@ -1646,7 +2124,20 @@ mnt3svc_mnt (rpcsvc_request_t *req) goto mnterr; } + /* The second authentication check is the exports/netgroups + * check. + */ + authcode = mnt3_authenticate_request (ms, req, NULL, NULL, path, NULL, + NULL, _gf_false); + if (authcode != 0) { + mntstat = MNT3ERR_ACCES; + gf_log (GF_MNT, GF_LOG_DEBUG, "Client mount not allowed"); + ret = -1; + goto mnterr; + } + ret = mnt3svc_mount (req, ms, exp); + if (ret < 0) mntstat = mnt3svc_errno_to_mnterr (-ret); mnterr: @@ -1952,10 +2443,13 @@ __mnt3svc_umountall (struct mount3_state *ms) return 0; list_for_each_entry_safe (me, tmp, &ms->mountlist, mlist) { - list_del (&me->mlist); + list_del (&me->mlist); /* Remove from the mount list */ + __mountdict_remove (ms, me); /* Remove from the mount dict */ GF_FREE (me); } + dict_unref (ms->mountdict); + return 0; } @@ -2284,7 +2778,7 @@ nfs3_rootfh (struct svc_req *req, xlator_t *nfsx, return NULL; } - exp = mnt3_mntpath_to_export (ms, path); + exp = mnt3_mntpath_to_export (ms, path , _gf_false); if (exp != NULL) mnt3type = exp->exptype; @@ -2300,7 +2794,7 @@ nfs3_rootfh (struct svc_req *req, xlator_t *nfsx, path = __volume_subdir (path, &volptr); if (exp == NULL) - exp = mnt3_mntpath_to_export (ms, volname); + exp = mnt3_mntpath_to_export (ms, volname , _gf_false); } if (exp == NULL) { @@ -2685,11 +3179,7 @@ __mnt3_init_volume_direxports (struct mount3_state *ms, xlator_t *xlator, if ((!ms) || (!xlator) || (!optstr)) return -1; - dupopt = gf_strdup (optstr); - if (!dupopt) { - gf_log (GF_MNT, GF_LOG_ERROR, "gf_strdup failed"); - goto err; - } + dupopt = strdupa (optstr); token = strtok_r (dupopt, ",", &savptr); while (token) { @@ -2707,8 +3197,6 @@ __mnt3_init_volume_direxports (struct mount3_state *ms, xlator_t *xlator, ret = 0; err: - GF_FREE (dupopt); - return ret; } @@ -2914,12 +3402,12 @@ mnt3_init_options (struct mount3_state *ms, dict_t *options) volentry = volentry->next; } + ret = 0; err: return ret; } - struct mount3_state * mnt3_init_state (xlator_t *nfsx) { @@ -2999,7 +3487,286 @@ rpcsvc_program_t mnt3prog = { .synctask = _gf_true, }; +/** + * __mnt3_mounted_exports_walk -- Walk through the mounted export directories + * and unmount the directories that are no + * longer authorized to be mounted. + * @dict: The dict to walk + * @key : The key we are on + * @val : The value associated with that key + * @tmp : Additional params (pointer to an auth params struct passed here) + * + */ +int +__mnt3_mounted_exports_walk (dict_t *dict, char *key, data_t *val, void *tmp) +{ + char *path = NULL; + char *host_addr_ip = NULL; + char *keydup = NULL; + char *colon = NULL; + struct mnt3_auth_params *auth_params = NULL; + int auth_status_code = 0; + + gf_log (GF_MNT, GF_LOG_TRACE, "Checking if key %s is authorized.", key); + + auth_params = (struct mnt3_auth_params *)tmp; + + /* Since we haven't obtained a lock around the mount dict + * here, we want to duplicate the key and then process it. + * Otherwise we would potentially have a race condition + * by modifying the key in the dict when other threads + * are accessing it. + */ + keydup = strdupa (key); + + colon = strchr (keydup, ':'); + if (!colon) + return 0; + + *colon = '\0'; + path = alloca (strlen (keydup) + 2); + snprintf (path, strlen (keydup) + 2, "/%s", keydup); + + /* Host is one character after ':' */ + host_addr_ip = colon + 1; + auth_status_code = mnt3_auth_host (auth_params, host_addr_ip, NULL, + path, _gf_false, NULL); + if (auth_status_code != 0) { + gf_log (GF_MNT, GF_LOG_ERROR, + "%s is no longer authorized for %s", + host_addr_ip, path); + mnt3svc_umount (auth_params->ms, path, host_addr_ip); + } + return 0; +} + +/** + * _mnt3_invalidate_old_mounts -- Calls __mnt3_mounted_exports_walk which checks + * checks if hosts are authorized to be mounted + * and umounts them. + * + * @ms: The mountstate for this service that holds all the information we need + * + */ +void +_mnt3_invalidate_old_mounts (struct mount3_state *ms) +{ + gf_log (GF_MNT, GF_LOG_DEBUG, "Invalidating old mounts ..."); + dict_foreach (ms->mountdict, __mnt3_mounted_exports_walk, + ms->auth_params); +} + + +/** + * _mnt3_has_file_changed -- Checks if a file has changed on disk + * + * @path: The path of the file on disk + * @oldmtime: The previous mtime of the file + * + * @return: file changed: TRUE + * otherwise : FALSE + * + * Uses get_file_mtime () in common-utils.c + */ +gf_boolean_t +_mnt3_has_file_changed (const char *path, time_t *oldmtime) +{ + gf_boolean_t changed = _gf_false; + time_t mtime = {0}; + int ret = 0; + + GF_VALIDATE_OR_GOTO (GF_MNT, path, out); + GF_VALIDATE_OR_GOTO (GF_MNT, oldmtime, out); + + ret = get_file_mtime (path, &mtime); + if (ret < 0) + goto out; + + if (mtime != *oldmtime) { + changed = _gf_true; + *oldmtime = mtime; + } +out: + return changed; +} + +/** + * _mnt_auth_param_refresh_thread - Started using pthread_create () in + * mnt3svc_init (). Reloads exports/netgroups + * files from disk and sets the auth params + * structure in the mount state to reflect + * any changes from disk. + * @argv: Unused argument + * @return: Always returns NULL + */ +void * +_mnt3_auth_param_refresh_thread (void *argv) +{ + struct mount3_state *mstate = (struct mount3_state *)argv; + char *exp_file_path = NULL; + char *ng_file_path = NULL; + size_t nbytes = 0; + time_t exp_time = 0; + time_t ng_time = 0; + gf_boolean_t any_file_changed = _gf_false; + int ret = 0; + + nbytes = strlen (exports_file_path) + 1; + exp_file_path = alloca (nbytes); + snprintf (exp_file_path, nbytes, "%s", exports_file_path); + + nbytes = strlen (netgroups_file_path) + 1; + ng_file_path = alloca (nbytes); + snprintf (ng_file_path, nbytes, "%s", netgroups_file_path); + + /* Set the initial timestamps to avoid reloading right after + * mnt3svc_init () spawns this thread */ + get_file_mtime (exp_file_path, &exp_time); + get_file_mtime (ng_file_path, &ng_time); + + while (_gf_true) { + if (mstate->stop_refresh) + break; + any_file_changed = _gf_false; + + /* Sleep before checking the file again */ + sleep (mstate->nfs->auth_refresh_time_secs); + + if (_mnt3_has_file_changed (exp_file_path, &exp_time)) { + gf_log (GF_MNT, GF_LOG_INFO, "File %s changed, " + "updating exports,", + exp_file_path); + + ret = mnt3_auth_set_exports_auth (mstate->auth_params, + exp_file_path); + if (ret) + gf_log (GF_MNT, GF_LOG_ERROR, + "Failed to set export auth params."); + else + any_file_changed = _gf_true; + } + + if (_mnt3_has_file_changed (ng_file_path, &ng_time)) { + gf_log (GF_MNT, GF_LOG_INFO, "File %s changed," + "updating netgroups", + ng_file_path); + + ret = mnt3_auth_set_netgroups_auth (mstate->auth_params, + ng_file_path); + if (ret) + gf_log (GF_MNT, GF_LOG_ERROR, + "Failed to set netgroup auth params."); + else + any_file_changed = _gf_true; + } + + /* If no files changed, go back to sleep */ + if (!any_file_changed) + continue; + + gf_log (GF_MNT, GF_LOG_INFO, "Purging auth cache."); + auth_cache_purge (mstate->authcache); + + /* Walk through mounts that are no longer authorized + * and unmount them on the server side. This will + * cause subsequent file ops to fail with access denied. + */ + _mnt3_invalidate_old_mounts (mstate); + } + + return NULL; +} + +/** + * _mnt3_init_auth_params -- Initialize authentication parameters by allocating + * the struct and setting the exports & netgroups + * files as parameters. + * + * @mstate : The mount state we are going to set the auth parameters in it. + * + * @return : success: 0 for success + * failure: -EINVAL for bad args, -ENOMEM for allocation errors, < 0 + * for other errors (parsing the files, etc.) These are + * bubbled up from the functions we call to set the params. + */ +int +_mnt3_init_auth_params (struct mount3_state *mstate) +{ + int ret = -EINVAL; + char *exp_file_path = NULL; + char *ng_file_path = NULL; + size_t nbytes = 0; + + GF_VALIDATE_OR_GOTO (GF_MNT, mstate, out); + + mstate->auth_params = mnt3_auth_params_init (mstate); + if (!mstate->auth_params) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Failed to init mount auth params."); + ret = -ENOMEM; + goto out; + } + + nbytes = strlen (exports_file_path) + 1; + exp_file_path = alloca (nbytes); + snprintf (exp_file_path, nbytes, "%s", exports_file_path); + + ret = mnt3_auth_set_exports_auth (mstate->auth_params, exp_file_path); + if (ret < 0) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Failed to set export auth params."); + goto out; + } + + nbytes = strlen (netgroups_file_path) + 1; + ng_file_path = alloca (nbytes); + snprintf (ng_file_path, nbytes, "%s", netgroups_file_path); + + ret = mnt3_auth_set_netgroups_auth (mstate->auth_params, ng_file_path); + if (ret < 0) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Failed to set netgroup auth params."); + goto out; + } + + ret = 0; +out: + return ret; +} + + +/** + * mnt3svc_deinit -- Function called by the nfs translator to cleanup all state + * + * @nfsx : The NFS translator used to perform the cleanup + * This structure holds all the pointers to memory that we need to free + * as well as the threads that have been started. + */ +void +mnt3svc_deinit (xlator_t *nfsx) +{ + struct mount3_state *mstate = NULL; + struct nfs_state *nfs = NULL; + + if (!nfsx || !nfsx->private) + return; + + nfs = (struct nfs_state *)nfsx->private; + mstate = (struct mount3_state *)nfs->mstate; + + if (nfs->refresh_auth) { + /* Mark as true and wait for thread to exit */ + mstate->stop_refresh = _gf_true; + pthread_join (mstate->auth_refresh_thread, NULL); + } + + if (nfs->exports_auth) + mnt3_auth_params_deinit (mstate->auth_params); + + /* Unmount everything and clear mountdict */ + mnt3svc_umountall (mstate); +} rpcsvc_program_t * mnt3svc_init (xlator_t *nfsx) @@ -3023,6 +3790,33 @@ mnt3svc_init (xlator_t *nfsx) goto err; } + mstate->nfs = nfs; + + mstate->mountdict = dict_new (); + if (!mstate->mountdict) { + gf_log (GF_MNT, GF_LOG_ERROR, + "Failed to setup mount dict. Allocation error."); + goto err; + } + + if (nfs->exports_auth) { + ret = _mnt3_init_auth_params (mstate); + if (ret < 0) + goto err; + + mstate->authcache = auth_cache_init (nfs->auth_cache_ttl_sec); + if (!mstate->authcache) { + ret = -ENOMEM; + goto err; + } + + mstate->stop_refresh = _gf_false; /* Allow thread to run */ + pthread_create (&mstate->auth_refresh_thread, NULL, + _mnt3_auth_param_refresh_thread, mstate); + } else + gf_log (GF_MNT, GF_LOG_WARNING, "Exports auth has been " + "disabled!"); + mnt3prog.private = mstate; options = dict_new (); @@ -3030,9 +3824,11 @@ mnt3svc_init (xlator_t *nfsx) if (ret == -1) goto err; - ret = dict_set_dynstr (options, "transport.socket.listen-port", portstr); + ret = dict_set_dynstr (options, "transport.socket.listen-port", + portstr); if (ret == -1) goto err; + ret = dict_set_str (options, "transport-type", "socket"); if (ret == -1) { gf_log (GF_NFS, GF_LOG_ERROR, "dict_set_str error"); diff --git a/xlators/nfs/server/src/mount3.h b/xlators/nfs/server/src/mount3.h index ed553d122ae..8ef9c62a655 100644 --- a/xlators/nfs/server/src/mount3.h +++ b/xlators/nfs/server/src/mount3.h @@ -26,6 +26,9 @@ #include "locking.h" #include "nfs3-fh.h" #include "uuid.h" +#include "exports.h" +#include "mount3-auth.h" +#include "auth-cache.h" /* Registered with portmap */ #define GF_MOUNTV3_PORT 38465 @@ -41,6 +44,9 @@ mnt3svc_init (xlator_t *nfsx); extern rpcsvc_program_t * mnt1svc_init (xlator_t *nfsx); +extern void +mnt3svc_deinit (xlator_t *nfsx); + extern int mount_init_state (xlator_t *nfsx); @@ -50,6 +56,20 @@ mount_reconfigure_state (xlator_t *nfsx, dict_t *options); void mount_rewrite_rmtab (struct mount3_state *ms, char *new_rmtab); +struct mnt3_export * +mnt3_mntpath_to_export (struct mount3_state *ms, const char *dirpath, + gf_boolean_t export_parsing_match); + +extern int +mnt3svc_update_mountlist (struct mount3_state *ms, rpcsvc_request_t *req, + const char *expname, const char *fullpath); + +int +mnt3_authenticate_request (struct mount3_state *ms, rpcsvc_request_t *req, + struct nfs3_fh *fh, const char *volname, + const char *path, char **authorized_path, + char **authorized_host, gf_boolean_t is_write_op); + /* Data structure used to store the list of mounts points currently * in use by NFS clients. */ @@ -60,6 +80,15 @@ struct mountentry { /* The export name */ char exname[MNTPATHLEN]; char hostname[MNTPATHLEN]; + char fullpath[MNTPATHLEN]; + + gf_boolean_t has_full_path; + + /* Since this is stored in a dict, we want to be able + * to find easily get the key we used to store + * the struct in our dict + */ + char hashkey[MNTPATHLEN*2+2]; }; #define MNT3_EXPTYPE_VOLUME 1 @@ -87,14 +116,23 @@ struct mnt3_export { xlator_t *vol; int exptype; + /* This holds the full path that the client requested including + * the volume name AND the subdirectory in the volume. + */ + char *fullpath; + /* Extracted from nfs volume options if nfs.dynamicvolumes is on. */ uuid_t volumeid; + uuid_t mountid; }; struct mount3_state { xlator_t *nfsx; + /* The NFS state that this belongs to */ + struct nfs_state *nfs; + /* The buffers for all network IO are got from this pool. */ struct iobuf_pool *iobpool; @@ -106,8 +144,17 @@ struct mount3_state { */ struct list_head mountlist; - /* Used to protect the mountlist. */ - gf_lock_t mountlock; + /* Dict of current mount points over all the exports from this + * server. Mirrors the mountlist above, but can be used for + * faster lookup in the event that there are several mounts. + * Currently, each NFSOP is validated against this dict: each + * op is checked to see if the host that operates on the path + * does in fact have an entry in the mount dict. + */ + dict_t *mountdict; + + /* Used to protect the mountlist & the mount dict */ + pthread_spinlock_t mountlock; /* Used to insert additional authentication parameters */ struct mnt3_auth_params *auth_params; @@ -115,6 +162,11 @@ struct mount3_state { /* Set to 0 if exporting full volumes is disabled. On by default. */ gf_boolean_t export_volumes; gf_boolean_t export_dirs; + + pthread_t auth_refresh_thread; + gf_boolean_t stop_refresh; + + struct auth_cache *authcache; }; #define gf_mnt3_export_dirs(mst) ((mst)->export_dirs) diff --git a/xlators/nfs/server/src/nfs.c b/xlators/nfs/server/src/nfs.c index 503f6534ab1..3940533ff3e 100644 --- a/xlators/nfs/server/src/nfs.c +++ b/xlators/nfs/server/src/nfs.c @@ -874,6 +874,14 @@ nfs_init_state (xlator_t *this) nfs->mount_udp = 1; } + nfs->exports_auth = GF_NFS_DEFAULT_EXPORT_AUTH; + nfs->auth_refresh_time_secs = GF_NFS_DEFAULT_AUTH_REFRESH_INTERVAL_SEC; + nfs->auth_cache_ttl_sec = GF_NFS_DEFAULT_AUTH_CACHE_TTL_SEC; + + /* TODO: Make this a configurable option in case we don't want to read + * exports/netgroup files off disk when they change. */ + nfs->refresh_auth = 1; + nfs->rmtab = gf_strdup (NFS_DATADIR "/rmtab"); if (dict_get(this->options, "nfs.mount-rmtab")) { ret = dict_get_str (this->options, "nfs.mount-rmtab", &nfs->rmtab); @@ -1390,6 +1398,7 @@ fini (xlator_t *this) struct nfs_state *nfs = NULL; + mnt3svc_deinit (this); nfs = (struct nfs_state *)this->private; gf_log (GF_NFS, GF_LOG_DEBUG, "NFS service going down"); nfs_deinit_versions (&nfs->versions, this); diff --git a/xlators/nfs/server/src/nfs.h b/xlators/nfs/server/src/nfs.h index 7d5dfa63acb..e2e5613d501 100644 --- a/xlators/nfs/server/src/nfs.h +++ b/xlators/nfs/server/src/nfs.h @@ -85,6 +85,15 @@ struct nfs_state { int enable_nlm; int enable_acl; int mount_udp; + + /* Enable exports auth model */ + int exports_auth; + /* Refresh auth params from disk periodically */ + int refresh_auth; + + unsigned int auth_refresh_time_secs; + unsigned int auth_cache_ttl_sec; + char *rmtab; struct rpc_clnt *rpc_clnt; gf_boolean_t server_aux_gids; diff --git a/xlators/nfs/server/src/nfs3-fh.c b/xlators/nfs/server/src/nfs3-fh.c index 510913e8c43..88a8fcb1180 100644 --- a/xlators/nfs/server/src/nfs3-fh.c +++ b/xlators/nfs/server/src/nfs3-fh.c @@ -81,7 +81,7 @@ nfs3_fh_build_indexed_root_fh (xlator_list_t *cl, xlator_t *xl) struct nfs3_fh -nfs3_fh_build_uuid_root_fh (uuid_t volumeid) +nfs3_fh_build_uuid_root_fh (uuid_t volumeid, uuid_t mountid) { struct nfs3_fh fh = {{0}, }; struct iatt buf = {0, }; @@ -90,6 +90,7 @@ nfs3_fh_build_uuid_root_fh (uuid_t volumeid) uuid_copy (buf.ia_gfid, root); nfs3_fh_init (&fh, &buf); uuid_copy (fh.exportid, volumeid); + uuid_copy (fh.mountid, mountid); return fh; } @@ -115,13 +116,15 @@ nfs3_fh_to_str (struct nfs3_fh *fh, char *str, size_t len) { char gfid[GF_UUID_BUF_SIZE]; char exportid[GF_UUID_BUF_SIZE]; + char mountid[GF_UUID_BUF_SIZE]; if ((!fh) || (!str)) return; - snprintf (str, len, "FH: exportid %s, gfid %s", + snprintf (str, len, "FH: exportid %s, gfid %s, mountid %s", uuid_utoa_r (fh->exportid, exportid), - uuid_utoa_r (fh->gfid, gfid)); + uuid_utoa_r (fh->gfid, gfid), + uuid_utoa_r (fh->mountid, mountid)); } void @@ -148,7 +151,6 @@ nfs3_fh_build_parent_fh (struct nfs3_fh *child, struct iatt *newstat, nfs3_fh_init (newfh, newstat); uuid_copy (newfh->exportid, child->exportid); - return 0; } @@ -164,6 +166,7 @@ nfs3_build_fh (inode_t *inode, uuid_t exportid, struct nfs3_fh *newfh) newfh->ident[3] = GF_NFSFH_IDENT3; uuid_copy (newfh->gfid, inode->gfid); uuid_copy (newfh->exportid, exportid); + /*uuid_copy (newfh->mountid, mountid);*/ return 0; } @@ -176,7 +179,7 @@ nfs3_fh_build_child_fh (struct nfs3_fh *parent, struct iatt *newstat, nfs3_fh_init (newfh, newstat); uuid_copy (newfh->exportid, parent->exportid); - + uuid_copy (newfh->mountid, parent->mountid); return 0; } diff --git a/xlators/nfs/server/src/nfs3-fh.h b/xlators/nfs/server/src/nfs3-fh.h index 42d4eaa64d3..3e64772af07 100644 --- a/xlators/nfs/server/src/nfs3-fh.h +++ b/xlators/nfs/server/src/nfs3-fh.h @@ -96,9 +96,10 @@ nfs3_fh_build_parent_fh (struct nfs3_fh *child, struct iatt *newstat, struct nfs3_fh *newfh); extern struct nfs3_fh -nfs3_fh_build_uuid_root_fh (uuid_t volumeid); +nfs3_fh_build_uuid_root_fh (uuid_t volumeid, uuid_t mountid); extern int -nfs3_build_fh (inode_t *inode, uuid_t exportid, struct nfs3_fh *newfh); +nfs3_build_fh (inode_t *inode, uuid_t exportid, + struct nfs3_fh *newfh); #endif diff --git a/xlators/nfs/server/src/nfs3-helpers.c b/xlators/nfs/server/src/nfs3-helpers.c index f6c6eb52ad0..b122faf764d 100644 --- a/xlators/nfs/server/src/nfs3-helpers.c +++ b/xlators/nfs/server/src/nfs3-helpers.c @@ -27,6 +27,7 @@ #include "nfs-mem-types.h" #include "iatt.h" #include "common-utils.h" +#include "mount3.h" #include <string.h> extern int @@ -3844,6 +3845,54 @@ out: return ret; } +/** + * __nfs3_fh_auth_get_peer -- Get a peer name from the rpc request object + * + * @peer: Char * to write to + * @req : The request to get host/peer from + */ +int +__nfs3_fh_auth_get_peer (const rpcsvc_request_t *req, char *peer) +{ + struct sockaddr_storage sastorage = {0, }; + rpc_transport_t *trans = NULL; + int ret = 0; + + /* Why do we pass in the peer here and then + * store it rather than malloc() and return a char * ? We want to avoid + * heap allocations in the IO path as much as possible for speed + * so we try to keep all allocations on the stack. + */ + trans = rpcsvc_request_transport (req); + ret = rpcsvc_transport_peeraddr (trans, peer, RPCSVC_PEER_STRLEN, + &sastorage, sizeof (sastorage)); + if (ret != 0) { + gf_log (GF_NFS3, GF_LOG_WARNING, "Failed to get peer addr: %s", + gai_strerror (ret)); + } + return ret; +} + +/* + * nfs3_fh_auth_nfsop () -- Checks if an nfsop is authorized. + * + * @cs: The NFS call state containing all the relevant information + * + * @return: 0 if authorized + * -EACCES for completely unauthorized fop + * -EROFS for unauthorized write operations (rm, mkdir, write) + */ +inline int +nfs3_fh_auth_nfsop (nfs3_call_state_t *cs, gf_boolean_t is_write_op) +{ + struct nfs_state *nfs = NULL; + struct mount3_state *ms = NULL; + + nfs = (struct nfs_state *)cs->nfsx->private; + ms = (struct mount3_state *)nfs->mstate; + return mnt3_authenticate_request (ms, cs->req, &cs->resolvefh, NULL, + NULL, NULL, NULL, is_write_op); +} int nfs3_fh_resolve_and_resume (nfs3_call_state_t *cs, struct nfs3_fh *fh, diff --git a/xlators/nfs/server/src/nfs3-helpers.h b/xlators/nfs/server/src/nfs3-helpers.h index eada242210d..626aa159d3a 100644 --- a/xlators/nfs/server/src/nfs3-helpers.h +++ b/xlators/nfs/server/src/nfs3-helpers.h @@ -334,6 +334,9 @@ nfs3_is_parentdir_entry (char *entry); uint32_t nfs3_request_to_accessbits (int32_t accbits); +extern int +nfs3_fh_auth_nfsop (nfs3_call_state_t *cs, gf_boolean_t is_write_op); + void nfs3_map_deviceid_to_statdev (struct iatt *ia, uint64_t deviceid); diff --git a/xlators/nfs/server/src/nfs3.c b/xlators/nfs/server/src/nfs3.c index 7ecfc675b61..b8ae5cdb5f9 100644 --- a/xlators/nfs/server/src/nfs3.c +++ b/xlators/nfs/server/src/nfs3.c @@ -246,6 +246,29 @@ out: } \ } while (0) \ + +#define nfs3_check_fh_auth_status(cst, nfstat, is_write_op, erlabl) \ + do { \ + xlator_t *xlatorp = NULL; \ + char buf[256], gfid[256]; \ + rpc_transport_t *trans = NULL; \ + cst->resolve_ret = cst->resolve_errno = \ + nfs3_fh_auth_nfsop (cst, is_write_op); \ + if ((cst)->resolve_ret < 0) { \ + trans = rpcsvc_request_transport (cst->req); \ + xlatorp = nfs3_fh_to_xlator (cst->nfs3state, \ + &cst->resolvefh); \ + uuid_unparse (cst->resolvefh.gfid, gfid); \ + sprintf (buf, "(%s) %s : %s", \ + trans->peerinfo.identifier, \ + xlatorp ? xlatorp->name : "ERR", gfid); \ + gf_log (GF_NFS3, GF_LOG_ERROR, "Unable to resolve FH"\ + ": %s", buf); \ + nfstat = nfs3_errno_to_nfsstat3 (-cst->resolve_errno);\ + goto erlabl; \ + } \ + } while (0) \ + #define nfs3_check_fh_resolve_status(cst, nfstat, erlabl) \ do { \ xlator_t *xlatorp = NULL; \ @@ -711,7 +734,7 @@ nfs3svc_getattr_lookup_cbk (call_frame_t *frame, void *cookie, xlator_t *this, } nfs3_log_common_res (rpcsvc_request_xid (cs->req), NFS3_GETATTR, - status, op_errno); + status, op_errno); nfs3_getattr_reply (cs->req, status, buf); nfs3_call_state_wipe (cs); @@ -738,7 +761,7 @@ nfs3svc_getattr_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, } nfs3_log_common_res (rpcsvc_request_xid (cs->req), NFS3_GETATTR, - status, op_errno); + status, op_errno); nfs3_getattr_reply (cs->req, status, buf); nfs3_call_state_wipe (cs); @@ -762,6 +785,7 @@ nfs3_getattr_resume (void *carg) return ret; cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_false, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); nfs_request_user_init (&nfu, cs->req); /* If inode which is to be getattr'd is the root, we need to do a @@ -777,11 +801,11 @@ nfs3_getattr_resume (void *carg) ret = nfs_stat (cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, */ - if (cs->hardresolved) { - ret = -EFAULT; - stat = NFS3_OK; - goto nfs3err; - } + if (cs->hardresolved) { + ret = -EFAULT; + stat = NFS3_OK; + goto nfs3err; + } /* * If brick state changed, we need to force a proper lookup cycle (as @@ -1242,8 +1266,8 @@ xmit_res: goto out; } - nfs3_log_newfh_res (rpcsvc_request_xid (cs->req), NFS3_LOOKUP, status, - op_errno, &newfh); + nfs3_log_newfh_res (rpcsvc_request_xid (cs->req), NFS3_LOOKUP, + status, op_errno, &newfh); nfs3_lookup_reply (cs->req, status, &newfh, buf, postparent); nfs3_call_state_wipe (cs); out: @@ -1265,6 +1289,7 @@ nfs3svc_lookup_parentdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, nfsstat3 status = NFS3_OK; nfs3_call_state_t *cs = NULL; uuid_t volumeid = {0, }; + uuid_t mountid = {1, }; struct nfs3_state *nfs3 = NULL; cs = frame->local; @@ -1291,7 +1316,7 @@ nfs3svc_lookup_parentdir_cbk (call_frame_t *frame, void *cookie, xlator_t *this, cs->vol); else { __nfs3_get_volume_id (nfs3, cs->vol, volumeid); - newfh = nfs3_fh_build_uuid_root_fh (volumeid); + newfh = nfs3_fh_build_uuid_root_fh (volumeid, mountid); } xmit_res: @@ -1321,6 +1346,7 @@ nfs3_lookup_parentdir_resume (void *carg) } cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_false, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); /* At this point now, the loc in cs is for the directory file handle @@ -1396,6 +1422,7 @@ nfs3_lookup_resume (void *carg) } cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_false, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); cs->parent = cs->resolvefh; @@ -1416,7 +1443,7 @@ nfs3err: nfs3_log_common_res (rpcsvc_request_xid (cs->req), NFS3_LOOKUP, stat, -ret); nfs3_lookup_reply (cs->req, stat, &newfh, &cs->stbuf, - &cs->postparent); + &cs->postparent); nfs3_call_state_wipe (cs); } @@ -1532,8 +1559,9 @@ nfs3svc_access_cbk (call_frame_t *frame, void *cookie, xlator_t *this, cs->resolvedloc.path, strerror (op_errno)); status = nfs3_cbk_errno_status (op_ret, op_errno); } - nfs3_log_common_res (rpcsvc_request_xid (cs->req), NFS3_ACCESS, status, - op_errno); + + nfs3_log_common_res (rpcsvc_request_xid (cs->req), NFS3_ACCESS, + status, op_errno); nfs3_access_reply (cs->req, status, op_errno, cs->accessbits); nfs3_call_state_wipe (cs); @@ -1555,6 +1583,18 @@ nfs3_access_resume (void *carg) } cs = (nfs3_call_state_t *)carg; + + /* Additional checks on the NFS file handle + * go here. The path for an NFS ACCESS call + * goes like this: + * nfs3_access -> nfs3_fh_resolve_and_resume -> nfs3_resolve_resume -> + * nfs3_access_resume -> <macro/function performs check on FH> -> + * <continue or return from function based on check.> ('goto nfs3err' + * terminates this function and writes the appropriate response to the + * client). It is important that you do NOT stick any sort of check + * on the file handle outside of the nfs3_##OP_resume functions. + */ + nfs3_check_fh_auth_status (cs, stat, _gf_false, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); cs->fh = cs->resolvefh; nfs_request_user_init (&nfu, cs->req); @@ -1698,6 +1738,7 @@ nfs3_readlink_resume (void *carg) return ret; cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_false, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); nfs_request_user_init (&nfu, cs->req); ret = nfs_readlink (cs->nfsx, cs->vol, &nfu, &cs->resolvedloc, @@ -1898,6 +1939,7 @@ nfs3_read_resume (void *carg) return ret; cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_false, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); fd = fd_anonymous (cs->resolvedloc.inode); if (!fd) { @@ -2145,6 +2187,7 @@ nfs3_write_resume (void *carg) return ret; cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_true, nfs3err); nfs3_check_fh_resolve_status (cs, stat, nfs3err); fd = fd_anonymous (cs->resolvedloc.inode); if (!fd) { @@ -2217,7 +2260,6 @@ nfs3_write (rpcsvc_request_t *req, struct nfs3_fh *fh, offset3 offset, cs->iobref = iobref; cs->datavec = payload; - ret = nfs3_fh_resolve_and_resume (cs, fh, NULL, nfs3_write_resume); if (ret < 0) stat = nfs3_errno_to_nfsstat3 (-ret); @@ -2361,7 +2403,7 @@ nfs3svc_create_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int ret = -EFAULT; nfs_user_t nfu = {0, }; nfs3_call_state_t *cs = NULL; - inode_t *oldinode = NULL; + inode_t *oldinode = NULL; cs = frame->local; if (op_ret == -1) { @@ -2553,6 +2595,7 @@ nfs3_create_resume (void *carg) return ret; cs = (nfs3_call_state_t *)carg; + nfs3_check_fh_auth_status (cs, stat, _gf_true, nfs3err); nfs3_check_new_fh_resolve_status (cs, stat, nfs3err); if (cs->createmode == EXCLUSIVE) ret = nfs3_create_exclusive (cs); diff --git a/xlators/nfs/server/src/nfs3.h b/xlators/nfs/server/src/nfs3.h index e64ef9d1591..f1b89fe17a8 100644 --- a/xlators/nfs/server/src/nfs3.h +++ b/xlators/nfs/server/src/nfs3.h @@ -232,7 +232,7 @@ struct nfs3_local { mode_t mode; /* NFSv3 FH resolver state */ - int hardresolved; + int hardresolved; struct nfs3_fh resolvefh; loc_t resolvedloc; int resolve_ret; |