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