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