xref: /illumos-gate/usr/src/lib/pyzfs/common/allow.py (revision 6d52f363e3b2c0c5da672c5b8c8adec99d345f38)
1#! /usr/bin/python2.6
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23#
24
25"""This module implements the "zfs allow" and "zfs unallow" subcommands.
26The only public interface is the zfs.allow.do_allow() function."""
27
28import zfs.util
29import zfs.dataset
30import optparse
31import sys
32import pwd
33import grp
34import errno
35
36_ = zfs.util._
37
38class FSPerms(object):
39	"""This class represents all the permissions that are set on a
40	particular filesystem (not including those inherited)."""
41
42	__slots__ = "create", "sets", "local", "descend", "ld"
43	__repr__ = zfs.util.default_repr
44
45	def __init__(self, raw):
46		"""Create a FSPerms based on the dict of raw permissions
47		from zfs.ioctl.get_fsacl()."""
48		# set of perms
49		self.create = set()
50
51		# below are { "Ntype name": set(perms) }
52		# where N is a number that we just use for sorting,
53		# type is "user", "group", "everyone", or "" (for sets)
54		# name is a user, group, or set name, or "" (for everyone)
55		self.sets = dict()
56		self.local = dict()
57		self.descend = dict()
58		self.ld = dict()
59
60		# see the comment in dsl_deleg.c for the definition of whokey
61		for whokey in raw.keys():
62			perms = raw[whokey].keys()
63			whotypechr = whokey[0].lower()
64			ws = whokey[3:]
65			if whotypechr == "c":
66				self.create.update(perms)
67			elif whotypechr == "s":
68				nwho = "1" + ws
69				self.sets.setdefault(nwho, set()).update(perms)
70			else:
71				if whotypechr == "u":
72					try:
73						name = pwd.getpwuid(int(ws)).pw_name
74					except KeyError:
75						name = ws
76					nwho = "1user " + name
77				elif whotypechr == "g":
78					try:
79						name = grp.getgrgid(int(ws)).gr_name
80					except KeyError:
81						name = ws
82					nwho = "2group " + name
83				elif whotypechr == "e":
84					nwho = "3everyone"
85				else:
86					raise ValueError(whotypechr)
87
88				if whokey[1] == "l":
89					d = self.local
90				elif whokey[1] == "d":
91					d = self.descend
92				else:
93					raise ValueError(whokey[1])
94
95				d.setdefault(nwho, set()).update(perms)
96
97		# Find perms that are in both local and descend, and
98		# move them to ld.
99		for nwho in self.local:
100			if nwho not in self.descend:
101				continue
102			# note: these are set operations
103			self.ld[nwho] = self.local[nwho] & self.descend[nwho]
104			self.local[nwho] -= self.ld[nwho]
105			self.descend[nwho] -= self.ld[nwho]
106
107	@staticmethod
108	def __ldstr(d, header):
109		s = ""
110		for (nwho, perms) in sorted(d.items()):
111			# local and descend may have entries where perms
112			# is an empty set, due to consolidating all
113			# permissions into ld
114			if perms:
115				s += "\t%s %s\n" % \
116				    (nwho[1:], ",".join(sorted(perms)))
117		if s:
118			s = header + s
119		return s
120
121	def __str__(self):
122		s = self.__ldstr(self.sets, _("Permission sets:\n"))
123
124		if self.create:
125			s += _("Create time permissions:\n")
126			s += "\t%s\n" % ",".join(sorted(self.create))
127
128		s += self.__ldstr(self.local, _("Local permissions:\n"))
129		s += self.__ldstr(self.descend, _("Descendent permissions:\n"))
130		s += self.__ldstr(self.ld, _("Local+Descendent permissions:\n"))
131		return s.rstrip()
132
133def args_to_perms(parser, options, who, perms):
134	"""Return a dict of raw perms {"whostr" -> {"perm" -> None}}
135	based on the command-line input."""
136
137	# perms is not set if we are doing a "zfs unallow <who> <fs>" to
138	# remove all of someone's permissions
139	if perms:
140		setperms = dict(((p, None) for p in perms if p[0] == "@"))
141		baseperms = dict(((canonicalized_perm(p), None)
142		    for p in perms if p[0] != "@"))
143	else:
144		setperms = None
145		baseperms = None
146
147	d = dict()
148
149	def storeperm(typechr, inheritchr, arg):
150		assert typechr in "ugecs"
151		assert inheritchr in "ld-"
152
153		def mkwhokey(t):
154			return "%c%c$%s" % (t, inheritchr, arg)
155
156		if baseperms or not perms:
157			d[mkwhokey(typechr)] = baseperms
158		if setperms or not perms:
159			d[mkwhokey(typechr.upper())] = setperms
160
161	def decodeid(w, toidfunc, fmt):
162		try:
163			return int(w)
164		except ValueError:
165			try:
166				return toidfunc(w)[2]
167			except KeyError:
168				parser.error(fmt % w)
169
170	if options.set:
171		storeperm("s", "-", who)
172	elif options.create:
173		storeperm("c", "-", "")
174	else:
175		for w in who:
176			if options.user:
177				id = decodeid(w, pwd.getpwnam,
178				    _("invalid user %s"))
179				typechr = "u"
180			elif options.group:
181				id = decodeid(w, grp.getgrnam,
182				    _("invalid group %s"))
183				typechr = "g"
184			elif w == "everyone":
185				id = ""
186				typechr = "e"
187			else:
188				try:
189					id = pwd.getpwnam(w)[2]
190					typechr = "u"
191				except KeyError:
192					try:
193						id = grp.getgrnam(w)[2]
194						typechr = "g"
195					except KeyError:
196						parser.error(_("invalid user/group %s") % w)
197			if options.local:
198				storeperm(typechr, "l", id)
199			if options.descend:
200				storeperm(typechr, "d", id)
201	return d
202
203perms_subcmd = dict(
204    create=_("Must also have the 'mount' ability"),
205    destroy=_("Must also have the 'mount' ability"),
206    snapshot="",
207    rollback="",
208    clone=_("""Must also have the 'create' ability and 'mount'
209\t\t\t\tability in the origin file system"""),
210    promote=_("""Must also have the 'mount'
211\t\t\t\tand 'promote' ability in the origin file system"""),
212    rename=_("""Must also have the 'mount' and 'create'
213\t\t\t\tability in the new parent"""),
214    receive=_("Must also have the 'mount' and 'create' ability"),
215    allow=_("Must also have the permission that is being\n\t\t\t\tallowed"),
216    mount=_("Allows mount/umount of ZFS datasets"),
217    share=_("Allows sharing file systems over NFS or SMB\n\t\t\t\tprotocols"),
218    send="",
219    hold=_("Allows adding a user hold to a snapshot"),
220    release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"),
221)
222
223perms_other = dict(
224    userprop=_("Allows changing any user property"),
225    userquota=_("Allows accessing any userquota@... property"),
226    groupquota=_("Allows accessing any groupquota@... property"),
227    userused=_("Allows reading any userused@... property"),
228    groupused=_("Allows reading any groupused@... property"),
229)
230
231def hasset(ds, setname):
232	"""Return True if the given setname (string) is defined for this
233	ds (Dataset)."""
234	# It would be nice to cache the result of get_fsacl().
235	for raw in ds.get_fsacl().values():
236		for whokey in raw.keys():
237			if whokey[0].lower() == "s" and whokey[3:] == setname:
238				return True
239	return False
240
241def canonicalized_perm(permname):
242	"""Return the canonical name (string) for this permission (string).
243	Raises ZFSError if it is not a valid permission."""
244	if permname in perms_subcmd.keys() or permname in perms_other.keys():
245		return permname
246	try:
247		return zfs.dataset.getpropobj(permname).name
248	except KeyError:
249		raise zfs.util.ZFSError(errno.EINVAL, permname,
250		    _("invalid permission"))
251
252def print_perms():
253	"""Print the set of supported permissions."""
254	print(_("\nThe following permissions are supported:\n"))
255	fmt = "%-16s %-14s\t%s"
256	print(fmt % (_("NAME"), _("TYPE"), _("NOTES")))
257
258	for (name, note) in sorted(perms_subcmd.iteritems()):
259		print(fmt % (name, _("subcommand"), note))
260
261	for (name, note) in sorted(perms_other.iteritems()):
262		print(fmt % (name, _("other"), note))
263
264	for (name, prop) in sorted(zfs.dataset.proptable.iteritems()):
265		if prop.visible and prop.delegatable():
266			print(fmt % (name, _("property"), ""))
267
268def do_allow():
269	"""Implements the "zfs allow" and "zfs unallow" subcommands."""
270	un = (sys.argv[1] == "unallow")
271
272	def usage(msg=None):
273		parser.print_help()
274		print_perms()
275		if msg:
276			print
277			parser.exit("zfs: error: " + msg)
278		else:
279			parser.exit()
280
281	if un:
282		u = _("""unallow [-rldug] <"everyone"|user|group>[,...]
283	    [<perm|@setname>[,...]] <filesystem|volume>
284	unallow [-rld] -e [<perm|@setname>[,...]] <filesystem|volume>
285	unallow [-r] -c [<perm|@setname>[,...]] <filesystem|volume>
286	unallow [-r] -s @setname [<perm|@setname>[,...]] <filesystem|volume>""")
287		verb = _("remove")
288		sstr = _("undefine permission set")
289	else:
290		u = _("""allow <filesystem|volume>
291	allow [-ldug] <"everyone"|user|group>[,...] <perm|@setname>[,...]
292	    <filesystem|volume>
293	allow [-ld] -e <perm|@setname>[,...] <filesystem|volume>
294	allow -c <perm|@setname>[,...] <filesystem|volume>
295	allow -s @setname <perm|@setname>[,...] <filesystem|volume>""")
296		verb = _("set")
297		sstr = _("define permission set")
298
299	parser = optparse.OptionParser(usage=u, prog="zfs")
300
301	parser.add_option("-l", action="store_true", dest="local",
302	    help=_("%s permission locally") % verb)
303	parser.add_option("-d", action="store_true", dest="descend",
304	    help=_("%s permission for descendents") % verb)
305	parser.add_option("-u", action="store_true", dest="user",
306	    help=_("%s permission for user") % verb)
307	parser.add_option("-g", action="store_true", dest="group",
308	    help=_("%s permission for group") % verb)
309	parser.add_option("-e", action="store_true", dest="everyone",
310	    help=_("%s permission for everyone") % verb)
311	parser.add_option("-c", action="store_true", dest="create",
312	    help=_("%s create time permissions") % verb)
313	parser.add_option("-s", action="store_true", dest="set", help=sstr)
314	if un:
315		parser.add_option("-r", action="store_true", dest="recursive",
316		    help=_("remove permissions recursively"))
317
318	if len(sys.argv) == 3 and not un:
319		# just print the permissions on this fs
320
321		if sys.argv[2] == "-h":
322			# hack to make "zfs allow -h" work
323			usage()
324		ds = zfs.dataset.Dataset(sys.argv[2], snaps=False)
325
326		p = dict()
327		for (fs, raw) in ds.get_fsacl().items():
328			p[fs] = FSPerms(raw)
329
330		for fs in sorted(p.keys(), reverse=True):
331			s = _("---- Permissions on %s ") % fs
332			print(s + "-" * (70-len(s)))
333			print(p[fs])
334		return
335
336
337	(options, args) = parser.parse_args(sys.argv[2:])
338
339	if sum((bool(options.everyone), bool(options.user),
340	    bool(options.group))) > 1:
341		parser.error(_("-u, -g, and -e are mutually exclusive"))
342
343	def mungeargs(expected_len):
344		if un and len(args) == expected_len-1:
345			return (None, args[expected_len-2])
346		elif len(args) == expected_len:
347			return (args[expected_len-2].split(","),
348			    args[expected_len-1])
349		else:
350			usage(_("wrong number of parameters"))
351
352	if options.set:
353		if options.local or options.descend or options.user or \
354		    options.group or options.everyone or options.create:
355			parser.error(_("invalid option combined with -s"))
356		if args[0][0] != "@":
357			parser.error(_("invalid set name: missing '@' prefix"))
358
359		(perms, fsname) = mungeargs(3)
360		who = args[0]
361	elif options.create:
362		if options.local or options.descend or options.user or \
363		    options.group or options.everyone or options.set:
364			parser.error(_("invalid option combined with -c"))
365
366		(perms, fsname) = mungeargs(2)
367		who = None
368	elif options.everyone:
369		if options.user or options.group or \
370		    options.create or options.set:
371			parser.error(_("invalid option combined with -e"))
372
373		(perms, fsname) = mungeargs(2)
374		who = ["everyone"]
375	else:
376		(perms, fsname) = mungeargs(3)
377		who = args[0].split(",")
378
379	if not options.local and not options.descend:
380		options.local = True
381		options.descend = True
382
383	d = args_to_perms(parser, options, who, perms)
384
385	ds = zfs.dataset.Dataset(fsname, snaps=False)
386
387	if not un and perms:
388		for p in perms:
389			if p[0] == "@" and not hasset(ds, p):
390				parser.error(_("set %s is not defined") % p)
391
392	ds.set_fsacl(un, d)
393	if un and options.recursive:
394		for child in ds.descendents():
395			child.set_fsacl(un, d)
396