Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a257cdd0 authored by Andreas Gruenbacher's avatar Andreas Gruenbacher Committed by Trond Myklebust
Browse files

[PATCH] NFSD: Add server support for NFSv3 ACLs.



 This adds functions for encoding and decoding POSIX ACLs for the NFSACL
 protocol extension, and the GETACL and SETACL RPCs.  The implementation is
 compatible with NFSACL in Solaris.

 Signed-off-by: default avatarAndreas Gruenbacher <agruen@suse.de>
 Acked-by: default avatarOlaf Kirch <okir@suse.de>
 Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
 Signed-off-by: default avatarTrond Myklebust <Trond.Myklebust@netapp.com>
parent 9ba02638
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -1353,6 +1353,7 @@ config NFSD
	select LOCKD
	select SUNRPC
	select EXPORTFS
	select NFS_ACL_SUPPORT if NFSD_V3_ACL || NFSD_V2_ACL
	help
	  If you want your Linux box to act as an NFS *server*, so that other
	  computers on your local network which support NFS can access certain
@@ -1376,6 +1377,10 @@ config NFSD
	  To compile the NFS server support as a module, choose M here: the
	  module will be called nfsd.  If unsure, say N.

config NFSD_V2_ACL
	bool
	depends on NFSD

config NFSD_V3
	bool "Provide NFSv3 server support"
	depends on NFSD
@@ -1383,6 +1388,16 @@ config NFSD_V3
	  If you would like to include the NFSv3 server as well as the NFSv2
	  server, say Y here.  If unsure, say Y.

config NFSD_V3_ACL
	bool "Provide server support for the NFSv3 ACL protocol extension"
	depends on NFSD_V3
	select NFSD_V2_ACL
	help
	  Implement the NFSv3 ACL protocol extension for manipulating POSIX
	  Access Control Lists on exported file systems. NFS clients should
	  be compiled with the NFSv3 ACL protocol extension; see the
	  CONFIG_NFS_V3_ACL option.  If unsure, say N.

config NFSD_V4
	bool "Provide NFSv4 server support (EXPERIMENTAL)"
	depends on NFSD_V3 && EXPERIMENTAL
@@ -1427,6 +1442,15 @@ config LOCKD_V4
config EXPORTFS
	tristate

config NFS_ACL_SUPPORT
	tristate
	select FS_POSIX_ACL

config NFS_COMMON
	bool
	depends on NFSD || NFS_FS
	default y

config SUNRPC
	tristate

+1 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ obj-$(CONFIG_BINFMT_FLAT) += binfmt_flat.o

obj-$(CONFIG_FS_MBCACHE)	+= mbcache.o
obj-$(CONFIG_FS_POSIX_ACL)	+= posix_acl.o xattr_acl.o
obj-$(CONFIG_NFS_COMMON)	+= nfs_common/

obj-$(CONFIG_QUOTA)		+= dquot.o
obj-$(CONFIG_QFMT_V1)		+= quota_v1.o

fs/nfs_common/Makefile

0 → 100644
+7 −0
Original line number Diff line number Diff line
#
# Makefile for Linux filesystem routines that are shared by client and server.
#

obj-$(CONFIG_NFS_ACL_SUPPORT) += nfs_acl.o

nfs_acl-objs := nfsacl.o

fs/nfs_common/nfsacl.c

0 → 100644
+257 −0
Original line number Diff line number Diff line
/*
 * fs/nfs_common/nfsacl.c
 *
 *  Copyright (C) 2002-2003 Andreas Gruenbacher <agruen@suse.de>
 */

/*
 * The Solaris nfsacl protocol represents some ACLs slightly differently
 * than POSIX 1003.1e draft 17 does (and we do):
 *
 *  - Minimal ACLs always have an ACL_MASK entry, so they have
 *    four instead of three entries.
 *  - The ACL_MASK entry in such minimal ACLs always has the same
 *    permissions as the ACL_GROUP_OBJ entry. (In extended ACLs
 *    the ACL_MASK and ACL_GROUP_OBJ entries may differ.)
 *  - The identifier fields of the ACL_USER_OBJ and ACL_GROUP_OBJ
 *    entries contain the identifiers of the owner and owning group.
 *    (In POSIX ACLs we always set them to ACL_UNDEFINED_ID).
 *  - ACL entries in the kernel are kept sorted in ascending order
 *    of (e_tag, e_id). Solaris ACLs are unsorted.
 */

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/sunrpc/xdr.h>
#include <linux/nfsacl.h>
#include <linux/nfs3.h>
#include <linux/sort.h>

