]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - cddl/contrib/opensolaris/lib/pyzfs/common/allow.py
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / cddl / contrib / opensolaris / lib / pyzfs / common / allow.py
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 # Copyright (c) 2013 by Delphix. All rights reserved.
24 #
25
26 """This module implements the "zfs allow" and "zfs unallow" subcommands.
27 The only public interface is the zfs.allow.do_allow() function."""
28
29 import zfs.util
30 import zfs.dataset
31 import optparse
32 import sys
33 import pwd
34 import grp
35 import errno
36
37 _ = zfs.util._
38
39 class 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
134 def 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
204 perms_subcmd = dict(
205     create=_("Must also have the 'mount' ability"),
206     destroy=_("Must also have the 'mount' ability"),
207     snapshot="",
208     rollback="",
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     hold=_("Allows adding a user hold to a snapshot"),
221     release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"),
222     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     bookmark="",
224 )
225
226 perms_other = dict(
227     userprop=_("Allows changing any user property"),
228     userquota=_("Allows accessing any userquota@... property"),
229     groupquota=_("Allows accessing any groupquota@... property"),
230     userused=_("Allows reading any userused@... property"),
231     groupused=_("Allows reading any groupused@... property"),
232 )
233
234 def hasset(ds, setname):
235         """Return True if the given setname (string) is defined for this
236         ds (Dataset)."""
237         # It would be nice to cache the result of get_fsacl().
238         for raw in ds.get_fsacl().values():
239                 for whokey in raw.keys():
240                         if whokey[0].lower() == "s" and whokey[3:] == setname:
241                                 return True
242         return False
243
244 def canonicalized_perm(permname):
245         """Return the canonical name (string) for this permission (string).
246         Raises ZFSError if it is not a valid permission."""
247         if permname in perms_subcmd.keys() or permname in perms_other.keys():
248                 return permname
249         try:
250                 return zfs.dataset.getpropobj(permname).name
251         except KeyError:
252                 raise zfs.util.ZFSError(errno.EINVAL, permname,
253                     _("invalid permission"))
254                 
255 def print_perms():
256         """Print the set of supported permissions."""
257         print(_("\nThe following permissions are supported:\n"))
258         fmt = "%-16s %-14s\t%s"
259         print(fmt % (_("NAME"), _("TYPE"), _("NOTES")))
260
261         for (name, note) in sorted(perms_subcmd.iteritems()):
262                 print(fmt % (name, _("subcommand"), note))
263
264         for (name, note) in sorted(perms_other.iteritems()):
265                 print(fmt % (name, _("other"), note))
266
267         for (name, prop) in sorted(zfs.dataset.proptable.iteritems()):
268                 if prop.visible and prop.delegatable():
269                         print(fmt % (name, _("property"), ""))
270
271 def do_allow():
272         """Implements the "zfs allow" and "zfs unallow" subcommands."""
273         un = (sys.argv[1] == "unallow")
274
275         def usage(msg=None):
276                 parser.print_help()
277                 print_perms()
278                 if msg:
279                         print
280                         parser.exit("zfs: error: " + msg)
281                 else:
282                         parser.exit()
283
284         if un:
285                 u = _("""unallow [-rldug] <"everyone"|user|group>[,...]
286             [<perm|@setname>[,...]] <filesystem|volume>
287         unallow [-rld] -e [<perm|@setname>[,...]] <filesystem|volume>
288         unallow [-r] -c [<perm|@setname>[,...]] <filesystem|volume>
289         unallow [-r] -s @setname [<perm|@setname>[,...]] <filesystem|volume>""")
290                 verb = _("remove")
291                 sstr = _("undefine permission set")
292         else:
293                 u = _("""allow <filesystem|volume>
294         allow [-ldug] <"everyone"|user|group>[,...] <perm|@setname>[,...]
295             <filesystem|volume>
296         allow [-ld] -e <perm|@setname>[,...] <filesystem|volume>
297         allow -c <perm|@setname>[,...] <filesystem|volume>
298         allow -s @setname <perm|@setname>[,...] <filesystem|volume>""")
299                 verb = _("set")
300                 sstr = _("define permission set")
301
302         parser = optparse.OptionParser(usage=u, prog="zfs")
303
304         parser.add_option("-l", action="store_true", dest="local",
305             help=_("%s permission locally") % verb)
306         parser.add_option("-d", action="store_true", dest="descend",
307             help=_("%s permission for descendents") % verb)
308         parser.add_option("-u", action="store_true", dest="user",
309             help=_("%s permission for user") % verb)
310         parser.add_option("-g", action="store_true", dest="group",
311             help=_("%s permission for group") % verb)
312         parser.add_option("-e", action="store_true", dest="everyone",
313             help=_("%s permission for everyone") % verb)
314         parser.add_option("-c", action="store_true", dest="create",
315             help=_("%s create time permissions") % verb)
316         parser.add_option("-s", action="store_true", dest="set", help=sstr)
317         if un:
318                 parser.add_option("-r", action="store_true", dest="recursive",
319                     help=_("remove permissions recursively"))
320
321         if len(sys.argv) == 3 and not un:
322                 # just print the permissions on this fs
323
324                 if sys.argv[2] == "-h":
325                         # hack to make "zfs allow -h" work
326                         usage()
327                 ds = zfs.dataset.Dataset(sys.argv[2], snaps=False)
328
329                 p = dict()
330                 for (fs, raw) in ds.get_fsacl().items():
331                         p[fs] = FSPerms(raw)
332
333                 for fs in sorted(p.keys(), reverse=True):
334                         s = _("---- Permissions on %s ") % fs
335                         print(s + "-" * (70-len(s)))
336                         print(p[fs])
337                 return
338         
339
340         (options, args) = parser.parse_args(sys.argv[2:])
341
342         if sum((bool(options.everyone), bool(options.user),
343             bool(options.group))) > 1:
344                 parser.error(_("-u, -g, and -e are mutually exclusive"))
345
346         def mungeargs(expected_len):
347                 if un and len(args) == expected_len-1:
348                         return (None, args[expected_len-2])
349                 elif len(args) == expected_len:
350                         return (args[expected_len-2].split(","),
351                             args[expected_len-1])
352                 else:
353                         usage(_("wrong number of parameters"))
354
355         if options.set:
356                 if options.local or options.descend or options.user or \
357                     options.group or options.everyone or options.create:
358                         parser.error(_("invalid option combined with -s"))
359                 if args[0][0] != "@":
360                         parser.error(_("invalid set name: missing '@' prefix"))
361
362                 (perms, fsname) = mungeargs(3)
363                 who = args[0]
364         elif options.create:
365                 if options.local or options.descend or options.user or \
366                     options.group or options.everyone or options.set:
367                         parser.error(_("invalid option combined with -c"))
368
369                 (perms, fsname) = mungeargs(2)
370                 who = None
371         elif options.everyone:
372                 if options.user or options.group or \
373                     options.create or options.set:
374                         parser.error(_("invalid option combined with -e"))
375
376                 (perms, fsname) = mungeargs(2)
377                 who = ["everyone"]
378         else:
379                 (perms, fsname) = mungeargs(3)
380                 who = args[0].split(",")
381
382         if not options.local and not options.descend:
383                 options.local = True
384                 options.descend = True
385
386         d = args_to_perms(parser, options, who, perms)
387
388         ds = zfs.dataset.Dataset(fsname, snaps=False)
389
390         if not un and perms:
391                 for p in perms:
392                         if p[0] == "@" and not hasset(ds, p):
393                                 parser.error(_("set %s is not defined") % p)
394
395         ds.set_fsacl(un, d)
396         if un and options.recursive:
397                 for child in ds.descendents():
398                         child.set_fsacl(un, d)