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