MODULE_LICENSE("GPL");

EXPORT_SYMBOL(nfsacl_encode);
EXPORT_SYMBOL(nfsacl_decode);

struct nfsacl_encode_desc {
	struct xdr_array2_desc desc;
	unsigned int count;
	struct posix_acl *acl;
	int typeflag;
	uid_t uid;
	gid_t gid;
};

static int
xdr_nfsace_encode(struct xdr_array2_desc *desc, void *elem)
{
	struct nfsacl_encode_desc *nfsacl_desc =
		(struct nfsacl_encode_desc *) desc;
	u32 *p = (u32 *) elem;

	if (nfsacl_desc->count < nfsacl_desc->acl->a_count) {
		struct posix_acl_entry *entry =
			&nfsacl_desc->acl->a_entries[nfsacl_desc->count++];

		*p++ = htonl(entry->e_tag | nfsacl_desc->typeflag);
		switch(entry->e_tag) {
			case ACL_USER_OBJ:
				*p++ = htonl(nfsacl_desc->uid);
				break;
			case ACL_GROUP_OBJ:
				*p++ = htonl(nfsacl_desc->gid);
				break;
			case ACL_USER:
			case ACL_GROUP:
				*p++ = htonl(entry->e_id);
				break;
			default:  /* Solaris depends on that! */
				*p++ = 0;
				break;
		}
		*p++ = htonl(entry->e_perm & S_IRWXO);
	} else {
		const struct posix_acl_entry *pa, *pe;
		int group_obj_perm = ACL_READ|ACL_WRITE|ACL_EXECUTE;

		FOREACH_ACL_ENTRY(pa, nfsacl_desc->acl, pe) {
			if (pa->e_tag == ACL_GROUP_OBJ) {
				group_obj_perm = pa->e_perm & S_IRWXO;
				break;
			}
		}
		/* fake up ACL_MASK entry */
		*p++ = htonl(ACL_MASK | nfsacl_desc->typeflag);
		*p++ = htonl(0);
		*p++ = htonl(group_obj_perm);
	}

	return 0;
}

unsigned int
nfsacl_encode(struct xdr_buf *buf, unsigned int base, struct inode *inode,
	      struct posix_acl *acl, int encode_entries, int typeflag)
{
	int entries = (acl && acl->a_count) ? max_t(int, acl->a_count, 4) : 0;
	struct nfsacl_encode_desc nfsacl_desc = {
		.desc = {
			.elem_size = 12,
			.array_len = encode_entries ? entries : 0,
			.xcode = xdr_nfsace_encode,
		},
		.acl = acl,
		.typeflag = typeflag,
		.uid = inode->i_uid,
		.gid = inode->i_gid,
	};
	int err;

	if (entries > NFS_ACL_MAX_ENTRIES ||
	    xdr_encode_word(buf, base, entries))
		return -EINVAL;
	err = xdr_encode_array2(buf, base + 4, &nfsacl_desc.desc);
	if (!err)
		err = 8 + nfsacl_desc.desc.elem_size *
			  nfsacl_desc.desc.array_len;
	return err;
}

struct nfsacl_decode_desc {
	struct xdr_array2_desc desc;
	unsigned int count;
	struct posix_acl *acl;
};

static int
xdr_nfsace_decode(struct xdr_array2_desc *desc, void *elem)
{
	struct nfsacl_decode_desc *nfsacl_desc =
		(struct nfsacl_decode_desc *) desc;
	u32 *p = (u32 *) elem;
	struct posix_acl_entry *entry;

	if (!nfsacl_desc->acl) {
		if (desc->array_len > NFS_ACL_MAX_ENTRIES)
			return -EINVAL;
		nfsacl_desc->acl = posix_acl_alloc(desc->array_len, GFP_KERNEL);
		if (!nfsacl_desc->acl)
			return -ENOMEM;
		nfsacl_desc->count = 0;
	}

	entry = &nfsacl_desc->acl->a_entries[nfsacl_desc->count++];
	entry->e_tag = ntohl(*p++) & ~NFS_ACL_DEFAULT;
	entry->e_id = ntohl(*p++);
	entry->e_perm = ntohl(*p++);

	switch(entry->e_tag) {
		case ACL_USER_OBJ:
		case ACL_USER:
		case ACL_GROUP_OBJ:
		case ACL_GROUP:
		case ACL_OTHER:
			if (entry->e_perm & ~S_IRWXO)
				return -EINVAL;
			break;
		case ACL_MASK:
			/* Solaris sometimes sets additonal bits in the mask */
			entry->e_perm &= S_IRWXO;
			break;
		default:
			return -EINVAL;
	}

	return 0;
}

