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