1219089Spjd#! /usr/bin/python2.6
2209962Smm#
3209962Smm# CDDL HEADER START
4209962Smm#
5209962Smm# The contents of this file are subject to the terms of the
6209962Smm# Common Development and Distribution License (the "License").
7209962Smm# You may not use this file except in compliance with the License.
8209962Smm#
9209962Smm# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10209962Smm# or http://www.opensolaris.org/os/licensing.
11209962Smm# See the License for the specific language governing permissions
12209962Smm# and limitations under the License.
13209962Smm#
14209962Smm# When distributing Covered Code, include this CDDL HEADER in each
15209962Smm# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16209962Smm# If applicable, add the following below this CDDL HEADER, with the
17209962Smm# fields enclosed by brackets "[]" replaced with your own identifying
18209962Smm# information: Portions Copyright [yyyy] [name of copyright owner]
19209962Smm#
20209962Smm# CDDL HEADER END
21209962Smm#
22219089Spjd# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23260183Sdelphij# Copyright (c) 2013 by Delphix. All rights reserved.
24209962Smm#
25209962Smm
26209962Smm"""This module implements the "zfs allow" and "zfs unallow" subcommands.
27209962SmmThe only public interface is the zfs.allow.do_allow() function."""
28209962Smm
29209962Smmimport zfs.util
30209962Smmimport zfs.dataset
31209962Smmimport optparse
32209962Smmimport sys
33209962Smmimport pwd
34209962Smmimport grp
35209962Smmimport errno
36209962Smm
37209962Smm_ = zfs.util._
38209962Smm
39209962Smmclass FSPerms(object):
40209962Smm	"""This class represents all the permissions that are set on a
41209962Smm	particular filesystem (not including those inherited)."""
42209962Smm
43209962Smm	__slots__ = "create", "sets", "local", "descend", "ld"
44209962Smm	__repr__ = zfs.util.default_repr
45209962Smm
46209962Smm	def __init__(self, raw):
47209962Smm		"""Create a FSPerms based on the dict of raw permissions
48209962Smm		from zfs.ioctl.get_fsacl()."""
49209962Smm		# set of perms
50209962Smm		self.create = set()
51209962Smm
52209962Smm		# below are { "Ntype name": set(perms) }
53209962Smm		# where N is a number that we just use for sorting,
54209962Smm		# type is "user", "group", "everyone", or "" (for sets)
55209962Smm		# name is a user, group, or set name, or "" (for everyone)
56209962Smm		self.sets = dict()
57209962Smm		self.local = dict()
58209962Smm		self.descend = dict()
59209962Smm		self.ld = dict()
60209962Smm
61209962Smm		# see the comment in dsl_deleg.c for the definition of whokey
62209962Smm		for whokey in raw.keys():
63209962Smm			perms = raw[whokey].keys()
64209962Smm			whotypechr = whokey[0].lower()
65209962Smm			ws = whokey[3:]
66209962Smm			if whotypechr == "c":
67209962Smm				self.create.update(perms)
68209962Smm			elif whotypechr == "s":
69209962Smm				nwho = "1" + ws
70209962Smm				self.sets.setdefault(nwho, set()).update(perms)
71209962Smm			else:
72209962Smm				if whotypechr == "u":
73209962Smm					try:
74209962Smm						name = pwd.getpwuid(int(ws)).pw_name
75209962Smm					except KeyError:
76209962Smm						name = ws
77209962Smm					nwho = "1user " + name
78209962Smm				elif whotypechr == "g":
79209962Smm					try:
80209962Smm						name = grp.getgrgid(int(ws)).gr_name
81209962Smm					except KeyError:
82209962Smm						name = ws
83209962Smm					nwho = "2group " + name
84209962Smm				elif whotypechr == "e":
85209962Smm					nwho = "3everyone"
86209962Smm				else:
87209962Smm					raise ValueError(whotypechr)
88209962Smm
89209962Smm				if whokey[1] == "l":
90209962Smm					d = self.local
91209962Smm				elif whokey[1] == "d":
92209962Smm					d = self.descend
93209962Smm				else:
94209962Smm					raise ValueError(whokey[1])
95209962Smm
96209962Smm				d.setdefault(nwho, set()).update(perms)
97209962Smm
98209962Smm		# Find perms that are in both local and descend, and
99209962Smm		# move them to ld.
100209962Smm		for nwho in self.local:
101209962Smm			if nwho not in self.descend:
102209962Smm				continue
103209962Smm			# note: these are set operations
104209962Smm			self.ld[nwho] = self.local[nwho] & self.descend[nwho]
105209962Smm			self.local[nwho] -= self.ld[nwho]
106209962Smm			self.descend[nwho] -= self.ld[nwho]
107209962Smm
108209962Smm	@staticmethod
109209962Smm	def __ldstr(d, header):
110209962Smm		s = ""
111209962Smm		for (nwho, perms) in sorted(d.items()):
112209962Smm			# local and descend may have entries where perms
113209962Smm			# is an empty set, due to consolidating all
114209962Smm			# permissions into ld
115209962Smm			if perms:
116209962Smm				s += "\t%s %s\n" % \
117209962Smm				    (nwho[1:], ",".join(sorted(perms)))
118209962Smm		if s:
119209962Smm			s = header + s
120209962Smm		return s
121209962Smm
122209962Smm	def __str__(self):
123209962Smm		s = self.__ldstr(self.sets, _("Permission sets:\n"))
124209962Smm
125209962Smm		if self.create:
126209962Smm			s += _("Create time permissions:\n")
127209962Smm			s += "\t%s\n" % ",".join(sorted(self.create))
128209962Smm
129209962Smm		s += self.__ldstr(self.local, _("Local permissions:\n"))
130209962Smm		s += self.__ldstr(self.descend, _("Descendent permissions:\n"))
131209962Smm		s += self.__ldstr(self.ld, _("Local+Descendent permissions:\n"))
132209962Smm		return s.rstrip()
133209962Smm
134209962Smmdef args_to_perms(parser, options, who, perms):
135209962Smm	"""Return a dict of raw perms {"whostr" -> {"perm" -> None}}
136209962Smm	based on the command-line input."""
137209962Smm
138209962Smm	# perms is not set if we are doing a "zfs unallow <who> <fs>" to
139209962Smm	# remove all of someone's permissions
140209962Smm	if perms:
141209962Smm		setperms = dict(((p, None) for p in perms if p[0] == "@"))
142209962Smm		baseperms = dict(((canonicalized_perm(p), None)
143209962Smm		    for p in perms if p[0] != "@"))
144209962Smm	else:
145209962Smm		setperms = None
146209962Smm		baseperms = None
147209962Smm
148209962Smm	d = dict()
149209962Smm
150209962Smm	def storeperm(typechr, inheritchr, arg):
151209962Smm		assert typechr in "ugecs"
152209962Smm		assert inheritchr in "ld-"
153209962Smm
154209962Smm		def mkwhokey(t):
155209962Smm			return "%c%c$%s" % (t, inheritchr, arg)
156209962Smm
157209962Smm		if baseperms or not perms:
158209962Smm			d[mkwhokey(typechr)] = baseperms
159209962Smm		if setperms or not perms:
160209962Smm			d[mkwhokey(typechr.upper())] = setperms
161209962Smm
162209962Smm	def decodeid(w, toidfunc, fmt):
163209962Smm		try:
164209962Smm			return int(w)
165209962Smm		except ValueError:
166209962Smm			try:
167209962Smm				return toidfunc(w)[2]
168209962Smm			except KeyError:
169209962Smm				parser.error(fmt % w)
170209962Smm
171209962Smm	if options.set:
172209962Smm		storeperm("s", "-", who)
173209962Smm	elif options.create:
174209962Smm		storeperm("c", "-", "")
175209962Smm	else:
176209962Smm		for w in who:
177209962Smm			if options.user:
178209962Smm				id = decodeid(w, pwd.getpwnam,
179209962Smm				    _("invalid user %s"))
180209962Smm				typechr = "u"
181209962Smm			elif options.group:
182209962Smm				id = decodeid(w, grp.getgrnam,
183209962Smm				    _("invalid group %s"))
184209962Smm				typechr = "g"
185209962Smm			elif w == "everyone":
186209962Smm				id = ""
187209962Smm				typechr = "e"
188209962Smm			else:
189209962Smm				try:
190209962Smm					id = pwd.getpwnam(w)[2]
191209962Smm					typechr = "u"
192209962Smm				except KeyError:
193209962Smm					try:
194209962Smm						id = grp.getgrnam(w)[2]
195209962Smm						typechr = "g"
196209962Smm					except KeyError:
197209962Smm						parser.error(_("invalid user/group %s") % w)
198209962Smm			if options.local:
199209962Smm				storeperm(typechr, "l", id)
200209962Smm			if options.descend:
201209962Smm				storeperm(typechr, "d", id)
202209962Smm	return d
203209962Smm
204209962Smmperms_subcmd = dict(
205209962Smm    create=_("Must also have the 'mount' ability"),
206209962Smm    destroy=_("Must also have the 'mount' ability"),
207219089Spjd    snapshot="",
208219089Spjd    rollback="",
209209962Smm    clone=_("""Must also have the 'create' ability and 'mount'
210209962Smm\t\t\t\tability in the origin file system"""),
211209962Smm    promote=_("""Must also have the 'mount'
212209962Smm\t\t\t\tand 'promote' ability in the origin file system"""),
213209962Smm    rename=_("""Must also have the 'mount' and 'create'
214209962Smm\t\t\t\tability in the new parent"""),
215209962Smm    receive=_("Must also have the 'mount' and 'create' ability"),
216209962Smm    allow=_("Must also have the permission that is being\n\t\t\t\tallowed"),
217209962Smm    mount=_("Allows mount/umount of ZFS datasets"),
218209962Smm    share=_("Allows sharing file systems over NFS or SMB\n\t\t\t\tprotocols"),
219209962Smm    send="",
220219089Spjd    hold=_("Allows adding a user hold to a snapshot"),
221219089Spjd    release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"),
222219089Spjd    diff=_("Allows lookup of paths within a dataset,\n\t\t\t\tgiven an object number. Ordinary users need this\n\t\t\t\tin order to use zfs diff"),
223260183Sdelphij    bookmark="",
224209962Smm)
225209962Smm
226209962Smmperms_other = dict(
227209962Smm    userprop=_("Allows changing any user property"),
228209962Smm    userquota=_("Allows accessing any userquota@... property"),
229209962Smm    groupquota=_("Allows accessing any groupquota@... property"),
230209962Smm    userused=_("Allows reading any userused@... property"),
231209962Smm    groupused=_("Allows reading any groupused@... property"),
232209962Smm)
233209962Smm
234209962Smmdef hasset(ds, setname):
235209962Smm	"""Return True if the given setname (string) is defined for this
236209962Smm	ds (Dataset)."""
237209962Smm	# It would be nice to cache the result of get_fsacl().
238209962Smm	for raw in ds.get_fsacl().values():
239209962Smm		for whokey in raw.keys():
240209962Smm			if whokey[0].lower() == "s" and whokey[3:] == setname:
241209962Smm				return True
242209962Smm	return False
243209962Smm
244209962Smmdef canonicalized_perm(permname):
245209962Smm	"""Return the canonical name (string) for this permission (string).
246209962Smm	Raises ZFSError if it is not a valid permission."""
247209962Smm	if permname in perms_subcmd.keys() or permname in perms_other.keys():
248209962Smm		return permname
249209962Smm	try:
250209962Smm		return zfs.dataset.getpropobj(permname).name
251209962Smm	except KeyError:
252209962Smm		raise zfs.util.ZFSError(errno.EINVAL, permname,
253209962Smm		    _("invalid permission"))
254209962Smm
255209962Smmdef print_perms():
256209962Smm	"""Print the set of supported permissions."""
257209962Smm	print(_("\nThe following permissions are supported:\n"))
258209962Smm	fmt = "%-16s %-14s\t%s"
259209962Smm	print(fmt % (_("NAME"), _("TYPE"), _("NOTES")))
260209962Smm
261209962Smm	for (name, note) in sorted(perms_subcmd.iteritems()):
262209962Smm		print(fmt % (name, _("subcommand"), note))
263209962Smm
264209962Smm	for (name, note) in sorted(perms_other.iteritems()):
265209962Smm		print(fmt % (name, _("other"), note))
266209962Smm
267209962Smm	for (name, prop) in sorted(zfs.dataset.proptable.iteritems()):
268209962Smm		if prop.visible and prop.delegatable():
269209962Smm			print(fmt % (name, _("property"), ""))
270209962Smm
271209962Smmdef do_allow():
272219089Spjd	"""Implements the "zfs allow" and "zfs unallow" subcommands."""
273209962Smm	un = (sys.argv[1] == "unallow")
274209962Smm
275209962Smm	def usage(msg=None):
276209962Smm		parser.print_help()
277209962Smm		print_perms()
278209962Smm		if msg:
279209962Smm			print
280209962Smm			parser.exit("zfs: error: " + msg)
281209962Smm		else:
282209962Smm			parser.exit()
283209962Smm
284209962Smm	if un:
285209962Smm		u = _("""unallow [-rldug] <"everyone"|user|group>[,...]
286209962Smm	    [<perm|@setname>[,...]] <filesystem|volume>
287209962Smm	unallow [-rld] -e [<perm|@setname>[,...]] <filesystem|volume>
288209962Smm	unallow [-r] -c [<perm|@setname>[,...]] <filesystem|volume>
289209962Smm	unallow [-r] -s @setname [<perm|@setname>[,...]] <filesystem|volume>""")
290209962Smm		verb = _("remove")
291209962Smm		sstr = _("undefine permission set")
292209962Smm	else:
293209962Smm		u = _("""allow <filesystem|volume>
294209962Smm	allow [-ldug] <"everyone"|user|group>[,...] <perm|@setname>[,...]
295209962Smm	    <filesystem|volume>
296209962Smm	allow [-ld] -e <perm|@setname>[,...] <filesystem|volume>
297209962Smm	allow -c <perm|@setname>[,...] <filesystem|volume>
298209962Smm	allow -s @setname <perm|@setname>[,...] <filesystem|volume>""")
299209962Smm		verb = _("set")
300209962Smm		sstr = _("define permission set")
301209962Smm
302209962Smm	parser = optparse.OptionParser(usage=u, prog="zfs")
303209962Smm
304209962Smm	parser.add_option("-l", action="store_true", dest="local",
305209962Smm	    help=_("%s permission locally") % verb)
306209962Smm	parser.add_option("-d", action="store_true", dest="descend",
307209962Smm	    help=_("%s permission for descendents") % verb)
308209962Smm	parser.add_option("-u", action="store_true", dest="user",
309209962Smm	    help=_("%s permission for user") % verb)
310209962Smm	parser.add_option("-g", action="store_true", dest="group",
311209962Smm	    help=_("%s permission for group") % verb)
312209962Smm	parser.add_option("-e", action="store_true", dest="everyone",
313209962Smm	    help=_("%s permission for everyone") % verb)
314209962Smm	parser.add_option("-c", action="store_true", dest="create",
315209962Smm	    help=_("%s create time permissions") % verb)
316209962Smm	parser.add_option("-s", action="store_true", dest="set", help=sstr)
317209962Smm	if un:
318209962Smm		parser.add_option("-r", action="store_true", dest="recursive",
319209962Smm		    help=_("remove permissions recursively"))
320209962Smm
321209962Smm	if len(sys.argv) == 3 and not un:
322209962Smm		# just print the permissions on this fs
323209962Smm
324209962Smm		if sys.argv[2] == "-h":
325209962Smm			# hack to make "zfs allow -h" work
326209962Smm			usage()
327219089Spjd		ds = zfs.dataset.Dataset(sys.argv[2], snaps=False)
328209962Smm
329209962Smm		p = dict()
330209962Smm		for (fs, raw) in ds.get_fsacl().items():
331209962Smm			p[fs] = FSPerms(raw)
332209962Smm
333209962Smm		for fs in sorted(p.keys(), reverse=True):
334209962Smm			s = _("---- Permissions on %s ") % fs
335209962Smm			print(s + "-" * (70-len(s)))
336209962Smm			print(p[fs])
337209962Smm		return
338209962Smm
339209962Smm
340209962Smm	(options, args) = parser.parse_args(sys.argv[2:])
341209962Smm
342209962Smm	if sum((bool(options.everyone), bool(options.user),
343209962Smm	    bool(options.group))) > 1:
344209962Smm		parser.error(_("-u, -g, and -e are mutually exclusive"))
345209962Smm
346209962Smm	def mungeargs(expected_len):
347209962Smm		if un and len(args) == expected_len-1:
348209962Smm			return (None, args[expected_len-2])
349209962Smm		elif len(args) == expected_len:
350209962Smm			return (args[expected_len-2].split(","),
351209962Smm			    args[expected_len-1])
352209962Smm		else:
353209962Smm			usage(_("wrong number of parameters"))
354209962Smm
355209962Smm	if options.set:
356209962Smm		if options.local or options.descend or options.user or \
357209962Smm		    options.group or options.everyone or options.create:
358209962Smm			parser.error(_("invalid option combined with -s"))
359209962Smm		if args[0][0] != "@":
360209962Smm			parser.error(_("invalid set name: missing '@' prefix"))
361209962Smm
362209962Smm		(perms, fsname) = mungeargs(3)
363209962Smm		who = args[0]
364209962Smm	elif options.create:
365209962Smm		if options.local or options.descend or options.user or \
366209962Smm		    options.group or options.everyone or options.set:
367209962Smm			parser.error(_("invalid option combined with -c"))
368209962Smm
369209962Smm		(perms, fsname) = mungeargs(2)
370209962Smm		who = None
371209962Smm	elif options.everyone:
372209962Smm		if options.user or options.group or \
373209962Smm		    options.create or options.set:
374209962Smm			parser.error(_("invalid option combined with -e"))
375209962Smm
376209962Smm		(perms, fsname) = mungeargs(2)
377209962Smm		who = ["everyone"]
378209962Smm	else:
379209962Smm		(perms, fsname) = mungeargs(3)
380209962Smm		who = args[0].split(",")
381209962Smm
382209962Smm	if not options.local and not options.descend:
383209962Smm		options.local = True
384209962Smm		options.descend = True
385209962Smm
386209962Smm	d = args_to_perms(parser, options, who, perms)
387209962Smm
388209962Smm	ds = zfs.dataset.Dataset(fsname, snaps=False)
389209962Smm
390209962Smm	if not un and perms:
391209962Smm		for p in perms:
392209962Smm			if p[0] == "@" and not hasset(ds, p):
393209962Smm				parser.error(_("set %s is not defined") % p)
394209962Smm
395209962Smm	ds.set_fsacl(un, d)
396209962Smm	if un and options.recursive:
397209962Smm		for child in ds.descendents():
398209962Smm			child.set_fsacl(un, d)
399