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