summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libglusterfs/src/common-utils.c112
-rw-r--r--libglusterfs/src/common-utils.h3
-rwxr-xr-xtests/basic/mount-nfs-auth.t148
-rw-r--r--tests/bugs/nfs/bug-1143880-fix-gNFSd-auth-crash.t20
-rw-r--r--xlators/nfs/server/src/mount3.c852
-rw-r--r--xlators/nfs/server/src/mount3.h56
-rw-r--r--xlators/nfs/server/src/nfs.c9
-rw-r--r--xlators/nfs/server/src/nfs.h9
-rw-r--r--xlators/nfs/server/src/nfs3-fh.c13
-rw-r--r--xlators/nfs/server/src/nfs3-fh.h5
-rw-r--r--xlators/nfs/server/src/nfs3-helpers.c49
-rw-r--r--xlators/nfs/server/src/nfs3-helpers.h3
-rw-r--r--xlators/nfs/server/src/nfs3.c73
-rw-r--r--xlators/nfs/server/src/nfs3.h2
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;