diff options
| -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;  | 
