+ xnd.ni_cnd.cn_flags = LOCKLEAF;
+ xnd.ni_pathlen = pathlen - 1;
+ xnd.ni_cnd.cn_nameptr = xnd.ni_cnd.cn_pnbuf = path;
+ xnd.ni_startdir = mvp;
+ xnd.ni_usedvp = mvp;
+ xnd.ni_cnd.cn_context = ctx;
+ while ((error = lookup(&xnd)) == ERECYCLE) {
+ xnd.ni_cnd.cn_flags = LOCKLEAF;
+ xnd.ni_cnd.cn_nameptr = xnd.ni_cnd.cn_pnbuf;
+ xnd.ni_usedvp = xnd.ni_dvp = xnd.ni_startdir = mvp;
+ }
+ if (error)
+ goto out1;
+ xvp = xnd.ni_vp;
+ }
+
+ if (vnode_vtype(xvp) != VDIR) {
+ error = EINVAL;
+ vnode_put(xvp);
+ goto out1;
+ }
+
+ /* grab file handle */
+ nx->nx_fh.nfh_len = NFSV3_MAX_FID_SIZE;
+ error = VFS_VPTOFH(xvp, (int*)&nx->nx_fh.nfh_len, &nx->nx_fh.nfh_fid[0], NULL);
+ if (!error && (nx->nx_fh.nfh_len > (int)NFSV3_MAX_FID_SIZE)) {
+ error = EIO;
+ } else {
+ nx->nx_fh.nfh_xh.nxh_fidlen = nx->nx_fh.nfh_len;
+ nx->nx_fh.nfh_len += sizeof(nx->nx_fh.nfh_xh);
+ }
+
+ vnode_put(xvp);
+ if (error)
+ goto out1;
+ } else {
+ nx->nx_fh.nfh_xh.nxh_flags = NXHF_INVALIDFH;
+ nx->nx_fh.nfh_xh.nxh_fidlen = 0;
+ nx->nx_fh.nfh_len = sizeof(nx->nx_fh.nfh_xh);
+ }
+ }
+ } else {
+ nx = NULL;
+ }
+
+ /* perform the export changes */
+ if (unxa->nxa_flags & NXA_DELETE) {
+ if (!nx) {
+ /* delete all exports on this file system */
+ while ((nx = LIST_FIRST(&nxfs->nxfs_exports))) {
+ LIST_REMOVE(nx, nx_next);
+ LIST_REMOVE(nx, nx_hash);
+ /* delete all netopts for this export */
+ nfsrv_free_addrlist(nx, NULL);
+ nx->nx_flags &= ~NX_DEFAULTEXPORT;
+ if (IS_VALID_CRED(nx->nx_defopt.nxo_cred)) {
+ kauth_cred_unref(&nx->nx_defopt.nxo_cred);
+ }
+ /* delete active user list for this export */
+ nfsrv_free_user_list(&nx->nx_user_list);
+ FREE(nx->nx_path, M_TEMP);
+ FREE(nx, M_TEMP);
+ }
+ goto out1;
+ } else if (!unxa->nxa_netcount) {
+ /* delete all netopts for this export */
+ nfsrv_free_addrlist(nx, NULL);
+ nx->nx_flags &= ~NX_DEFAULTEXPORT;
+ if (IS_VALID_CRED(nx->nx_defopt.nxo_cred)) {
+ kauth_cred_unref(&nx->nx_defopt.nxo_cred);
+ }
+ } else {
+ /* delete only the netopts for the given addresses */
+ error = nfsrv_free_addrlist(nx, unxa);
+ if (error)
+ goto out1;
+ }
+ }
+ if (unxa->nxa_flags & NXA_ADD) {
+ /*
+ * If going offline set the export time so that when
+ * coming back on line we will present a new write verifier
+ * to the client.
+ */
+ if (unxa->nxa_flags & NXA_OFFLINE)
+ microtime(&nx->nx_exptime);
+
+ error = nfsrv_hang_addrlist(nx, unxa);
+ if (!error && mp)
+ vfs_setflags(mp, MNT_EXPORTED);
+ }
+
+out1:
+ if (nx && !nx->nx_expcnt) {
+ /* export has no export options */
+ LIST_REMOVE(nx, nx_next);
+ LIST_REMOVE(nx, nx_hash);
+ /* delete active user list for this export */
+ nfsrv_free_user_list(&nx->nx_user_list);
+ FREE(nx->nx_path, M_TEMP);
+ FREE(nx, M_TEMP);
+ }
+ if (LIST_EMPTY(&nxfs->nxfs_exports)) {
+ /* exported file system has no more exports */
+ LIST_REMOVE(nxfs, nxfs_next);
+ FREE(nxfs->nxfs_path, M_TEMP);
+ FREE(nxfs, M_TEMP);
+ if (mp)
+ vfs_clearflags(mp, MNT_EXPORTED);
+ }
+
+out:
+ if (mvp) {
+ vnode_put(mvp);
+ nameidone(&mnd);
+ }
+unlock_out:
+ if (mp)
+ mount_drop(mp, 0);
+ lck_rw_done(&nfsrv_export_rwlock);
+ return (error);
+}
+
+struct nfs_export_options *
+nfsrv_export_lookup(struct nfs_export *nx, mbuf_t nam)
+{
+ struct nfs_export_options *nxo = NULL;
+ struct nfs_netopt *no = NULL;
+ struct radix_node_head *rnh;
+ struct sockaddr *saddr;
+
+ /* Lookup in the export list first. */
+ if (nam != NULL) {
+ saddr = mbuf_data(nam);
+ rnh = nx->nx_rtable[saddr->sa_family];
+ if (rnh != NULL) {
+ no = (struct nfs_netopt *)
+ (*rnh->rnh_matchaddr)((caddr_t)saddr, rnh);
+ if (no && no->no_rnodes->rn_flags & RNF_ROOT)
+ no = NULL;
+ if (no)
+ nxo = &no->no_opt;
+ }
+ }
+ /* If no address match, use the default if it exists. */
+ if ((nxo == NULL) && (nx->nx_flags & NX_DEFAULTEXPORT))
+ nxo = &nx->nx_defopt;
+ return (nxo);
+}
+
+/* find an export for the given handle */
+struct nfs_export *
+nfsrv_fhtoexport(struct nfs_filehandle *nfhp)
+{
+ struct nfs_exphandle *nxh = (struct nfs_exphandle*)nfhp->nfh_fhp;
+ struct nfs_export *nx;
+ uint32_t fsid, expid;
+
+ if (!nfsrv_export_hashtbl)
+ return (NULL);
+ fsid = ntohl(nxh->nxh_fsid);
+ expid = ntohl(nxh->nxh_expid);
+ nx = NFSRVEXPHASH(fsid, expid)->lh_first;
+ for (; nx; nx = LIST_NEXT(nx, nx_hash)) {
+ if (nx->nx_fs->nxfs_id != fsid)
+ continue;
+ if (nx->nx_id != expid)
+ continue;
+ break;
+ }
+ return (nx);
+}
+
+/*
+ * nfsrv_fhtovp() - convert FH to vnode and export info
+ */
+int
+nfsrv_fhtovp(
+ struct nfs_filehandle *nfhp,
+ struct nfsrv_descript *nd,
+ vnode_t *vpp,
+ struct nfs_export **nxp,
+ struct nfs_export_options **nxop)
+{
+ struct nfs_exphandle *nxh = (struct nfs_exphandle*)nfhp->nfh_fhp;
+ struct nfs_export_options *nxo;
+ u_char *fidp;
+ int error;
+ struct mount *mp;
+ mbuf_t nam = NULL;
+ uint32_t v;
+ int i, valid;
+
+ *vpp = NULL;
+ *nxp = NULL;
+ *nxop = NULL;
+
+ if (nd != NULL)
+ nam = nd->nd_nam;
+
+ v = ntohl(nxh->nxh_version);
+ if (v != NFS_FH_VERSION) {
+ /* file handle format not supported */
+ return (ESTALE);
+ }
+ if (nfhp->nfh_len > NFSV3_MAX_FH_SIZE)
+ return (EBADRPC);
+ if (nfhp->nfh_len < (int)sizeof(struct nfs_exphandle))
+ return (ESTALE);
+ v = ntohs(nxh->nxh_flags);
+ if (v & NXHF_INVALIDFH)
+ return (ESTALE);
+
+ *nxp = nfsrv_fhtoexport(nfhp);
+ if (!*nxp)
+ return (ESTALE);
+
+ /* Get the export option structure for this <export, client> tuple. */
+ *nxop = nxo = nfsrv_export_lookup(*nxp, nam);
+ if (nam && (*nxop == NULL))
+ return (EACCES);
+
+ if (nd != NULL) {
+ /* Validate the security flavor of the request */
+ for (i = 0, valid = 0; i < nxo->nxo_sec.count; i++) {
+ if (nd->nd_sec == nxo->nxo_sec.flavors[i]) {
+ valid = 1;
+ break;
+ }
+ }
+ if (!valid) {
+ /*
+ * RFC 2623 section 2.3.2 recommends no authentication
+ * requirement for certain NFS procedures used for mounting.
+ * This allows an unauthenticated superuser on the client
+ * to do mounts for the benefit of authenticated users.
+ */
+ if (nd->nd_vers == NFS_VER2)
+ if (nd->nd_procnum == NFSV2PROC_GETATTR ||
+ nd->nd_procnum == NFSV2PROC_STATFS)
+ valid = 1;
+ if (nd->nd_vers == NFS_VER3)
+ if (nd->nd_procnum == NFSPROC_FSINFO)
+ valid = 1;
+
+ if (!valid)
+ return (NFSERR_AUTHERR | AUTH_REJECTCRED);
+ }
+ }
+
+ if (nxo && (nxo->nxo_flags & NX_OFFLINE))
+ return ((nd == NULL || nd->nd_vers == NFS_VER2) ? ESTALE : NFSERR_TRYLATER);
+
+ /* find mount structure */
+ mp = vfs_getvfs_by_mntonname((*nxp)->nx_fs->nxfs_path);
+ if (mp) {
+ error = vfs_busy(mp, LK_NOWAIT);
+ mount_iterdrop(mp);
+ if (error)
+ mp = NULL;
+ }
+ if (!mp) {
+ /*
+ * We have an export, but no mount?
+ * Perhaps the export just hasn't been marked offline yet.
+ */
+ return ((nd == NULL || nd->nd_vers == NFS_VER2) ? ESTALE : NFSERR_TRYLATER);
+ }
+
+ fidp = nfhp->nfh_fhp + sizeof(*nxh);
+ error = VFS_FHTOVP(mp, nxh->nxh_fidlen, fidp, vpp, NULL);
+ vfs_unbusy(mp);
+ if (error)
+ return (error);
+ /* vnode pointer should be good at this point or ... */
+ if (*vpp == NULL)
+ return (ESTALE);
+ return (0);
+}
+
+/*
+ * nfsrv_credcheck() - check/map credentials according
+ * to given export options.
+ */
+int
+nfsrv_credcheck(
+ struct nfsrv_descript *nd,
+ vfs_context_t ctx,
+ __unused struct nfs_export *nx,
+ struct nfs_export_options *nxo)
+{
+ if (nxo && nxo->nxo_cred) {
+ if ((nxo->nxo_flags & NX_MAPALL) ||
+ ((nxo->nxo_flags & NX_MAPROOT) && !suser(nd->nd_cr, NULL))) {
+ kauth_cred_ref(nxo->nxo_cred);
+ kauth_cred_unref(&nd->nd_cr);
+ nd->nd_cr = nxo->nxo_cred;
+ }
+ }
+ ctx->vc_ucred = nd->nd_cr;
+ return (0);
+}
+
+/*
+ * nfsrv_vptofh() - convert vnode to file handle for given export
+ *
+ * If the caller is passing in a vnode for a ".." directory entry,
+ * they can pass a directory NFS file handle (dnfhp) which will be
+ * checked against the root export file handle. If it matches, we
+ * refuse to provide the file handle for the out-of-export directory.
+ */
+int
+nfsrv_vptofh(
+ struct nfs_export *nx,
+ int nfsvers,
+ struct nfs_filehandle *dnfhp,
+ vnode_t vp,
+ vfs_context_t ctx,
+ struct nfs_filehandle *nfhp)
+{
+ int error;
+ uint32_t maxfidsize;
+
+ nfhp->nfh_fhp = (u_char*)&nfhp->nfh_xh;
+ nfhp->nfh_xh.nxh_version = htonl(NFS_FH_VERSION);
+ nfhp->nfh_xh.nxh_fsid = htonl(nx->nx_fs->nxfs_id);
+ nfhp->nfh_xh.nxh_expid = htonl(nx->nx_id);
+ nfhp->nfh_xh.nxh_flags = 0;
+ nfhp->nfh_xh.nxh_reserved = 0;
+
+ if (nfsvers == NFS_VER2)
+ bzero(&nfhp->nfh_fid[0], NFSV2_MAX_FID_SIZE);
+
+ /* if directory FH matches export root, return invalid FH */
+ if (dnfhp && nfsrv_fhmatch(dnfhp, &nx->nx_fh)) {
+ if (nfsvers == NFS_VER2)
+ nfhp->nfh_len = NFSX_V2FH;
+ else
+ nfhp->nfh_len = sizeof(nfhp->nfh_xh);
+ nfhp->nfh_xh.nxh_fidlen = 0;
+ nfhp->nfh_xh.nxh_flags = htons(NXHF_INVALIDFH);
+ return (0);
+ }
+
+ if (nfsvers == NFS_VER2)
+ maxfidsize = NFSV2_MAX_FID_SIZE;
+ else
+ maxfidsize = NFSV3_MAX_FID_SIZE;
+ nfhp->nfh_len = maxfidsize;
+
+ error = VFS_VPTOFH(vp, (int*)&nfhp->nfh_len, &nfhp->nfh_fid[0], ctx);
+ if (error)
+ return (error);
+ if (nfhp->nfh_len > maxfidsize)
+ return (EOVERFLOW);
+ nfhp->nfh_xh.nxh_fidlen = nfhp->nfh_len;
+ nfhp->nfh_len += sizeof(nfhp->nfh_xh);
+ if ((nfsvers == NFS_VER2) && (nfhp->nfh_len < NFSX_V2FH))
+ nfhp->nfh_len = NFSX_V2FH;
+
+ return (0);
+}
+
+/*
+ * Compare two file handles to see it they're the same.
+ * Note that we don't use nfh_len because that may include
+ * padding in an NFSv2 file handle.
+ */
+int
+nfsrv_fhmatch(struct nfs_filehandle *fh1, struct nfs_filehandle *fh2)
+{
+ struct nfs_exphandle *nxh1, *nxh2;
+ int len1, len2;
+
+ nxh1 = (struct nfs_exphandle *)fh1->nfh_fhp;
+ nxh2 = (struct nfs_exphandle *)fh2->nfh_fhp;
+ len1 = sizeof(fh1->nfh_xh) + nxh1->nxh_fidlen;
+ len2 = sizeof(fh2->nfh_xh) + nxh2->nxh_fidlen;
+ if (len1 != len2)
+ return (0);
+ if (bcmp(nxh1, nxh2, len1))
+ return (0);
+ return (1);
+}
+
+/*
+ * Functions for dealing with active user lists
+ */
+
+/*
+ * Search the hash table for a user node with a matching IP address and uid field.
+ * If found, the node's tm_last timestamp is updated and the node is returned.
+ *
+ * If not found, a new node is allocated (or reclaimed via LRU), initialized, and returned.
+ * Returns NULL if a new node could not be allcoated.
+ *
+ * The list's user_mutex lock MUST be held.
+ */
+struct nfs_user_stat_node *
+nfsrv_get_user_stat_node(struct nfs_active_user_list *list, struct sockaddr *saddr, uid_t uid)
+{
+ struct nfs_user_stat_node *unode;
+ struct timeval now;
+ struct nfs_user_stat_hashtbl_head *head;
+
+ /* seach the hash table */
+ head = NFS_USER_STAT_HASH(list->user_hashtbl, uid);
+ LIST_FOREACH(unode, head, hash_link) {
+ if ((uid == unode->uid) && (nfs_sockaddr_cmp(saddr, (struct sockaddr*)&unode->sock) == 0)) {
+ /* found matching node */
+ break;
+ }
+ }
+
+ if (unode) {
+ /* found node in the hash table, now update lru position */
+ TAILQ_REMOVE(&list->user_lru, unode, lru_link);
+ TAILQ_INSERT_TAIL(&list->user_lru, unode, lru_link);
+
+ /* update time stamp */
+ microtime(&now);
+ unode->tm_last = (uint32_t)now.tv_sec;
+ return unode;
+ }
+
+ if (list->node_count < nfsrv_user_stat_max_nodes) {
+ /* Allocate a new node */
+ MALLOC(unode, struct nfs_user_stat_node *, sizeof(struct nfs_user_stat_node),
+ M_TEMP, M_WAITOK | M_ZERO);
+
+ if (!unode)
+ return NULL;
+
+ /* increment node count */
+ OSAddAtomic(1, &nfsrv_user_stat_node_count);
+ list->node_count++;
+ } else {
+ /* reuse the oldest node in the lru list */
+ unode = TAILQ_FIRST(&list->user_lru);
+
+ if (!unode)
+ return NULL;
+
+ /* Remove the node */
+ TAILQ_REMOVE(&list->user_lru, unode, lru_link);
+ LIST_REMOVE(unode, hash_link);
+ }
+
+ /* Initialize the node */
+ unode->uid = uid;
+ bcopy(saddr, &unode->sock, saddr->sa_len);
+ microtime(&now);
+ unode->ops = 0;
+ unode->bytes_read = 0;
+ unode->bytes_written = 0;
+ unode->tm_start = (uint32_t)now.tv_sec;
+ unode->tm_last = (uint32_t)now.tv_sec;
+
+ /* insert the node */
+ TAILQ_INSERT_TAIL(&list->user_lru, unode, lru_link);
+ LIST_INSERT_HEAD(head, unode, hash_link);
+
+ return unode;
+}
+
+void
+nfsrv_update_user_stat(struct nfs_export *nx, struct nfsrv_descript *nd, uid_t uid, u_int ops, u_int rd_bytes, u_int wr_bytes)
+{
+ struct nfs_user_stat_node *unode;
+ struct nfs_active_user_list *ulist;
+ struct sockaddr *saddr;
+
+ if ((!nfsrv_user_stat_enabled) || (!nx) || (!nd) || (!nd->nd_nam))
+ return;
+
+ saddr = (struct sockaddr *)mbuf_data(nd->nd_nam);
+
+ /* check address family before going any further */
+ if ((saddr->sa_family != AF_INET) && (saddr->sa_family != AF_INET6))
+ return;
+
+ ulist = &nx->nx_user_list;
+
+ /* lock the active user list */
+ lck_mtx_lock(&ulist->user_mutex);
+
+ /* get the user node */
+ unode = nfsrv_get_user_stat_node(ulist, saddr, uid);
+
+ if (!unode) {
+ lck_mtx_unlock(&ulist->user_mutex);
+ return;
+ }
+
+ /* update counters */
+ unode->ops += ops;
+ unode->bytes_read += rd_bytes;
+ unode->bytes_written += wr_bytes;
+
+ /* done */
+ lck_mtx_unlock(&ulist->user_mutex);
+}
+
+/* initialize an active user list */
+void
+nfsrv_init_user_list(struct nfs_active_user_list *ulist)
+{
+ uint i;
+
+ /* initialize the lru */
+ TAILQ_INIT(&ulist->user_lru);
+
+ /* initialize the hash table */
+ for(i = 0; i < NFS_USER_STAT_HASH_SIZE; i++)
+ LIST_INIT(&ulist->user_hashtbl[i]);
+ ulist->node_count = 0;
+
+ lck_mtx_init(&ulist->user_mutex, nfsrv_active_user_mutex_group, LCK_ATTR_NULL);
+}
+
+/* Free all nodes in an active user list */
+void
+nfsrv_free_user_list(struct nfs_active_user_list *ulist)
+{
+ struct nfs_user_stat_node *unode;
+
+ if (!ulist)
+ return;
+
+ while ((unode = TAILQ_FIRST(&ulist->user_lru))) {
+ /* Remove node and free */
+ TAILQ_REMOVE(&ulist->user_lru, unode, lru_link);
+ LIST_REMOVE(unode, hash_link);
+ FREE(unode, M_TEMP);
+
+ /* decrement node count */
+ OSAddAtomic(-1, &nfsrv_user_stat_node_count);
+ }
+ ulist->node_count = 0;
+
+ lck_mtx_destroy(&ulist->user_mutex, nfsrv_active_user_mutex_group);
+}
+
+/* Reclaim old expired user nodes from active user lists. */
+void
+nfsrv_active_user_list_reclaim(void)
+{
+ struct nfs_exportfs *nxfs;
+ struct nfs_export *nx;
+ struct nfs_active_user_list *ulist;
+ struct nfs_user_stat_hashtbl_head oldlist;
+ struct nfs_user_stat_node *unode, *unode_next;
+ struct timeval now;
+ uint32_t tstale;
+
+ LIST_INIT(&oldlist);
+
+ lck_rw_lock_shared(&nfsrv_export_rwlock);
+ microtime(&now);
+ tstale = now.tv_sec - nfsrv_user_stat_max_idle_sec;
+ LIST_FOREACH(nxfs, &nfsrv_exports, nxfs_next) {
+ LIST_FOREACH(nx, &nxfs->nxfs_exports, nx_next) {
+ /* Scan through all user nodes of this export */
+ ulist = &nx->nx_user_list;
+ lck_mtx_lock(&ulist->user_mutex);
+ for (unode = TAILQ_FIRST(&ulist->user_lru); unode; unode = unode_next) {
+ unode_next = TAILQ_NEXT(unode, lru_link);
+
+ /* check if this node has expired */
+ if (unode->tm_last >= tstale)
+ break;
+
+ /* Remove node from the active user list */
+ TAILQ_REMOVE(&ulist->user_lru, unode, lru_link);
+ LIST_REMOVE(unode, hash_link);
+
+ /* Add node to temp list */
+ LIST_INSERT_HEAD(&oldlist, unode, hash_link);
+
+ /* decrement node count */
+ OSAddAtomic(-1, &nfsrv_user_stat_node_count);
+ ulist->node_count--;
+ }
+ /* can unlock this export's list now */
+ lck_mtx_unlock(&ulist->user_mutex);
+ }
+ }
+ lck_rw_done(&nfsrv_export_rwlock);
+
+ /* Free expired nodes */
+ while ((unode = LIST_FIRST(&oldlist))) {
+ LIST_REMOVE(unode, hash_link);
+ FREE(unode, M_TEMP);
+ }