static int
cmp_acl_entry(const void *x, const void *y)
{
	const struct posix_acl_entry *a = x, *b = y;

	if (a->e_tag != b->e_tag)
		return a->e_tag - b->e_tag;
	else if (a->e_id > b->e_id)
		return 1;
	else if (a->e_id < b->e_id)
		return -1;
	else
		return 0;
}

/*
 * Convert from a Solaris ACL to a POSIX 1003.1e draft 17 ACL.
 */
static int
posix_acl_from_nfsacl(struct posix_acl *acl)
{
	struct posix_acl_entry *pa, *pe,
	       *group_obj = NULL, *mask = NULL;

	if (!acl)
		return 0;

	sort(acl->a_entries, acl->a_count, sizeof(struct posix_acl_entry),
	     cmp_acl_entry, NULL);

	/* Clear undefined identifier fields and find the ACL_GROUP_OBJ
	   and ACL_MASK entries. */
	FOREACH_ACL_ENTRY(pa, acl, pe) {
		switch(pa->e_tag) {
			case ACL_USER_OBJ:
				pa->e_id = ACL_UNDEFINED_ID;
				break;
			case ACL_GROUP_OBJ:
				pa->e_id = ACL_UNDEFINED_ID;
				group_obj = pa;
				break;
			case ACL_MASK:
				mask = pa;
				/* fall through */
			case ACL_OTHER:
				pa->e_id = ACL_UNDEFINED_ID;
				break;
		}
	}
	if (acl->a_count == 4 && group_obj && mask &&
	    mask->e_perm == group_obj->e_perm) {
		/* remove bogus ACL_MASK entry */
		memmove(mask, mask+1, (3 - (mask - acl->a_entries)) *
				      sizeof(struct posix_acl_entry));
		acl->a_count = 3;
	}
	return 0;
}

unsigned int
nfsacl_decode(struct xdr_buf *buf, unsigned int base, unsigned int *aclcnt,
	      struct posix_acl **pacl)
{
	struct nfsacl_decode_desc nfsacl_desc = {
		.desc = {
			.elem_size = 12,
			.xcode = pacl ? xdr_nfsace_decode : NULL,
		},
	};
	u32 entries;
	int err;

	if (xdr_decode_word(buf, base, &entries) ||
	    entries > NFS_ACL_MAX_ENTRIES)
		return -EINVAL;
	err = xdr_decode_array2(buf, base + 4, &nfsacl_desc.desc);
	if (err)
		return err;
	if (pacl) {
		if (entries != nfsacl_desc.desc.array_len ||
		    posix_acl_from_nfsacl(nfsacl_desc.acl) != 0) {
			posix_acl_release(nfsacl_desc.acl);
			return -EINVAL;
		}
		*pacl = nfsacl_desc.acl;
	}
	if (aclcnt)
		*aclcnt = entries;
	return 8 + nfsacl_desc.desc.elem_size *
		   nfsacl_desc.desc.array_len;
}
+2 −0
Original line number Diff line number Diff line
@@ -6,7 +6,9 @@ obj-$(CONFIG_NFSD) += nfsd.o

nfsd-y 			:= nfssvc.o nfsctl.o nfsproc.o nfsfh.o vfs.o \
			   export.o auth.o lockd.o nfscache.o nfsxdr.o stats.o
nfsd-$(CONFIG_NFSD_V2_ACL) += nfs2acl.o
nfsd-$(CONFIG_NFSD_V3)	+= nfs3proc.o nfs3xdr.o
nfsd-$(CONFIG_NFSD_V3_ACL) += nfs3acl.o
nfsd-$(CONFIG_NFSD_V4)	+= nfs4proc.o nfs4xdr.o nfs4state.o nfs4idmap.o \
			   nfs4acl.o nfs4callback.o
nfsd-objs		:= $(nfsd-y)
Loading