allow.py revision 209962
128219Smsmith#! /usr/bin/python2.4
242475Snsouch#
328219Smsmith# CDDL HEADER START
428219Smsmith#
528219Smsmith# The contents of this file are subject to the terms of the
628219Smsmith# Common Development and Distribution License (the "License").
728219Smsmith# You may not use this file except in compliance with the License.
828219Smsmith#
928219Smsmith# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
1028219Smsmith# or http://www.opensolaris.org/os/licensing.
1128219Smsmith# See the License for the specific language governing permissions
1228219Smsmith# and limitations under the License.
1328219Smsmith#
1428219Smsmith# When distributing Covered Code, include this CDDL HEADER in each
1528219Smsmith# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1628219Smsmith# If applicable, add the following below this CDDL HEADER, with the
1728219Smsmith# fields enclosed by brackets "[]" replaced with your own identifying
1828219Smsmith# information: Portions Copyright [yyyy] [name of copyright owner]
1928219Smsmith#
2028219Smsmith# CDDL HEADER END
2128219Smsmith#
2228219Smsmith# Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
2328219Smsmith# Use is subject to license terms.
2428219Smsmith#
2528219Smsmith
2628219Smsmith"""This module implements the "zfs allow" and "zfs unallow" subcommands.
2728219SmsmithThe only public interface is the zfs.allow.do_allow() function."""
28119418Sobrien
29119418Sobrienimport zfs.util
30119418Sobrienimport zfs.dataset
3155939Snsouchimport optparse
3255939Snsouchimport sys
3328219Smsmithimport pwd
3428219Smsmithimport grp
3555939Snsouchimport errno
36187576Sjhb
3755939Snsouch_ = zfs.util._
38187576Sjhb
3955939Snsouchclass FSPerms(object):
4028219Smsmith	"""This class represents all the permissions that are set on a
41175222Sjhb	particular filesystem (not including those inherited)."""
4228219Smsmith
43175222Sjhb	__slots__ = "create", "sets", "local", "descend", "ld"
44175222Sjhb	__repr__ = zfs.util.default_repr
4528219Smsmith
4628257Smsmith	def __init__(self, raw):
4728219Smsmith		"""Create a FSPerms based on the dict of raw permissions
4855939Snsouch		from zfs.ioctl.get_fsacl()."""
49185003Sjhb		# set of perms
5055939Snsouch		self.create = set()
51185003Sjhb
5269774Sphk		# below are { "Ntype name": set(perms) }
5342475Snsouch		# where N is a number that we just use for sorting,
5428219Smsmith		# type is "user", "group", "everyone", or "" (for sets)
55187576Sjhb		# name is a user, group, or set name, or "" (for everyone)
56187576Sjhb		self.sets = dict()
5728219Smsmith		self.local = dict()
5855939Snsouch		self.descend = dict()
5928219Smsmith		self.ld = dict()
6028219Smsmith
61184130Sjhb		# see the comment in dsl_deleg.c for the definition of whokey
6255939Snsouch		for whokey in raw.keys():
6355939Snsouch			perms = raw[whokey].keys()
6455939Snsouch			whotypechr = whokey[0].lower()
65184130Sjhb			ws = whokey[3:]
6628219Smsmith			if whotypechr == "c":
67184130Sjhb				self.create.update(perms)
6855939Snsouch			elif whotypechr == "s":
6955939Snsouch				nwho = "1" + ws
7055939Snsouch				self.sets.setdefault(nwho, set()).update(perms)
7155939Snsouch			else:
72184130Sjhb				if whotypechr == "u":
7355939Snsouch					try:
74184130Sjhb						name = pwd.getpwuid(int(ws)).pw_name
7555939Snsouch					except KeyError:
76184130Sjhb						name = ws
7755939Snsouch					nwho = "1user " + name
7855939Snsouch				elif whotypechr == "g":
7955939Snsouch					try:
8055939Snsouch						name = grp.getgrgid(int(ws)).gr_name
8155939Snsouch					except KeyError:
8255939Snsouch						name = ws
8355939Snsouch					nwho = "2group " + name
8455939Snsouch				elif whotypechr == "e":
8555939Snsouch					nwho = "3everyone"
8655939Snsouch				else:
8728219Smsmith					raise ValueError(whotypechr)
8856455Speter
8928219Smsmith				if whokey[1] == "l":
9055939Snsouch					d = self.local
9128219Smsmith				elif whokey[1] == "d":
9256455Speter					d = self.descend
93212413Savg				else:
9428219Smsmith					raise ValueError(whokey[1])
9555939Snsouch
9655939Snsouch				d.setdefault(nwho, set()).update(perms)
97185003Sjhb
9855939Snsouch		# Find perms that are in both local and descend, and
9969781Sdwmalone		# move them to ld.
10069781Sdwmalone		for nwho in self.local:
10155939Snsouch			if nwho not in self.descend:
102185003Sjhb				continue
10328219Smsmith			# note: these are set operations
10455939Snsouch			self.ld[nwho] = self.local[nwho] & self.descend[nwho]
10555939Snsouch			self.local[nwho] -= self.ld[nwho]
10628219Smsmith			self.descend[nwho] -= self.ld[nwho]
10755939Snsouch
10855939Snsouch	@staticmethod
10956455Speter	def __ldstr(d, header):
11055939Snsouch		s = ""
11128219Smsmith		for (nwho, perms) in sorted(d.items()):
112185003Sjhb			# local and descend may have entries where perms
11355939Snsouch			# is an empty set, due to consolidating all
11455939Snsouch			# permissions into ld
11555939Snsouch			if perms:
11655939Snsouch				s += "\t%s %s\n" % \
11756455Speter				    (nwho[1:], ",".join(sorted(perms)))
118185003Sjhb		if s:
11955939Snsouch			s = header + s
12055939Snsouch		return s
12155939Snsouch
12255939Snsouch	def __str__(self):
12355939Snsouch		s = self.__ldstr(self.sets, _("Permission sets:\n"))
12455939Snsouch
12555939Snsouch		if self.create:
12628219Smsmith			s += _("Create time permissions:\n")
127185003Sjhb			s += "\t%s\n" % ",".join(sorted(self.create))
12855939Snsouch
12928219Smsmith		s += self.__ldstr(self.local, _("Local permissions:\n"))
130185003Sjhb		s += self.__ldstr(self.descend, _("Descendent permissions:\n"))
13155939Snsouch		s += self.__ldstr(self.ld, _("Local+Descendent permissions:\n"))
132185003Sjhb		return s.rstrip()
13355939Snsouch
13428219Smsmithdef args_to_perms(parser, options, who, perms):
13555939Snsouch	"""Return a dict of raw perms {"whostr" -> {"perm" -> None}}
13655939Snsouch	based on the command-line input."""
13755939Snsouch
138184130Sjhb	# perms is not set if we are doing a "zfs unallow <who> <fs>" to
13955939Snsouch	# remove all of someone's permissions
14055939Snsouch	if perms:
14155939Snsouch		setperms = dict(((p, None) for p in perms if p[0] == "@"))
14255939Snsouch		baseperms = dict(((canonicalized_perm(p), None)
14355939Snsouch		    for p in perms if p[0] != "@"))
14455939Snsouch	else:
145182014Sjhb		setperms = None
14655939Snsouch		baseperms = None
14742475Snsouch
14842475Snsouch	d = dict()
14942475Snsouch
15042475Snsouch	def storeperm(typechr, inheritchr, arg):
15142475Snsouch		assert typechr in "ugecs"
15242475Snsouch		assert inheritchr in "ld-"
15342475Snsouch
15442475Snsouch		def mkwhokey(t):
15542475Snsouch			return "%c%c$%s" % (t, inheritchr, arg)
15642475Snsouch
15742475Snsouch		if baseperms or not perms:
15842475Snsouch			d[mkwhokey(typechr)] = baseperms
15942475Snsouch		if setperms or not perms:
16028257Smsmith			d[mkwhokey(typechr.upper())] = setperms
16128257Smsmith
16228257Smsmith	def decodeid(w, toidfunc, fmt):
16328257Smsmith		try:
16442475Snsouch			return int(w)
16528257Smsmith		except ValueError:
16628257Smsmith			try:
16728257Smsmith				return toidfunc(w)[2]
16828257Smsmith			except KeyError:
16928257Smsmith				parser.error(fmt % w)
17042475Snsouch
17128257Smsmith	if options.set:
17228219Smsmith		storeperm("s", "-", who)
17328257Smsmith	elif options.create:
17428257Smsmith		storeperm("c", "-", "")
175249582Sgabor	else:
17628257Smsmith		for w in who:
17728257Smsmith			if options.user:
17828257Smsmith				id = decodeid(w, pwd.getpwnam,
17928257Smsmith				    _("invalid user %s"))
18078132Speter				typechr = "u"
18128257Smsmith			elif options.group:
18228257Smsmith				id = decodeid(w, grp.getgrnam,
18328257Smsmith				    _("invalid group %s"))
18428257Smsmith				typechr = "g"
18528257Smsmith			elif w == "everyone":
18675061Salfred				id = ""
18728257Smsmith				typechr = "e"
18828257Smsmith			else:
18975061Salfred				try:
19028257Smsmith					id = pwd.getpwnam(w)[2]
19128257Smsmith					typechr = "u"
19228257Smsmith				except KeyError:
19328257Smsmith					try:
19475061Salfred						id = grp.getgrnam(w)[2]
19528257Smsmith						typechr = "g"
19628257Smsmith					except KeyError:
19728257Smsmith						parser.error(_("invalid user/group %s") % w)
19828257Smsmith			if options.local:
19928257Smsmith				storeperm(typechr, "l", id)
20028257Smsmith			if options.descend:
20128257Smsmith				storeperm(typechr, "d", id)
20228257Smsmith	return d
20328257Smsmith
20428257Smsmithperms_subcmd = dict(
20528257Smsmith    create=_("Must also have the 'mount' ability"),
20628257Smsmith    destroy=_("Must also have the 'mount' ability"),
20755939Snsouch    snapshot=_("Must also have the 'mount' ability"),
20828257Smsmith    rollback=_("Must also have the 'mount' ability"),
20942475Snsouch    clone=_("""Must also have the 'create' ability and 'mount'
21028257Smsmith\t\t\t\tability in the origin file system"""),
21138061Smsmith    promote=_("""Must also have the 'mount'
21228257Smsmith\t\t\t\tand 'promote' ability in the origin file system"""),
21328257Smsmith    rename=_("""Must also have the 'mount' and 'create'
214184130Sjhb\t\t\t\tability in the new parent"""),
215185003Sjhb    receive=_("Must also have the 'mount' and 'create' ability"),
21655939Snsouch    allow=_("Must also have the permission that is being\n\t\t\t\tallowed"),
21742475Snsouch    mount=_("Allows mount/umount of ZFS datasets"),
21838061Smsmith    share=_("Allows sharing file systems over NFS or SMB\n\t\t\t\tprotocols"),
21939134Snsouch    send="",
22042475Snsouch)
221184130Sjhb
22242475Snsouchperms_other = dict(
22342475Snsouch    userprop=_("Allows changing any user property"),
22442475Snsouch    userquota=_("Allows accessing any userquota@... property"),
22542475Snsouch    groupquota=_("Allows accessing any groupquota@... property"),
22628257Smsmith    userused=_("Allows reading any userused@... property"),
22728257Smsmith    groupused=_("Allows reading any groupused@... property"),
22828257Smsmith)
22928257Smsmith
23028257Smsmithdef hasset(ds, setname):
23142475Snsouch	"""Return True if the given setname (string) is defined for this
23242475Snsouch	ds (Dataset)."""
233184130Sjhb	# It would be nice to cache the result of get_fsacl().
23428257Smsmith	for raw in ds.get_fsacl().values():
23528257Smsmith		for whokey in raw.keys():
236184130Sjhb			if whokey[0].lower() == "s" and whokey[3:] == setname:
23728257Smsmith				return True
23842475Snsouch	return False
23942475Snsouch
24028257Smsmithdef canonicalized_perm(permname):
24128257Smsmith	"""Return the canonical name (string) for this permission (string).
24228257Smsmith	Raises ZFSError if it is not a valid permission."""
24328257Smsmith	if permname in perms_subcmd.keys() or permname in perms_other.keys():
24428257Smsmith		return permname
24528257Smsmith	try:
24628257Smsmith		return zfs.dataset.getpropobj(permname).name
24728257Smsmith	except KeyError:
24828257Smsmith		raise zfs.util.ZFSError(errno.EINVAL, permname,
24928257Smsmith		    _("invalid permission"))
25028257Smsmith
25128257Smsmithdef print_perms():
25228257Smsmith	"""Print the set of supported permissions."""
25328257Smsmith	print(_("\nThe following permissions are supported:\n"))
25428257Smsmith	fmt = "%-16s %-14s\t%s"
25528257Smsmith	print(fmt % (_("NAME"), _("TYPE"), _("NOTES")))
25628257Smsmith
25728257Smsmith	for (name, note) in sorted(perms_subcmd.iteritems()):
25828257Smsmith		print(fmt % (name, _("subcommand"), note))
25928257Smsmith
26042475Snsouch	for (name, note) in sorted(perms_other.iteritems()):
26142475Snsouch		print(fmt % (name, _("other"), note))
26228257Smsmith
26328257Smsmith	for (name, prop) in sorted(zfs.dataset.proptable.iteritems()):
26428257Smsmith		if prop.visible and prop.delegatable():
26528257Smsmith			print(fmt % (name, _("property"), ""))
26628257Smsmith
26728257Smsmithdef do_allow():
26828257Smsmith	"""Implementes the "zfs allow" and "zfs unallow" subcommands."""
26928257Smsmith	un = (sys.argv[1] == "unallow")
27028257Smsmith
27138061Smsmith	def usage(msg=None):
27238061Smsmith		parser.print_help()
27328257Smsmith		print_perms()
27428257Smsmith		if msg:
27528257Smsmith			print
27638061Smsmith			parser.exit("zfs: error: " + msg)
27738061Smsmith		else:
27838061Smsmith			parser.exit()
27942475Snsouch
28042475Snsouch	if un:
28139134Snsouch		u = _("""unallow [-rldug] <"everyone"|user|group>[,...]
28242475Snsouch	    [<perm|@setname>[,...]] <filesystem|volume>
28342475Snsouch	unallow [-rld] -e [<perm|@setname>[,...]] <filesystem|volume>
28442475Snsouch	unallow [-r] -c [<perm|@setname>[,...]] <filesystem|volume>
28542475Snsouch	unallow [-r] -s @setname [<perm|@setname>[,...]] <filesystem|volume>""")
28642475Snsouch		verb = _("remove")
28742475Snsouch		sstr = _("undefine permission set")
28855939Snsouch	else:
28942475Snsouch		u = _("""allow <filesystem|volume>
29055939Snsouch	allow [-ldug] <"everyone"|user|group>[,...] <perm|@setname>[,...]
29142475Snsouch	    <filesystem|volume>
29242475Snsouch	allow [-ld] -e <perm|@setname>[,...] <filesystem|volume>
29342475Snsouch	allow -c <perm|@setname>[,...] <filesystem|volume>
294185003Sjhb	allow -s @setname <perm|@setname>[,...] <filesystem|volume>""")
29542475Snsouch		verb = _("set")
29642475Snsouch		sstr = _("define permission set")
29742475Snsouch
29842475Snsouch	parser = optparse.OptionParser(usage=u, prog="zfs")
29955939Snsouch
30042475Snsouch	parser.add_option("-l", action="store_true", dest="local",
30142475Snsouch	    help=_("%s permission locally") % verb)
30242475Snsouch	parser.add_option("-d", action="store_true", dest="descend",
30342475Snsouch	    help=_("%s permission for descendents") % verb)
30455939Snsouch	parser.add_option("-u", action="store_true", dest="user",
30542475Snsouch	    help=_("%s permission for user") % verb)
306184130Sjhb	parser.add_option("-g", action="store_true", dest="group",
30742475Snsouch	    help=_("%s permission for group") % verb)
30855939Snsouch	parser.add_option("-e", action="store_true", dest="everyone",
30942475Snsouch	    help=_("%s permission for everyone") % verb)
31055939Snsouch	parser.add_option("-c", action="store_true", dest="create",
31142475Snsouch	    help=_("%s create time permissions") % verb)
31242475Snsouch	parser.add_option("-s", action="store_true", dest="set", help=sstr)
31355939Snsouch	if un:
31442475Snsouch		parser.add_option("-r", action="store_true", dest="recursive",
31555939Snsouch		    help=_("remove permissions recursively"))
31642475Snsouch
31742475Snsouch	if len(sys.argv) == 3 and not un:
31855939Snsouch		# just print the permissions on this fs
31942475Snsouch
32055939Snsouch		if sys.argv[2] == "-h":
32142475Snsouch			# hack to make "zfs allow -h" work
32242475Snsouch			usage()
32355939Snsouch		ds = zfs.dataset.Dataset(sys.argv[2])
32442475Snsouch
32555939Snsouch		p = dict()
32642475Snsouch		for (fs, raw) in ds.get_fsacl().items():
32742475Snsouch			p[fs] = FSPerms(raw)
32855939Snsouch
32942475Snsouch		for fs in sorted(p.keys(), reverse=True):
33055939Snsouch			s = _("---- Permissions on %s ") % fs
33142475Snsouch			print(s + "-" * (70-len(s)))
33242475Snsouch			print(p[fs])
33342536Snsouch		return
33442536Snsouch
33555939Snsouch
33642536Snsouch	(options, args) = parser.parse_args(sys.argv[2:])
33742536Snsouch
33855939Snsouch	if sum((bool(options.everyone), bool(options.user),
33942536Snsouch	    bool(options.group))) > 1:
34042475Snsouch		parser.error(_("-u, -g, and -e are mutually exclusive"))
34155939Snsouch
34242536Snsouch	def mungeargs(expected_len):
34342536Snsouch		if un and len(args) == expected_len-1:
34455939Snsouch			return (None, args[expected_len-2])
34542536Snsouch		elif len(args) == expected_len:
34642475Snsouch			return (args[expected_len-2].split(","),
34755939Snsouch			    args[expected_len-1])
34842536Snsouch		else:
34942536Snsouch			usage(_("wrong number of parameters"))
35055939Snsouch
35142536Snsouch	if options.set:
35242475Snsouch		if options.local or options.descend or options.user or \
35355939Snsouch		    options.group or options.everyone or options.create:
35442536Snsouch			parser.error(_("invalid option combined with -s"))
35542536Snsouch		if args[0][0] != "@":
35655939Snsouch			parser.error(_("invalid set name: missing '@' prefix"))
35742536Snsouch
35842475Snsouch		(perms, fsname) = mungeargs(3)
35955939Snsouch		who = args[0]
36042475Snsouch	elif options.create:
36142536Snsouch		if options.local or options.descend or options.user or \
36255939Snsouch		    options.group or options.everyone or options.set:
36342536Snsouch			parser.error(_("invalid option combined with -c"))
36442536Snsouch
36542475Snsouch		(perms, fsname) = mungeargs(2)
36642536Snsouch		who = None
36742536Snsouch	elif options.everyone:
36842475Snsouch		if options.user or options.group or \
36955939Snsouch		    options.create or options.set:
37042475Snsouch			parser.error(_("invalid option combined with -e"))
37142475Snsouch
37242475Snsouch		(perms, fsname) = mungeargs(2)
37342475Snsouch		who = ["everyone"]
37442475Snsouch	else:
37528257Smsmith		(perms, fsname) = mungeargs(3)
37628257Smsmith		who = args[0].split(",")
37742475Snsouch
37842475Snsouch	if not options.local and not options.descend:
37955939Snsouch		options.local = True
38055939Snsouch		options.descend = True
38128219Smsmith
382187576Sjhb	d = args_to_perms(parser, options, who, perms)
383187576Sjhb
38428257Smsmith	ds = zfs.dataset.Dataset(fsname, snaps=False)
385187576Sjhb
386187576Sjhb	if not un and perms:
387187576Sjhb		for p in perms:
388187576Sjhb			if p[0] == "@" and not hasset(ds, p):
389187576Sjhb				parser.error(_("set %s is not defined") % p)
390187576Sjhb
391187576Sjhb	ds.set_fsacl(un, d)
392187576Sjhb	if un and options.recursive:
393187576Sjhb		for child in ds.descendents():
394187576Sjhb			child.set_fsacl(un, d)
395187576Sjhb