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