diff -urN current/TODO nfs4acl/TODO --- current/TODO 1970-01-01 01:00:00.000000000 +0100 +++ nfs4acl/TODO 2009-09-18 14:09:46.095083176 +0200 @@ -0,0 +1,29 @@ +Things that need to be done before this goes into -CURRENT: + +- Decide how VAPPEND is supposed to work - always OR-ed with VWRITE, + or used alone. Fix stuff accordingly. + +- MFP4 of the above. + +- Decide what to do with MNT_RDONLY et a. Fix stuff. + +- MFP4 of the above. + +- Talk about semantics. Do whatever is needed. + +- Review. + +Things to do, in no particular order: + +- Add support for NFSv4 ACLs to tar(1). + +- Make 'struct acl' variable size. + +- Benchmark things. + +- (maybe) Add a flag to inode to mark whether the file has ACL; + don't try to read ACL extatrr if the flag is not set. + +- Port Samba vfs_zfsacl module using libsunacl + (http://freebsd.org/~trasz/libsunacl.shar). + diff -urN current/sbin/mount/mntopts.h nfs4acl/sbin/mount/mntopts.h --- current/sbin/mount/mntopts.h 2009-09-18 14:03:02.280397231 +0200 +++ nfs4acl/sbin/mount/mntopts.h 2009-09-18 14:23:46.584032749 +0200 @@ -54,6 +54,7 @@ #define MOPT_SNAPSHOT { "snapshot", 0, MNT_SNAPSHOT, 0 } #define MOPT_MULTILABEL { "multilabel", 0, MNT_MULTILABEL, 0 } #define MOPT_ACLS { "acls", 0, MNT_ACLS, 0 } +#define MOPT_NFS4ACLS { "nfs4acls", 0, MNT_NFS4ACLS, 0 } /* Control flags. */ #define MOPT_FORCE { "force", 0, MNT_FORCE, 0 } @@ -87,7 +88,8 @@ MOPT_NOCLUSTERR, \ MOPT_NOCLUSTERW, \ MOPT_MULTILABEL, \ - MOPT_ACLS + MOPT_ACLS, \ + MOPT_NFS4ACLS void getmntopts(const char *, const struct mntopt *, int *, int *); void rmslashes(char *, char *); diff -urN current/sbin/mount/mount.8 nfs4acl/sbin/mount/mount.8 --- current/sbin/mount/mount.8 2009-09-18 14:03:02.289197232 +0200 +++ nfs4acl/sbin/mount/mount.8 2009-09-21 22:25:35.510062591 +0200 @@ -120,11 +120,14 @@ The following options are available: .Bl -tag -width indent .It Cm acls -Enable Access Control Lists, or ACLS, which can be customized via the +Enable POSIX.1e Access Control Lists, or ACLS, which can be customized via the .Xr setfacl 1 and .Xr getfacl 1 commands. +This flag is mutually exclusive with +.Cm nfs4acls +flag. .It Cm async All .Tn I/O @@ -186,6 +189,15 @@ .Xr mac 4 for more information, which cause the multilabel mount flag to be set automatically at mount-time. +.It Cm nfs4acls +Enable NFSv4 ACLS, which can be customized via the +.Xr setfacl 1 +and +.Xr getfacl 1 +commands. +This flag is mutually exclusive with +.Cm acls +flag. .It Cm noasync Metadata I/O should be done synchronously, while data I/O should be done asynchronously. diff -urN current/sbin/mount/mount.c nfs4acl/sbin/mount/mount.c --- current/sbin/mount/mount.c 2009-09-18 14:03:02.309340854 +0200 +++ nfs4acl/sbin/mount/mount.c 2009-09-21 22:25:35.719995418 +0200 @@ -111,6 +111,7 @@ { MNT_SOFTDEP, "soft-updates" }, { MNT_MULTILABEL, "multilabel" }, { MNT_ACLS, "acls" }, + { MNT_NFS4ACLS, "nfs4acls" }, { MNT_GJOURNAL, "gjournal" }, { 0, NULL } }; @@ -918,6 +919,7 @@ if (flags & MNT_SUIDDIR) res = catopt(res, "suiddir"); if (flags & MNT_MULTILABEL) res = catopt(res, "multilabel"); if (flags & MNT_ACLS) res = catopt(res, "acls"); + if (flags & MNT_NFS4ACLS) res = catopt(res, "nfs4acls"); return (res); } diff -urN current/sys/kern/vfs_acl.c nfs4acl/sys/kern/vfs_acl.c --- current/sys/kern/vfs_acl.c 2009-10-10 23:45:18.427827279 +0200 +++ nfs4acl/sys/kern/vfs_acl.c 2009-10-10 23:54:42.615667354 +0200 @@ -173,7 +173,7 @@ /* * Convert "old" type - ACL_TYPE_{ACCESS,DEFAULT}_OLD - into its "new" - * counterpart. It's required for old (pre-NFS4 ACLs) libc to work + * counterpart. It's required for old (pre-NFSv4 ACLs) libc to work * with new kernel. Fixing 'type' for old binaries with new libc * is being done in lib/libc/posix1e/acl_support.c:_acl_type_unold(). */ @@ -213,8 +213,20 @@ inkernelacl = acl_alloc(M_WAITOK); error = acl_copyin(aclp, inkernelacl, type); - if (error) + if (error != 0) + goto out; + + /* + * With NFSv4 ACLs, chmod(2) may need to add additional entries. + * Make sure it has enough room for that - splitting every entry + * into two and appending "canonical six" entries at the end. + */ + if (type == ACL_TYPE_NFS4 && + inkernelacl->acl_cnt > (ACL_MAX_ENTRIES - 6) / 2) { + error = ENOSPC; goto out; + } + error = vn_start_write(vp, &mp, V_WAIT | PCATCH); if (error != 0) goto out; @@ -233,7 +245,7 @@ vn_finished_write(mp); out: acl_free(inkernelacl); - return(error); + return (error); } /* @@ -253,12 +265,14 @@ if (error != 0) goto out; #endif + error = VOP_ACCESSX(vp, VREAD_ACL, td->td_ucred, td); + if (error != 0) + goto out; + error = VOP_GETACL(vp, acl_type_unold(type), inkernelacl, td->td_ucred, td); -#ifdef MAC out: -#endif VOP_UNLOCK(vp, 0); if (error == 0) error = acl_copyout(inkernelacl, aclp, type); @@ -276,12 +290,12 @@ int error; error = vn_start_write(vp, &mp, V_WAIT | PCATCH); - if (error) + if (error != 0) return (error); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); #ifdef MAC error = mac_vnode_check_deleteacl(td->td_ucred, vp, type); - if (error) + if (error != 0) goto out; #endif error = VOP_SETACL(vp, acl_type_unold(type), 0, td->td_ucred, td); @@ -305,9 +319,22 @@ inkernelacl = acl_alloc(M_WAITOK); error = acl_copyin(aclp, inkernelacl, type); - if (error) + if (error != 0) goto out; - error = VOP_ACLCHECK(vp, type, inkernelacl, td->td_ucred, td); + + /* + * With NFSv4 ACLs, chmod(2) may need to add additional entries. + * Make sure it has enough room for that - splitting every entry + * into two and appending "canonical six" entries at the end. + */ + if (type == ACL_TYPE_NFS4 && + inkernelacl->acl_cnt > (ACL_MAX_ENTRIES - 6) / 2) { + error = ENOSPC; + goto out; + } + + error = VOP_ACLCHECK(vp, acl_type_unold(type), inkernelacl, + td->td_ucred, td); out: acl_free(inkernelacl); return (error); diff -urN current/sys/kern/vfs_vnops.c nfs4acl/sys/kern/vfs_vnops.c --- current/sys/kern/vfs_vnops.c 2009-10-02 08:53:04.081084325 +0200 +++ nfs4acl/sys/kern/vfs_vnops.c 2009-10-02 08:53:15.899220150 +0200 @@ -714,6 +714,9 @@ if (error) return (error); #endif + error = VOP_ACCESSX(vp, VREAD_ATTRIBUTES, active_cred, td); + if (error) + return (error); vap = &vattr; diff -urN current/sys/security/mac_lomac/mac_lomac.c nfs4acl/sys/security/mac_lomac/mac_lomac.c --- current/sys/security/mac_lomac/mac_lomac.c 2009-09-18 14:08:23.917651687 +0200 +++ nfs4acl/sys/security/mac_lomac/mac_lomac.c 2009-09-18 14:31:02.458091323 +0200 @@ -2470,7 +2470,7 @@ obj = SLOT(vplabel); /* XXX privilege override for admin? */ - if (accmode & (VWRITE | VAPPEND | VADMIN)) { + if (accmode & VMODIFY_PERMS) { if (!lomac_subject_dominate(subj, obj)) return (EACCES); } diff -urN current/sys/sys/mount.h nfs4acl/sys/sys/mount.h --- current/sys/sys/mount.h 2009-09-18 14:08:29.867321267 +0200 +++ nfs4acl/sys/sys/mount.h 2009-09-18 14:31:14.897835202 +0200 @@ -239,6 +239,7 @@ #define MNT_NOATIME 0x10000000 /* disable update of file access time */ #define MNT_NOCLUSTERR 0x40000000 /* disable cluster read */ #define MNT_NOCLUSTERW 0x80000000 /* disable cluster write */ +#define MNT_NFS4ACLS 0x00000010 /* * NFS export related mount flags. @@ -274,7 +275,7 @@ MNT_ROOTFS | MNT_NOATIME | MNT_NOCLUSTERR| \ MNT_NOCLUSTERW | MNT_SUIDDIR | MNT_SOFTDEP | \ MNT_IGNORE | MNT_EXPUBLIC | MNT_NOSYMFOLLOW | \ - MNT_GJOURNAL | MNT_MULTILABEL | MNT_ACLS) + MNT_GJOURNAL | MNT_MULTILABEL | MNT_ACLS | MNT_NFS4ACLS) /* Mask of flags that can be updated. */ #define MNT_UPDATEMASK (MNT_NOSUID | MNT_NOEXEC | \ @@ -282,7 +283,7 @@ MNT_NOATIME | \ MNT_NOSYMFOLLOW | MNT_IGNORE | \ MNT_NOCLUSTERR | MNT_NOCLUSTERW | MNT_SUIDDIR | \ - MNT_ACLS | MNT_USER) + MNT_ACLS | MNT_USER | MNT_NFS4ACLS) /* * External filesystem command modifier flags. @@ -300,10 +301,6 @@ #define MNT_CMDFLAGS (MNT_UPDATE | MNT_DELEXPORT | MNT_RELOAD | \ MNT_FORCE | MNT_SNAPSHOT | MNT_BYFSID) /* - * Still available. - */ -#define MNT_SPARE_0x00000010 0x00000010 -/* * Internal filesystem control flags stored in mnt_kern_flag. * * MNTK_UNMOUNT locks the mount entry so that name lookup cannot proceed diff -urN current/sys/ufs/ffs/ffs_vfsops.c nfs4acl/sys/ufs/ffs/ffs_vfsops.c --- current/sys/ufs/ffs/ffs_vfsops.c 2009-09-18 14:08:32.937639702 +0200 +++ nfs4acl/sys/ufs/ffs/ffs_vfsops.c 2009-09-23 19:54:44.375167513 +0200 @@ -128,7 +128,7 @@ static const char *ffs_opts[] = { "acls", "async", "noatime", "noclusterr", "noclusterw", "noexec", "export", "force", "from", "multilabel", "snapshot", "nosuid", "suiddir", "nosymfollow", "sync", - "union", NULL }; + "union", "nfs4acls", NULL }; static int ffs_mount(struct mount *mp) @@ -177,6 +177,15 @@ vfs_deleteopt(mp->mnt_opt, "snapshot"); } + if (vfs_getopt(mp->mnt_optnew, "nfs4acls", NULL, NULL) == 0) { + if (mntorflags & MNT_ACLS) { + printf("WARNING: \"acls\" and \"nfs4acls\" " + "options are mutually exclusive\n"); + return (EINVAL); + } + mntorflags |= MNT_NFS4ACLS; + } + MNT_ILOCK(mp); mp->mnt_flag = (mp->mnt_flag | mntorflags) & ~mntandnotflags; MNT_IUNLOCK(mp); @@ -834,7 +843,13 @@ if ((fs->fs_flags & FS_ACLS) != 0) { #ifdef UFS_ACL MNT_ILOCK(mp); - mp->mnt_flag |= MNT_ACLS; + + if (mp->mnt_flag & MNT_NFS4ACLS) + printf("WARNING: ACLs flag on fs conflicts with " + "\"nfs4acls\" mount option; flag ignored\n"); + else + mp->mnt_flag |= MNT_ACLS; + MNT_IUNLOCK(mp); #else printf( diff -urN current/sys/ufs/ufs/ufs_acl.c nfs4acl/sys/ufs/ufs/ufs_acl.c --- current/sys/ufs/ufs/ufs_acl.c 2009-09-18 14:08:33.907238631 +0200 +++ nfs4acl/sys/ufs/ufs/ufs_acl.c 2009-09-24 15:25:44.489076125 +0200 @@ -140,6 +140,62 @@ DIP_SET(ip, i_mode, ip->i_mode); } +static int +ufs_getacl_nfs4(struct vop_getacl_args *ap) +{ + int error, len; + struct inode *ip = VTOI(ap->a_vp); + + if ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) == 0) + return (EINVAL); + + len = sizeof(*ap->a_aclp); + bzero(ap->a_aclp, len); + + error = vn_extattr_get(ap->a_vp, IO_NODELOCKED, + NFS4_ACL_EXTATTR_NAMESPACE, + NFS4_ACL_EXTATTR_NAME, &len, (char *) ap->a_aclp, + ap->a_td); + ap->a_aclp->acl_maxcnt = ACL_MAX_ENTRIES; + if (error == ENOATTR) { + /* + * Legitimately no ACL set on object, purely + * emulate it through the inode. + */ + acl_nfs4_sync_acl_from_mode(ap->a_aclp, ip->i_mode, ip->i_uid); + + return (0); + } + + if (error) + return (error); + + if (len != sizeof(*ap->a_aclp)) { + /* + * A short (or long) read, meaning that for + * some reason the ACL is corrupted. Return + * EPERM since the object DAC protections + * are unsafe. + */ + printf("ufs_getacl_nfs4(): Loaded invalid ACL (" + "%d bytes), inumber %d on %s\n", len, + ip->i_number, ip->i_fs->fs_fsmnt); + + return (EPERM); + } + + error = acl_nfs4_check(ap->a_aclp, ap->a_vp->v_type == VDIR); + if (error) { + printf("ufs_getacl_nfs4(): Loaded invalid ACL " + "(failed acl_nfs4_check), inumber %d on %s\n", + ip->i_number, ip->i_fs->fs_fsmnt); + + return (EPERM); + } + + return (0); +} + /* * Read POSIX.1e ACL from an EA. Return error if its not found * or if any other error has occured. @@ -209,7 +265,7 @@ * ACLs, remove this check. */ if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) - return (EOPNOTSUPP); + return (EINVAL); old = malloc(sizeof(*old), M_ACL, M_WAITOK | M_ZERO); @@ -282,9 +338,92 @@ } */ *ap; { + if ((ap->a_vp->v_mount->mnt_flag & (MNT_ACLS | MNT_NFS4ACLS)) == 0) + return (EOPNOTSUPP); + + if (ap->a_type == ACL_TYPE_NFS4) + return (ufs_getacl_nfs4(ap)); + return (ufs_getacl_posix1e(ap)); } +static int +ufs_setacl_nfs4(struct vop_setacl_args *ap) +{ + int error; + mode_t mode; + struct inode *ip = VTOI(ap->a_vp); + + if ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) == 0) + return (EINVAL); + + if (ap->a_vp->v_mount->mnt_flag & MNT_RDONLY) + return (EROFS); + + if (ap->a_aclp == NULL) + return (EINVAL); + + error = VOP_ACLCHECK(ap->a_vp, ap->a_type, ap->a_aclp, ap->a_cred, + ap->a_td); + if (error) + return (error); + + /* + * Authorize the ACL operation. + */ + if (ip->i_flags & (IMMUTABLE | APPEND)) + return (EPERM); + + /* + * Must hold VWRITE_ACL or have appropriate privilege. + */ + if ((error = VOP_ACCESSX(ap->a_vp, VWRITE_ACL, ap->a_cred, ap->a_td))) + return (error); + + if (acl_nfs4_is_trivial(ap->a_aclp, ip->i_uid)) { + error = vn_extattr_rm(ap->a_vp, IO_NODELOCKED, + NFS4_ACL_EXTATTR_NAMESPACE, + NFS4_ACL_EXTATTR_NAME, ap->a_td); + + /* + * An attempt to remove ACL from a file that didn't have + * any extended entries is not an error. + */ + if (error == ENOATTR) + error = 0; + + } else { + error = vn_extattr_set(ap->a_vp, IO_NODELOCKED, + NFS4_ACL_EXTATTR_NAMESPACE, + NFS4_ACL_EXTATTR_NAME, + sizeof(*ap->a_aclp), + (char *) ap->a_aclp, ap->a_td); + } + + /* + * Map lack of attribute definition in UFS_EXTATTR into lack of + * support for ACLs on the filesystem. + */ + if (error == ENOATTR) + return (EOPNOTSUPP); + + if (error) + return (error); + + mode = ip->i_mode; + + acl_nfs4_sync_mode_from_acl(&mode, ap->a_aclp); + + ip->i_mode &= ACL_PRESERVE_MASK; + ip->i_mode |= mode; + DIP_SET(ip, i_mode, ip->i_mode); + ip->i_flag |= IN_CHANGE; + + VN_KNOTE_UNLOCKED(ap->a_vp, NOTE_ATTRIB); + + return (0); +} + /* * Set the ACL on a file. * @@ -302,7 +441,7 @@ struct oldacl *old; if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) - return (EOPNOTSUPP); + return (EINVAL); /* * If this is a set operation rather than a delete operation, @@ -422,16 +561,35 @@ struct thread *td; } */ *ap; { + if ((ap->a_vp->v_mount->mnt_flag & (MNT_ACLS | MNT_NFS4ACLS)) == 0) + return (EOPNOTSUPP); + + if (ap->a_type == ACL_TYPE_NFS4) + return (ufs_setacl_nfs4(ap)); return (ufs_setacl_posix1e(ap)); } static int +ufs_aclcheck_nfs4(struct vop_aclcheck_args *ap) +{ + int is_directory = 0; + + if ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) == 0) + return (EINVAL); + + if (ap->a_vp->v_type == VDIR) + is_directory = 1; + + return (acl_nfs4_check(ap->a_aclp, is_directory)); +} + +static int ufs_aclcheck_posix1e(struct vop_aclcheck_args *ap) { if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) - return (EOPNOTSUPP); + return (EINVAL); /* * Verify we understand this type of ACL, and that it applies @@ -471,6 +629,12 @@ } */ *ap; { + if ((ap->a_vp->v_mount->mnt_flag & (MNT_ACLS | MNT_NFS4ACLS)) == 0) + return (EOPNOTSUPP); + + if (ap->a_type == ACL_TYPE_NFS4) + return (ufs_aclcheck_nfs4(ap)); + return (ufs_aclcheck_posix1e(ap)); } diff -urN current/sys/ufs/ufs/ufs_lookup.c nfs4acl/sys/ufs/ufs/ufs_lookup.c --- current/sys/ufs/ufs/ufs_lookup.c 2009-09-18 14:08:35.087138032 +0200 +++ nfs4acl/sys/ufs/ufs/ufs_lookup.c 2009-09-18 14:31:20.047432147 +0200 @@ -80,6 +80,61 @@ static int ufs_lookup_(struct vnode *, struct vnode **, struct componentname *, ino_t *); +static int +ufs_delete_denied(struct vnode *vdp, struct vnode *tdp, struct ucred *cred, + struct thread *td) +{ + int error; + +#ifdef UFS_ACL + /* + * NFSv4 Minor Version 1, draft-ietf-nfsv4-minorversion1-03.txt + * + * 3.16.2.1. ACE4_DELETE vs. ACE4_DELETE_CHILD + */ + + /* + * XXX: Is this check required? + */ + error = VOP_ACCESS(vdp, VEXEC, cred, td); + if (error) + return (error); + + error = VOP_ACCESSX(tdp, VDELETE, cred, td); + if (error == 0) + return (0); + + error = VOP_ACCESSX(vdp, VDELETE_CHILD, cred, td); + if (error == 0) + return (0); + + error = VOP_ACCESSX(vdp, VEXPLICIT_DENY | VDELETE_CHILD, cred, td); + if (error) + return (error); + +#endif /* !UFS_ACL */ + + /* + * Standard Unix access control - delete access requires VWRITE. + */ + error = VOP_ACCESS(vdp, VWRITE, cred, td); + if (error) + return (error); + + /* + * If directory is "sticky", then user must own + * the directory, or the file in it, else she + * may not delete it (unless she's root). This + * implements append-only directories. + */ + if ((VTOI(vdp)->i_mode & ISVTX) && + VOP_ACCESS(vdp, VADMIN, cred, td) && + VOP_ACCESS(tdp, VADMIN, cred, td)) + return (EPERM); + + return (0); +} + /* * Convert a component of a pathname into a pointer to a locked inode. * This is a very central and rather complicated routine. @@ -410,8 +465,13 @@ /* * Access for write is interpreted as allowing * creation of files in the directory. + * + * XXX: Fix the comment above. */ - error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread); + if (flags & WILLBEDIR) + error = VOP_ACCESSX(vdp, VAPPEND, cred, cnp->cn_thread); + else + error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread); if (error) return (error); /* @@ -498,12 +558,17 @@ if (nameiop == DELETE && (flags & ISLASTCN)) { if (flags & LOCKPARENT) ASSERT_VOP_ELOCKED(vdp, __FUNCTION__); - /* - * Write access to directory required to delete files. - */ - error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread); - if (error) + if ((error = VFS_VGET(vdp->v_mount, ino, + LK_EXCLUSIVE, &tdp)) != 0) return (error); + + error = ufs_delete_denied(vdp, tdp, cred, cnp->cn_thread); + if (error) { + vput(tdp); + return (error); + } + + /* * Return pointer to current entry in dp->i_offset, * and distance past previous entry (if there @@ -523,23 +588,10 @@ if (dp->i_number == ino) { VREF(vdp); *vpp = vdp; - return (0); - } - if ((error = VFS_VGET(vdp->v_mount, ino, - LK_EXCLUSIVE, &tdp)) != 0) - return (error); - /* - * If directory is "sticky", then user must own - * the directory, or the file in it, else she - * may not delete it (unless she's root). This - * implements append-only directories. - */ - if ((dp->i_mode & ISVTX) && - VOP_ACCESS(vdp, VADMIN, cred, cnp->cn_thread) && - VOP_ACCESS(tdp, VADMIN, cred, cnp->cn_thread)) { vput(tdp); - return (EPERM); + return (0); } + *vpp = tdp; return (0); } @@ -551,7 +603,11 @@ * regular file, or empty directory. */ if (nameiop == RENAME && (flags & ISLASTCN)) { - if ((error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread))) + if (flags & WILLBEDIR) + error = VOP_ACCESSX(vdp, VAPPEND, cred, cnp->cn_thread); + else + error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread); + if (error) return (error); /* * Careful about locking second inode. @@ -563,6 +619,33 @@ if ((error = VFS_VGET(vdp->v_mount, ino, LK_EXCLUSIVE, &tdp)) != 0) return (error); + + error = ufs_delete_denied(vdp, tdp, cred, cnp->cn_thread); + if (error) { + vput(tdp); + return (error); + } + +#ifdef SunOS_doesnt_do_that + /* + * The only purpose of this check is to return the correct + * error. Assume that we want to rename directory "a" + * to a file "b", and that we have no ACL_WRITE_DATA on + * a containing directory, but we _do_ have ACL_APPEND_DATA. + * In that case, the VOP_ACCESS check above will return 0, + * and the operation will fail with ENOTDIR instead + * of EACCESS. + */ + if (tdp->v_type == VDIR) + error = VOP_ACCESSX(vdp, VAPPEND, cred, cnp->cn_thread); + else + error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread); + if (error) { + vput(tdp); + return (error); + } +#endif + *vpp = tdp; cnp->cn_flags |= SAVENAME; return (0); diff -urN current/sys/ufs/ufs/ufs_vnops.c nfs4acl/sys/ufs/ufs/ufs_vnops.c --- current/sys/ufs/ufs/ufs_vnops.c 2009-09-18 14:08:35.537222330 +0200 +++ nfs4acl/sys/ufs/ufs/ufs_vnops.c 2009-09-25 16:49:21.751993616 +0200 @@ -88,7 +88,7 @@ #include -static vop_access_t ufs_access; +static vop_accessx_t ufs_accessx; static int ufs_chmod(struct vnode *, int, struct ucred *, struct thread *); static int ufs_chown(struct vnode *, uid_t, gid_t, struct ucred *, struct thread *); static vop_close_t ufs_close; @@ -298,8 +298,8 @@ } static int -ufs_access(ap) - struct vop_access_args /* { +ufs_accessx(ap) + struct vop_accessx_args /* { struct vnode *a_vp; accmode_t a_accmode; struct ucred *a_cred; @@ -315,6 +315,7 @@ #endif #ifdef UFS_ACL struct acl *acl; + acl_type_t type; #endif /* @@ -322,7 +323,7 @@ * unless the file is a socket, fifo, or a block or * character device resident on the filesystem. */ - if (accmode & VWRITE) { + if (accmode & VMODIFY_PERMS) { switch (vp->v_type) { case VDIR: case VLNK: @@ -367,41 +368,60 @@ } } - /* If immutable bit set, nobody gets to write it. */ - if ((accmode & VWRITE) && (ip->i_flags & (IMMUTABLE | SF_SNAPSHOT))) + /* + * If immutable bit set, nobody gets to write it. "& ~VADMIN_PERMS" + * is here, because without it, * it would be impossible for the owner + * to remove the IMMUTABLE flag. + */ + if ((accmode & (VMODIFY_PERMS & ~VADMIN_PERMS)) && + (ip->i_flags & (IMMUTABLE | SF_SNAPSHOT))) return (EPERM); #ifdef UFS_ACL - if ((vp->v_mount->mnt_flag & MNT_ACLS) != 0) { + if ((vp->v_mount->mnt_flag & (MNT_ACLS | MNT_NFS4ACLS)) != 0) { + if (vp->v_mount->mnt_flag & MNT_NFS4ACLS) + type = ACL_TYPE_NFS4; + else + type = ACL_TYPE_ACCESS; + acl = acl_alloc(M_WAITOK); - error = VOP_GETACL(vp, ACL_TYPE_ACCESS, acl, ap->a_cred, - ap->a_td); + error = VOP_GETACL(vp, type, acl, ap->a_cred, ap->a_td); switch (error) { - case EOPNOTSUPP: - error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, - ip->i_gid, ap->a_accmode, ap->a_cred, NULL); - break; case 0: - error = vaccess_acl_posix1e(vp->v_type, ip->i_uid, - ip->i_gid, acl, ap->a_accmode, ap->a_cred, NULL); + if (type == ACL_TYPE_NFS4) { + error = vaccess_acl_nfs4(vp->v_type, ip->i_uid, + ip->i_gid, acl, accmode, ap->a_cred, NULL); + } else { + error = vfs_unixify_accmode(&accmode); + if (error == 0) + error = vaccess_acl_posix1e(vp->v_type, ip->i_uid, + ip->i_gid, acl, accmode, ap->a_cred, NULL); + } break; default: - printf( -"ufs_access(): Error retrieving ACL on object (%d).\n", - error); + if (error != EOPNOTSUPP) + printf( +"ufs_accessx(): Error retrieving ACL on object (%d).\n", + error); /* * XXX: Fall back until debugged. Should * eventually possibly log an error, and return * EPERM for safety. */ - error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, - ip->i_gid, ap->a_accmode, ap->a_cred, NULL); + error = vfs_unixify_accmode(&accmode); + if (error == 0) + error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, + ip->i_gid, accmode, ap->a_cred, NULL); } acl_free(acl); - } else + + return (error); + } #endif /* !UFS_ACL */ + error = vfs_unixify_accmode(&accmode); + if (error == 0) error = vaccess(vp->v_type, ip->i_mode, ip->i_uid, ip->i_gid, - ap->a_accmode, ap->a_cred, NULL); + accmode, ap->a_cred, NULL); return (error); } @@ -608,11 +628,20 @@ * check succeeds. */ if (vap->va_vaflags & VA_UTIMES_NULL) { - error = VOP_ACCESS(vp, VADMIN, cred, td); + /* + * NFSv4.1, draft 21, 6.2.1.3.1, Discussion of Mask Attributes + * + * "A user having ACL_WRITE_DATA or ACL_WRITE_ATTRIBUTES + * will be allowed to set the times [..] to the current + * server time." + * + * XXX: Calling it four times seems a little excessive. + */ + error = VOP_ACCESSX(vp, VWRITE_ATTRIBUTES, cred, td); if (error) error = VOP_ACCESS(vp, VWRITE, cred, td); } else - error = VOP_ACCESS(vp, VADMIN, cred, td); + error = VOP_ACCESSX(vp, VWRITE_ATTRIBUTES, cred, td); if (error) return (error); if (vap->va_atime.tv_sec != VNOVAL) @@ -652,6 +681,35 @@ return (error); } +#ifdef UFS_ACL +static int +ufs_update_nfs4_acl_after_mode_change(struct vnode *vp, int mode, + int file_owner_id, struct ucred *cred, struct thread *td) +{ + int error; + struct acl *aclp; + + aclp = acl_alloc(M_WAITOK); + + error = VOP_GETACL(vp, ACL_TYPE_NFS4, aclp, cred, td); + /* + * We cannot get EOPNOTSUPP here, as the filesystem claims + * to support ACLs. + */ + if (error) + goto out; + + acl_nfs4_sync_acl_from_mode(aclp, mode, file_owner_id); + + error = VOP_SETACL(vp, ACL_TYPE_NFS4, aclp, cred, td); + +out: + acl_free(aclp); + + return (error); +} +#endif /* UFS_ACL */ + /* * Mark this file's access time for update for vfs_mark_atime(). This * is called from execve() and mmap(). @@ -689,7 +747,7 @@ * To modify the permissions on a file, must possess VADMIN * for that file. */ - if ((error = VOP_ACCESS(vp, VADMIN, cred, td))) + if ((error = VOP_ACCESSX(vp, VWRITE_ACL, cred, td))) return (error); /* * Privileged processes may set the sticky bit on non-directories, @@ -706,11 +764,25 @@ if (error) return (error); } + + /* + * Deny setting setuid if we are not the file owner. + */ + if ((mode & ISUID) && ip->i_uid != cred->cr_uid) { + error = priv_check_cred(cred, PRIV_VFS_ADMIN, 0); + if (error) + return (error); + } + ip->i_mode &= ~ALLPERMS; ip->i_mode |= (mode & ALLPERMS); DIP_SET(ip, i_mode, ip->i_mode); ip->i_flag |= IN_CHANGE; - return (0); +#ifdef UFS_ACL + if ((vp->v_mount->mnt_flag & MNT_NFS4ACLS) != 0) + error = ufs_update_nfs4_acl_after_mode_change(vp, mode, ip->i_uid, cred, td); +#endif + return (error); } /* @@ -742,14 +814,14 @@ * To modify the ownership of a file, must possess VADMIN for that * file. */ - if ((error = VOP_ACCESS(vp, VADMIN, cred, td))) + if ((error = VOP_ACCESSX(vp, VWRITE_OWNER, cred, td))) return (error); /* * To change the owner of a file, or change the group of a file to a * group of which we are not a member, the caller must have * privilege. */ - if ((uid != ip->i_uid || + if (((uid != ip->i_uid && uid != cred->cr_uid) || (gid != ip->i_gid && !groupmember(gid, cred))) && (error = priv_check_cred(cred, PRIV_VFS_CHOWN, 0))) return (error); @@ -1397,6 +1469,36 @@ return (error); } +#ifdef UFS_ACL +static int +ufs_do_nfs4_acl_inheritance(struct vnode *dvp, struct vnode *tvp, + mode_t child_mode, struct ucred *cred, struct thread *td) +{ + int error; + struct acl *parent_aclp, *child_aclp; + + parent_aclp = acl_alloc(M_WAITOK); + child_aclp = acl_alloc(M_WAITOK | M_ZERO); + + error = VOP_GETACL(dvp, ACL_TYPE_NFS4, parent_aclp, cred, td); + if (error) + goto out; + + acl_nfs4_compute_inherited_acl(parent_aclp, child_aclp, + child_mode, VTOI(tvp)->i_uid, tvp->v_type == VDIR); + + error = VOP_SETACL(tvp, ACL_TYPE_NFS4, child_aclp, cred, td); + if (error) + goto out; + +out: + acl_free(parent_aclp); + acl_free(child_aclp); + + return (error); +} +#endif + /* * Mkdir system call */ @@ -1630,6 +1732,13 @@ acl_free(dacl); dacl = acl = NULL; } + + if (dvp->v_mount->mnt_flag & MNT_NFS4ACLS) { + error = ufs_do_nfs4_acl_inheritance(dvp, tvp, dmode, + cnp->cn_cred, cnp->cn_thread); + if (error) + goto bad; + } #endif /* !UFS_ACL */ /* @@ -2117,6 +2226,7 @@ switch (ap->a_name) { case _PC_ACL_EXTENDED: + case _PC_ACL_NFS4: case _PC_ACL_PATH_MAX: case _PC_MAC_PRESENT: return (ufs_pathconf(ap)); @@ -2169,9 +2279,21 @@ *ap->a_retval = 0; #endif break; + + case _PC_ACL_NFS4: +#ifdef UFS_ACL + if (ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) + *ap->a_retval = 1; + else + *ap->a_retval = 0; +#else + *ap->a_retval = 0; +#endif + break; + case _PC_ACL_PATH_MAX: #ifdef UFS_ACL - if (ap->a_vp->v_mount->mnt_flag & MNT_ACLS) + if (ap->a_vp->v_mount->mnt_flag & (MNT_ACLS | MNT_NFS4ACLS)) *ap->a_retval = ACL_MAX_ENTRIES; else *ap->a_retval = 3; @@ -2466,6 +2588,13 @@ } acl_free(acl); } + + if (dvp->v_mount->mnt_flag & MNT_NFS4ACLS) { + error = ufs_do_nfs4_acl_inheritance(dvp, tvp, mode, + cnp->cn_cred, cnp->cn_thread); + if (error) + goto bad; + } #endif /* !UFS_ACL */ ufs_makedirentry(ip, cnp, &newdir); error = ufs_direnter(dvp, tvp, &newdir, cnp, NULL); @@ -2496,7 +2625,7 @@ .vop_read = VOP_PANIC, .vop_reallocblks = VOP_PANIC, .vop_write = VOP_PANIC, - .vop_access = ufs_access, + .vop_accessx = ufs_accessx, .vop_bmap = ufs_bmap, .vop_cachedlookup = ufs_lookup, .vop_close = ufs_close, @@ -2540,7 +2669,7 @@ struct vop_vector ufs_fifoops = { .vop_default = &fifo_specops, .vop_fsync = VOP_PANIC, - .vop_access = ufs_access, + .vop_accessx = ufs_accessx, .vop_close = ufsfifo_close, .vop_getattr = ufs_getattr, .vop_inactive = ufs_inactive, diff -urN current/tools/regression/acltools/02.t nfs4acl/tools/regression/acltools/02.t --- current/tools/regression/acltools/02.t 1970-01-01 01:00:00.000000000 +0100 +++ nfs4acl/tools/regression/acltools/02.t 2009-09-18 14:31:24.747377532 +0200 @@ -0,0 +1,59 @@ +#!/bin/sh +# +# This is a wrapper script to run tools-nfs4.test. +# +# If any of the tests fails, here is how to debug it: go to +# the directory with problematic filesystem mounted on it, +# and do /path/to/test run /path/to/test tools-nfs4.test, e.g. +# +# /usr/src/tools/regression/acltools/run /usr/src/tools/regression/acltools/tools-nfs4.test +# +# Output should be obvious. + +echo "1..4" + +if [ `whoami` != "root" ]; then + echo "not ok 1 - you need to be root to run this test." + exit 1 +fi + +TESTDIR=`dirname $0` + +# Set up the test filesystem. +MD=`mdconfig -at swap -s 10m` +MNT=`mktemp -dt acltools` +newfs /dev/$MD > /dev/null +mount -o nfs4acls /dev/$MD $MNT +if [ $? -ne 0 ]; then + echo "not ok 1 - mount failed." + exit 1 +fi + +echo "ok 1" + +cd $MNT + +# First, check whether we can crash the kernel by creating too many +# entries. For some reason this won't work in the test file. +touch xxx +setfacl -x5 xxx +while :; do setfacl -a0 u:42:rwx:allow xxx 2> /dev/null; if [ $? -ne 0 ]; then break; fi; done +chmod 600 xxx +rm xxx +echo "ok 2" + +perl $TESTDIR/run $TESTDIR/tools-nfs4.test > /dev/null + +if [ $? -eq 0 ]; then + echo "ok 3" +else + echo "not ok 3" +fi + +cd / +umount -f $MNT +rmdir $MNT +mdconfig -du $MD + +echo "ok 4" +