#! /usr/bin/python2.6 # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2013 by Delphix. All rights reserved. # """This module implements the "zfs allow" and "zfs unallow" subcommands. The only public interface is the zfs.allow.do_allow() function.""" import zfs.util import zfs.dataset import optparse import sys import pwd import grp import errno _ = zfs.util._ class FSPerms(object): """This class represents all the permissions that are set on a particular filesystem (not including those inherited).""" __slots__ = "create", "sets", "local", "descend", "ld" __repr__ = zfs.util.default_repr def __init__(self, raw): """Create a FSPerms based on the dict of raw permissions from zfs.ioctl.get_fsacl().""" # set of perms self.create = set() # below are { "Ntype name": set(perms) } # where N is a number that we just use for sorting, # type is "user", "group", "everyone", or "" (for sets) # name is a user, group, or set name, or "" (for everyone) self.sets = dict() self.local = dict() self.descend = dict() self.ld = dict() # see the comment in dsl_deleg.c for the definition of whokey for whokey in raw.keys(): perms = raw[whokey].keys() whotypechr = whokey[0].lower() ws = whokey[3:] if whotypechr == "c": self.create.update(perms) elif whotypechr == "s": nwho = "1" + ws self.sets.setdefault(nwho, set()).update(perms) else: if whotypechr == "u": try: name = pwd.getpwuid(int(ws)).pw_name except KeyError: name = ws nwho = "1user " + name elif whotypechr == "g": try: name = grp.getgrgid(int(ws)).gr_name except KeyError: name = ws nwho = "2group " + name elif whotypechr == "e": nwho = "3everyone" else: raise ValueError(whotypechr) if whokey[1] == "l": d = self.local elif whokey[1] == "d": d = self.descend else: raise ValueError(whokey[1]) d.setdefault(nwho, set()).update(perms) # Find perms that are in both local and descend, and # move them to ld. for nwho in self.local: if nwho not in self.descend: continue # note: these are set operations self.ld[nwho] = self.local[nwho] & self.descend[nwho] self.local[nwho] -= self.ld[nwho] self.descend[nwho] -= self.ld[nwho] @staticmethod def __ldstr(d, header): s = "" for (nwho, perms) in sorted(d.items()): # local and descend may have entries where perms # is an empty set, due to consolidating all # permissions into ld if perms: s += "\t%s %s\n" % \ (nwho[1:], ",".join(sorted(perms))) if s: s = header + s return s def __str__(self): s = self.__ldstr(self.sets, _("Permission sets:\n")) if self.create: s += _("Create time permissions:\n") s += "\t%s\n" % ",".join(sorted(self.create)) s += self.__ldstr(self.local, _("Local permissions:\n")) s += self.__ldstr(self.descend, _("Descendent permissions:\n")) s += self.__ldstr(self.ld, _("Local+Descendent permissions:\n")) return s.rstrip() def args_to_perms(parser, options, who, perms): """Return a dict of raw perms {"whostr" -> {"perm" -> None}} based on the command-line input.""" # perms is not set if we are doing a "zfs unallow " to # remove all of someone's permissions if perms: setperms = dict(((p, None) for p in perms if p[0] == "@")) baseperms = dict(((canonicalized_perm(p), None) for p in perms if p[0] != "@")) else: setperms = None baseperms = None d = dict() def storeperm(typechr, inheritchr, arg): assert typechr in "ugecs" assert inheritchr in "ld-" def mkwhokey(t): return "%c%c$%s" % (t, inheritchr, arg) if baseperms or not perms: d[mkwhokey(typechr)] = baseperms if setperms or not perms: d[mkwhokey(typechr.upper())] = setperms def decodeid(w, toidfunc, fmt): try: return int(w) except ValueError: try: return toidfunc(w)[2] except KeyError: parser.error(fmt % w) if options.set: storeperm("s", "-", who) elif options.create: storeperm("c", "-", "") else: for w in who: if options.user: id = decodeid(w, pwd.getpwnam, _("invalid user %s")) typechr = "u" elif options.group: id = decodeid(w, grp.getgrnam, _("invalid group %s")) typechr = "g" elif w == "everyone": id = "" typechr = "e" else: try: id = pwd.getpwnam(w)[2] typechr = "u" except KeyError: try: id = grp.getgrnam(w)[2] typechr = "g" except KeyError: parser.error(_("invalid user/group %s") % w) if options.local: storeperm(typechr, "l", id) if options.descend: storeperm(typechr, "d", id) return d perms_subcmd = dict( create=_("Must also have the 'mount' ability"), destroy=_("Must also have the 'mount' ability"), snapshot="", rollback="", clone=_("""Must also have the 'create' ability and 'mount' \t\t\t\tability in the origin file system"""), promote=_("""Must also have the 'mount' \t\t\t\tand 'promote' ability in the origin file system"""), rename=_("""Must also have the 'mount' and 'create' \t\t\t\tability in the new parent"""), receive=_("Must also have the 'mount' and 'create' ability"), allow=_("Must also have the permission that is being\n\t\t\t\tallowed"), mount=_("Allows mount/umount of ZFS datasets"), share=_("Allows sharing file systems over NFS or SMB\n\t\t\t\tprotocols"), send="", hold=_("Allows adding a user hold to a snapshot"), release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"), 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"), bookmark="", ) perms_other = dict( userprop=_("Allows changing any user property"), userquota=_("Allows accessing any userquota@... property"), groupquota=_("Allows accessing any groupquota@... property"), userused=_("Allows reading any userused@... property"), groupused=_("Allows reading any groupused@... property"), ) def hasset(ds, setname): """Return True if the given setname (string) is defined for this ds (Dataset).""" # It would be nice to cache the result of get_fsacl(). for raw in ds.get_fsacl().values(): for whokey in raw.keys(): if whokey[0].lower() == "s" and whokey[3:] == setname: return True return False def canonicalized_perm(permname): """Return the canonical name (string) for this permission (string). Raises ZFSError if it is not a valid permission.""" if permname in perms_subcmd.keys() or permname in perms_other.keys(): return permname try: return zfs.dataset.getpropobj(permname).name except KeyError: raise zfs.util.ZFSError(errno.EINVAL, permname, _("invalid permission")) def print_perms(): """Print the set of supported permissions.""" print(_("\nThe following permissions are supported:\n")) fmt = "%-16s %-14s\t%s" print(fmt % (_("NAME"), _("TYPE"), _("NOTES"))) for (name, note) in sorted(perms_subcmd.iteritems()): print(fmt % (name, _("subcommand"), note)) for (name, note) in sorted(perms_other.iteritems()): print(fmt % (name, _("other"), note)) for (name, prop) in sorted(zfs.dataset.proptable.iteritems()): if prop.visible and prop.delegatable(): print(fmt % (name, _("property"), "")) def do_allow(): """Implements the "zfs allow" and "zfs unallow" subcommands.""" un = (sys.argv[1] == "unallow") def usage(msg=None): parser.print_help() print_perms() if msg: print parser.exit("zfs: error: " + msg) else: parser.exit() if un: u = _("""unallow [-rldug] <"everyone"|user|group>[,...] [[,...]] unallow [-rld] -e [[,...]] unallow [-r] -c [[,...]] unallow [-r] -s @setname [[,...]] """) verb = _("remove") sstr = _("undefine permission set") else: u = _("""allow allow [-ldug] <"everyone"|user|group>[,...] [,...] allow [-ld] -e [,...] allow -c [,...] allow -s @setname [,...] """) verb = _("set") sstr = _("define permission set") parser = optparse.OptionParser(usage=u, prog="zfs") parser.add_option("-l", action="store_true", dest="local", help=_("%s permission locally") % verb) parser.add_option("-d", action="store_true", dest="descend", help=_("%s permission for descendents") % verb) parser.add_option("-u", action="store_true", dest="user", help=_("%s permission for user") % verb) parser.add_option("-g", action="store_true", dest="group", help=_("%s permission for group") % verb) parser.add_option("-e", action="store_true", dest="everyone", help=_("%s permission for everyone") % verb) parser.add_option("-c", action="store_true", dest="create", help=_("%s create time permissions") % verb) parser.add_option("-s", action="store_true", dest="set", help=sstr) if un: parser.add_option("-r", action="store_true", dest="recursive", help=_("remove permissions recursively")) if len(sys.argv) == 3 and not un: # just print the permissions on this fs if sys.argv[2] == "-h": # hack to make "zfs allow -h" work usage() ds = zfs.dataset.Dataset(sys.argv[2], snaps=False) p = dict() for (fs, raw) in ds.get_fsacl().items(): p[fs] = FSPerms(raw) for fs in sorted(p.keys(), reverse=True): s = _("---- Permissions on %s ") % fs print(s + "-" * (70-len(s))) print(p[fs]) return (options, args) = parser.parse_args(sys.argv[2:]) if sum((bool(options.everyone), bool(options.user), bool(options.group))) > 1: parser.error(_("-u, -g, and -e are mutually exclusive")) def mungeargs(expected_len): if un and len(args) == expected_len-1: return (None, args[expected_len-2]) elif len(args) == expected_len: return (args[expected_len-2].split(","), args[expected_len-1]) else: usage(_("wrong number of parameters")) if options.set: if options.local or options.descend or options.user or \ options.group or options.everyone or options.create: parser.error(_("invalid option combined with -s")) if args[0][0] != "@": parser.error(_("invalid set name: missing '@' prefix")) (perms, fsname) = mungeargs(3) who = args[0] elif options.create: if options.local or options.descend or options.user or \ options.group or options.everyone or options.set: parser.error(_("invalid option combined with -c")) (perms, fsname) = mungeargs(2) who = None elif options.everyone: if options.user or options.group or \ options.create or options.set: parser.error(_("invalid option combined with -e")) (perms, fsname) = mungeargs(2) who = ["everyone"] else: (perms, fsname) = mungeargs(3) who = args[0].split(",") if not options.local and not options.descend: options.local = True options.descend = True d = args_to_perms(parser, options, who, perms) ds = zfs.dataset.Dataset(fsname, snaps=False) if not un and perms: for p in perms: if p[0] == "@" and not hasset(ds, p): parser.error(_("set %s is not defined") % p) ds.set_fsacl(un, d) if un and options.recursive: for child in ds.descendents(): child.set_fsacl(un, d)