1219089Spjd#! /usr/bin/python2.6 2209962Smm# 3209962Smm# CDDL HEADER START 4209962Smm# 5209962Smm# The contents of this file are subject to the terms of the 6209962Smm# Common Development and Distribution License (the "License"). 7209962Smm# You may not use this file except in compliance with the License. 8209962Smm# 9209962Smm# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10209962Smm# or http://www.opensolaris.org/os/licensing. 11209962Smm# See the License for the specific language governing permissions 12209962Smm# and limitations under the License. 13209962Smm# 14209962Smm# When distributing Covered Code, include this CDDL HEADER in each 15209962Smm# file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16209962Smm# If applicable, add the following below this CDDL HEADER, with the 17209962Smm# fields enclosed by brackets "[]" replaced with your own identifying 18209962Smm# information: Portions Copyright [yyyy] [name of copyright owner] 19209962Smm# 20209962Smm# CDDL HEADER END 21209962Smm# 22219089Spjd# Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. 23263407Sdelphij# Copyright (c) 2013 by Delphix. All rights reserved. 24209962Smm# 25209962Smm 26209962Smm"""This module implements the "zfs allow" and "zfs unallow" subcommands. 27209962SmmThe only public interface is the zfs.allow.do_allow() function.""" 28209962Smm 29209962Smmimport zfs.util 30209962Smmimport zfs.dataset 31209962Smmimport optparse 32209962Smmimport sys 33209962Smmimport pwd 34209962Smmimport grp 35209962Smmimport errno 36209962Smm 37209962Smm_ = zfs.util._ 38209962Smm 39209962Smmclass FSPerms(object): 40209962Smm """This class represents all the permissions that are set on a 41209962Smm particular filesystem (not including those inherited).""" 42209962Smm 43209962Smm __slots__ = "create", "sets", "local", "descend", "ld" 44209962Smm __repr__ = zfs.util.default_repr 45209962Smm 46209962Smm def __init__(self, raw): 47209962Smm """Create a FSPerms based on the dict of raw permissions 48209962Smm from zfs.ioctl.get_fsacl().""" 49209962Smm # set of perms 50209962Smm self.create = set() 51209962Smm 52209962Smm # below are { "Ntype name": set(perms) } 53209962Smm # where N is a number that we just use for sorting, 54209962Smm # type is "user", "group", "everyone", or "" (for sets) 55209962Smm # name is a user, group, or set name, or "" (for everyone) 56209962Smm self.sets = dict() 57209962Smm self.local = dict() 58209962Smm self.descend = dict() 59209962Smm self.ld = dict() 60209962Smm 61209962Smm # see the comment in dsl_deleg.c for the definition of whokey 62209962Smm for whokey in raw.keys(): 63209962Smm perms = raw[whokey].keys() 64209962Smm whotypechr = whokey[0].lower() 65209962Smm ws = whokey[3:] 66209962Smm if whotypechr == "c": 67209962Smm self.create.update(perms) 68209962Smm elif whotypechr == "s": 69209962Smm nwho = "1" + ws 70209962Smm self.sets.setdefault(nwho, set()).update(perms) 71209962Smm else: 72209962Smm if whotypechr == "u": 73209962Smm try: 74209962Smm name = pwd.getpwuid(int(ws)).pw_name 75209962Smm except KeyError: 76209962Smm name = ws 77209962Smm nwho = "1user " + name 78209962Smm elif whotypechr == "g": 79209962Smm try: 80209962Smm name = grp.getgrgid(int(ws)).gr_name 81209962Smm except KeyError: 82209962Smm name = ws 83209962Smm nwho = "2group " + name 84209962Smm elif whotypechr == "e": 85209962Smm nwho = "3everyone" 86209962Smm else: 87209962Smm raise ValueError(whotypechr) 88209962Smm 89209962Smm if whokey[1] == "l": 90209962Smm d = self.local 91209962Smm elif whokey[1] == "d": 92209962Smm d = self.descend 93209962Smm else: 94209962Smm raise ValueError(whokey[1]) 95209962Smm 96209962Smm d.setdefault(nwho, set()).update(perms) 97209962Smm 98209962Smm # Find perms that are in both local and descend, and 99209962Smm # move them to ld. 100209962Smm for nwho in self.local: 101209962Smm if nwho not in self.descend: 102209962Smm continue 103209962Smm # note: these are set operations 104209962Smm self.ld[nwho] = self.local[nwho] & self.descend[nwho] 105209962Smm self.local[nwho] -= self.ld[nwho] 106209962Smm self.descend[nwho] -= self.ld[nwho] 107209962Smm 108209962Smm @staticmethod 109209962Smm def __ldstr(d, header): 110209962Smm s = "" 111209962Smm for (nwho, perms) in sorted(d.items()): 112209962Smm # local and descend may have entries where perms 113209962Smm # is an empty set, due to consolidating all 114209962Smm # permissions into ld 115209962Smm if perms: 116209962Smm s += "\t%s %s\n" % \ 117209962Smm (nwho[1:], ",".join(sorted(perms))) 118209962Smm if s: 119209962Smm s = header + s 120209962Smm return s 121209962Smm 122209962Smm def __str__(self): 123209962Smm s = self.__ldstr(self.sets, _("Permission sets:\n")) 124209962Smm 125209962Smm if self.create: 126209962Smm s += _("Create time permissions:\n") 127209962Smm s += "\t%s\n" % ",".join(sorted(self.create)) 128209962Smm 129209962Smm s += self.__ldstr(self.local, _("Local permissions:\n")) 130209962Smm s += self.__ldstr(self.descend, _("Descendent permissions:\n")) 131209962Smm s += self.__ldstr(self.ld, _("Local+Descendent permissions:\n")) 132209962Smm return s.rstrip() 133209962Smm 134209962Smmdef args_to_perms(parser, options, who, perms): 135209962Smm """Return a dict of raw perms {"whostr" -> {"perm" -> None}} 136209962Smm based on the command-line input.""" 137209962Smm 138209962Smm # perms is not set if we are doing a "zfs unallow <who> <fs>" to 139209962Smm # remove all of someone's permissions 140209962Smm if perms: 141209962Smm setperms = dict(((p, None) for p in perms if p[0] == "@")) 142209962Smm baseperms = dict(((canonicalized_perm(p), None) 143209962Smm for p in perms if p[0] != "@")) 144209962Smm else: 145209962Smm setperms = None 146209962Smm baseperms = None 147209962Smm 148209962Smm d = dict() 149209962Smm 150209962Smm def storeperm(typechr, inheritchr, arg): 151209962Smm assert typechr in "ugecs" 152209962Smm assert inheritchr in "ld-" 153209962Smm 154209962Smm def mkwhokey(t): 155209962Smm return "%c%c$%s" % (t, inheritchr, arg) 156209962Smm 157209962Smm if baseperms or not perms: 158209962Smm d[mkwhokey(typechr)] = baseperms 159209962Smm if setperms or not perms: 160209962Smm d[mkwhokey(typechr.upper())] = setperms 161209962Smm 162209962Smm def decodeid(w, toidfunc, fmt): 163209962Smm try: 164209962Smm return int(w) 165209962Smm except ValueError: 166209962Smm try: 167209962Smm return toidfunc(w)[2] 168209962Smm except KeyError: 169209962Smm parser.error(fmt % w) 170209962Smm 171209962Smm if options.set: 172209962Smm storeperm("s", "-", who) 173209962Smm elif options.create: 174209962Smm storeperm("c", "-", "") 175209962Smm else: 176209962Smm for w in who: 177209962Smm if options.user: 178209962Smm id = decodeid(w, pwd.getpwnam, 179209962Smm _("invalid user %s")) 180209962Smm typechr = "u" 181209962Smm elif options.group: 182209962Smm id = decodeid(w, grp.getgrnam, 183209962Smm _("invalid group %s")) 184209962Smm typechr = "g" 185209962Smm elif w == "everyone": 186209962Smm id = "" 187209962Smm typechr = "e" 188209962Smm else: 189209962Smm try: 190209962Smm id = pwd.getpwnam(w)[2] 191209962Smm typechr = "u" 192209962Smm except KeyError: 193209962Smm try: 194209962Smm id = grp.getgrnam(w)[2] 195209962Smm typechr = "g" 196209962Smm except KeyError: 197209962Smm parser.error(_("invalid user/group %s") % w) 198209962Smm if options.local: 199209962Smm storeperm(typechr, "l", id) 200209962Smm if options.descend: 201209962Smm storeperm(typechr, "d", id) 202209962Smm return d 203209962Smm 204209962Smmperms_subcmd = dict( 205209962Smm create=_("Must also have the 'mount' ability"), 206209962Smm destroy=_("Must also have the 'mount' ability"), 207219089Spjd snapshot="", 208219089Spjd rollback="", 209209962Smm clone=_("""Must also have the 'create' ability and 'mount' 210209962Smm\t\t\t\tability in the origin file system"""), 211209962Smm promote=_("""Must also have the 'mount' 212209962Smm\t\t\t\tand 'promote' ability in the origin file system"""), 213209962Smm rename=_("""Must also have the 'mount' and 'create' 214209962Smm\t\t\t\tability in the new parent"""), 215209962Smm receive=_("Must also have the 'mount' and 'create' ability"), 216209962Smm allow=_("Must also have the permission that is being\n\t\t\t\tallowed"), 217209962Smm mount=_("Allows mount/umount of ZFS datasets"), 218209962Smm share=_("Allows sharing file systems over NFS or SMB\n\t\t\t\tprotocols"), 219209962Smm send="", 220219089Spjd hold=_("Allows adding a user hold to a snapshot"), 221219089Spjd release=_("Allows releasing a user hold which\n\t\t\t\tmight destroy the snapshot"), 222219089Spjd 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"), 223263407Sdelphij bookmark="", 224209962Smm) 225209962Smm 226209962Smmperms_other = dict( 227209962Smm userprop=_("Allows changing any user property"), 228209962Smm userquota=_("Allows accessing any userquota@... property"), 229209962Smm groupquota=_("Allows accessing any groupquota@... property"), 230209962Smm userused=_("Allows reading any userused@... property"), 231209962Smm groupused=_("Allows reading any groupused@... property"), 232209962Smm) 233209962Smm 234209962Smmdef hasset(ds, setname): 235209962Smm """Return True if the given setname (string) is defined for this 236209962Smm ds (Dataset).""" 237209962Smm # It would be nice to cache the result of get_fsacl(). 238209962Smm for raw in ds.get_fsacl().values(): 239209962Smm for whokey in raw.keys(): 240209962Smm if whokey[0].lower() == "s" and whokey[3:] == setname: 241209962Smm return True 242209962Smm return False 243209962Smm 244209962Smmdef canonicalized_perm(permname): 245209962Smm """Return the canonical name (string) for this permission (string). 246209962Smm Raises ZFSError if it is not a valid permission.""" 247209962Smm if permname in perms_subcmd.keys() or permname in perms_other.keys(): 248209962Smm return permname 249209962Smm try: 250209962Smm return zfs.dataset.getpropobj(permname).name 251209962Smm except KeyError: 252209962Smm raise zfs.util.ZFSError(errno.EINVAL, permname, 253209962Smm _("invalid permission")) 254209962Smm 255209962Smmdef print_perms(): 256209962Smm """Print the set of supported permissions.""" 257209962Smm print(_("\nThe following permissions are supported:\n")) 258209962Smm fmt = "%-16s %-14s\t%s" 259209962Smm print(fmt % (_("NAME"), _("TYPE"), _("NOTES"))) 260209962Smm 261209962Smm for (name, note) in sorted(perms_subcmd.iteritems()): 262209962Smm print(fmt % (name, _("subcommand"), note)) 263209962Smm 264209962Smm for (name, note) in sorted(perms_other.iteritems()): 265209962Smm print(fmt % (name, _("other"), note)) 266209962Smm 267209962Smm for (name, prop) in sorted(zfs.dataset.proptable.iteritems()): 268209962Smm if prop.visible and prop.delegatable(): 269209962Smm print(fmt % (name, _("property"), "")) 270209962Smm 271209962Smmdef do_allow(): 272219089Spjd """Implements the "zfs allow" and "zfs unallow" subcommands.""" 273209962Smm un = (sys.argv[1] == "unallow") 274209962Smm 275209962Smm def usage(msg=None): 276209962Smm parser.print_help() 277209962Smm print_perms() 278209962Smm if msg: 279209962Smm print 280209962Smm parser.exit("zfs: error: " + msg) 281209962Smm else: 282209962Smm parser.exit() 283209962Smm 284209962Smm if un: 285209962Smm u = _("""unallow [-rldug] <"everyone"|user|group>[,...] 286209962Smm [<perm|@setname>[,...]] <filesystem|volume> 287209962Smm unallow [-rld] -e [<perm|@setname>[,...]] <filesystem|volume> 288209962Smm unallow [-r] -c [<perm|@setname>[,...]] <filesystem|volume> 289209962Smm unallow [-r] -s @setname [<perm|@setname>[,...]] <filesystem|volume>""") 290209962Smm verb = _("remove") 291209962Smm sstr = _("undefine permission set") 292209962Smm else: 293209962Smm u = _("""allow <filesystem|volume> 294209962Smm allow [-ldug] <"everyone"|user|group>[,...] <perm|@setname>[,...] 295209962Smm <filesystem|volume> 296209962Smm allow [-ld] -e <perm|@setname>[,...] <filesystem|volume> 297209962Smm allow -c <perm|@setname>[,...] <filesystem|volume> 298209962Smm allow -s @setname <perm|@setname>[,...] <filesystem|volume>""") 299209962Smm verb = _("set") 300209962Smm sstr = _("define permission set") 301209962Smm 302209962Smm parser = optparse.OptionParser(usage=u, prog="zfs") 303209962Smm 304209962Smm parser.add_option("-l", action="store_true", dest="local", 305209962Smm help=_("%s permission locally") % verb) 306209962Smm parser.add_option("-d", action="store_true", dest="descend", 307209962Smm help=_("%s permission for descendents") % verb) 308209962Smm parser.add_option("-u", action="store_true", dest="user", 309209962Smm help=_("%s permission for user") % verb) 310209962Smm parser.add_option("-g", action="store_true", dest="group", 311209962Smm help=_("%s permission for group") % verb) 312209962Smm parser.add_option("-e", action="store_true", dest="everyone", 313209962Smm help=_("%s permission for everyone") % verb) 314209962Smm parser.add_option("-c", action="store_true", dest="create", 315209962Smm help=_("%s create time permissions") % verb) 316209962Smm parser.add_option("-s", action="store_true", dest="set", help=sstr) 317209962Smm if un: 318209962Smm parser.add_option("-r", action="store_true", dest="recursive", 319209962Smm help=_("remove permissions recursively")) 320209962Smm 321209962Smm if len(sys.argv) == 3 and not un: 322209962Smm # just print the permissions on this fs 323209962Smm 324209962Smm if sys.argv[2] == "-h": 325209962Smm # hack to make "zfs allow -h" work 326209962Smm usage() 327219089Spjd ds = zfs.dataset.Dataset(sys.argv[2], snaps=False) 328209962Smm 329209962Smm p = dict() 330209962Smm for (fs, raw) in ds.get_fsacl().items(): 331209962Smm p[fs] = FSPerms(raw) 332209962Smm 333209962Smm for fs in sorted(p.keys(), reverse=True): 334209962Smm s = _("---- Permissions on %s ") % fs 335209962Smm print(s + "-" * (70-len(s))) 336209962Smm print(p[fs]) 337209962Smm return 338209962Smm 339209962Smm 340209962Smm (options, args) = parser.parse_args(sys.argv[2:]) 341209962Smm 342209962Smm if sum((bool(options.everyone), bool(options.user), 343209962Smm bool(options.group))) > 1: 344209962Smm parser.error(_("-u, -g, and -e are mutually exclusive")) 345209962Smm 346209962Smm def mungeargs(expected_len): 347209962Smm if un and len(args) == expected_len-1: 348209962Smm return (None, args[expected_len-2]) 349209962Smm elif len(args) == expected_len: 350209962Smm return (args[expected_len-2].split(","), 351209962Smm args[expected_len-1]) 352209962Smm else: 353209962Smm usage(_("wrong number of parameters")) 354209962Smm 355209962Smm if options.set: 356209962Smm if options.local or options.descend or options.user or \ 357209962Smm options.group or options.everyone or options.create: 358209962Smm parser.error(_("invalid option combined with -s")) 359209962Smm if args[0][0] != "@": 360209962Smm parser.error(_("invalid set name: missing '@' prefix")) 361209962Smm 362209962Smm (perms, fsname) = mungeargs(3) 363209962Smm who = args[0] 364209962Smm elif options.create: 365209962Smm if options.local or options.descend or options.user or \ 366209962Smm options.group or options.everyone or options.set: 367209962Smm parser.error(_("invalid option combined with -c")) 368209962Smm 369209962Smm (perms, fsname) = mungeargs(2) 370209962Smm who = None 371209962Smm elif options.everyone: 372209962Smm if options.user or options.group or \ 373209962Smm options.create or options.set: 374209962Smm parser.error(_("invalid option combined with -e")) 375209962Smm 376209962Smm (perms, fsname) = mungeargs(2) 377209962Smm who = ["everyone"] 378209962Smm else: 379209962Smm (perms, fsname) = mungeargs(3) 380209962Smm who = args[0].split(",") 381209962Smm 382209962Smm if not options.local and not options.descend: 383209962Smm options.local = True 384209962Smm options.descend = True 385209962Smm 386209962Smm d = args_to_perms(parser, options, who, perms) 387209962Smm 388209962Smm ds = zfs.dataset.Dataset(fsname, snaps=False) 389209962Smm 390209962Smm if not un and perms: 391209962Smm for p in perms: 392209962Smm if p[0] == "@" and not hasset(ds, p): 393209962Smm parser.error(_("set %s is not defined") % p) 394209962Smm 395209962Smm ds.set_fsacl(un, d) 396209962Smm if un and options.recursive: 397209962Smm for child in ds.descendents(): 398209962Smm child.set_fsacl(un, d) 399