1 /*
   2  * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * Redistribution and use in source and binary forms, with or without
   5  * modification, are permitted provided that the following conditions
   6  * are met:
   7  *
   8  *   - Redistributions of source code must retain the above copyright
   9  *     notice, this list of conditions and the following disclaimer.
  10  *
  11  *   - Redistributions in binary form must reproduce the above copyright
  12  *     notice, this list of conditions and the following disclaimer in the
  13  *     documentation and/or other materials provided with the distribution.
  14  *
  15  *   - Neither the name of Oracle nor the names of its
  16  *     contributors may be used to endorse or promote products derived
  17  *     from this software without specific prior written permission.
  18  *
  19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  20  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  21  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  23  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  24  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  25  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  26  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  27  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  28  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  29  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  30  */
  31 
  32 import java.nio.file.*;
  33 import java.nio.file.attribute.*;
  34 import java.io.IOException;
  35 import java.util.*;
  36 import java.util.regex.Pattern;
  37 
  38 /**
  39  * Sample utility for editing a file's ACL.
  40  */
  41 
  42 public class AclEdit {
  43 
  44     // parse string as list of ACE permissions separated by /
  45     static Set<AclEntryPermission> parsePermissions(String permsString) {
  46         Set<AclEntryPermission> perms = new HashSet<AclEntryPermission>();
  47         String[] result = permsString.split("/");
  48         for (String s : result) {
  49             if (s.equals(""))
  50                 continue;
  51             try {
  52                 perms.add(AclEntryPermission.valueOf(s.toUpperCase()));
  53             } catch (IllegalArgumentException x) {
  54                 System.err.format("Invalid permission '%s'\n", s);
  55                 System.exit(-1);
  56             }
  57         }
  58         return perms;
  59     }
  60 
  61     // parse string as list of ACE flags separated by /
  62     static Set<AclEntryFlag> parseFlags(String flagsString) {
  63         Set<AclEntryFlag> flags = new HashSet<AclEntryFlag>();
  64         String[] result = flagsString.split("/");
  65         for (String s : result) {
  66             if (s.equals(""))
  67                 continue;
  68             try {
  69                 flags.add(AclEntryFlag.valueOf(s.toUpperCase()));
  70             } catch (IllegalArgumentException x) {
  71                 System.err.format("Invalid flag '%s'\n", s);
  72                 System.exit(-1);
  73             }
  74         }
  75         return flags;
  76     }
  77 
  78     // parse ACE type
  79     static AclEntryType parseType(String typeString) {
  80         // FIXME: support audit and alarm types in the future
  81         if (typeString.equalsIgnoreCase("allow"))
  82             return AclEntryType.ALLOW;
  83         if (typeString.equalsIgnoreCase("deny"))
  84             return AclEntryType.DENY;
  85         System.err.format("Invalid type '%s'\n", typeString);
  86         System.exit(-1);
  87         return null;    // keep compiler happy
  88     }
  89 
  90     /**
  91      * Parse string of the form:
  92      *   [user|group:]<username|groupname>:<perms>[:flags]:<allow|deny>
  93      */
  94     static AclEntry parseAceString(String s,
  95                                    UserPrincipalLookupService lookupService)
  96     {
  97         String[] result = s.split(":");
  98 
  99         // must have at least 3 components (username:perms:type)
 100         if (result.length < 3)
 101             usage();
 102 
 103         int index = 0;
 104         int remaining = result.length;
 105 
 106         // optional first component can indicate user or group type
 107         boolean isGroup = false;
 108         if (result[index].equalsIgnoreCase("user") ||
 109             result[index].equalsIgnoreCase("group"))
 110         {
 111             if (--remaining < 3)
 112                 usage();
 113             isGroup = result[index++].equalsIgnoreCase("group");
 114         }
 115 
 116         // user and permissions required
 117         String userString = result[index++]; remaining--;
 118         String permsString = result[index++]; remaining--;
 119 
 120         // flags are optional
 121         String flagsString = "";
 122         String typeString = null;
 123         if (remaining == 1) {
 124             typeString = result[index++];
 125         } else {
 126             if (remaining == 2) {
 127                 flagsString = result[index++];
 128                 typeString = result[index++];
 129             } else {
 130                 usage();
 131             }
 132         }
 133 
 134         // lookup UserPrincipal
 135         UserPrincipal user = null;
 136         try {
 137             user = (isGroup) ?
 138                 lookupService.lookupPrincipalByGroupName(userString) :
 139                 lookupService.lookupPrincipalByName(userString);
 140         } catch (UserPrincipalNotFoundException x) {
 141             System.err.format("Invalid %s '%s'\n",
 142                 ((isGroup) ? "group" : "user"),
 143                 userString);
 144             System.exit(-1);
 145         } catch (IOException x) {
 146             System.err.format("Lookup of '%s' failed: %s\n", userString, x);
 147             System.exit(-1);
 148         }
 149 
 150         // map string representation of permissions, flags, and type
 151         Set<AclEntryPermission> perms = parsePermissions(permsString);
 152         Set<AclEntryFlag> flags = parseFlags(flagsString);
 153         AclEntryType type = parseType(typeString);
 154 
 155         // build the ACL entry
 156         return AclEntry.newBuilder()
 157             .setType(type)
 158             .setPrincipal(user)
 159             .setPermissions(perms).setFlags(flags).build();
 160     }
 161 
 162     static void usage() {
 163         System.err.println("usage: java AclEdit [ACL-operation] file");
 164         System.err.println("");
 165         System.err.println("Example 1: Prepends access control entry to the begining of the myfile's ACL");
 166         System.err.println("       java AclEdit A+alice:read_data/read_attributes:allow myfile");
 167         System.err.println("");
 168         System.err.println("Example 2: Remove the entry at index 6 of myfile's ACL");
 169         System.err.println("       java AclEdit A6- myfile");
 170         System.err.println("");
 171         System.err.println("Example 3: Replace the entry at index 2 of myfile's ACL");
 172         System.err.println("       java AclEdit A2=bob:write_data/append_data:deny myfile");
 173         System.exit(-1);
 174     }
 175 
 176     static enum Action {
 177         PRINT,
 178         ADD,
 179         REMOVE,
 180         REPLACE;
 181     }
 182 
 183     /**
 184      * Main class: parses arguments and prints or edits ACL
 185      */
 186     public static void main(String[] args) throws IOException {
 187         Action action = null;
 188         int index = -1;
 189         String entryString = null;
 190 
 191         // parse arguments
 192         if (args.length < 1 || args[0].equals("-help") || args[0].equals("-?"))
 193             usage();
 194 
 195         if (args.length == 1) {
 196             action = Action.PRINT;
 197         } else {
 198             String s = args[0];
 199 
 200             // A[index]+entry
 201             if (Pattern.matches("^A[0-9]*\\+.*", s)) {
 202                 String[] result = s.split("\\+", 2);
 203                 if (result.length == 2) {
 204                     if (result[0].length() < 2) {
 205                         index = 0;
 206                     } else {
 207                         index = Integer.parseInt(result[0].substring(1));
 208                     }
 209                     entryString = result[1];
 210                     action = Action.ADD;
 211                 }
 212             }
 213 
 214             // Aindex-
 215             if (Pattern.matches("^A[0-9]+\\-", s)) {
 216                 String[] result = s.split("\\-", 2);
 217                 if (result.length == 2) {
 218                     index = Integer.parseInt(result[0].substring(1));
 219                     entryString = result[1];
 220                     action = Action.REMOVE;
 221                 }
 222             }
 223 
 224             // Aindex=entry
 225             if (Pattern.matches("^A[0-9]+=.*", s)) {
 226                 String[] result = s.split("=", 2);
 227                 if (result.length == 2) {
 228                     index = Integer.parseInt(result[0].substring(1));
 229                     entryString = result[1];
 230                     action = Action.REPLACE;
 231                 }
 232             }
 233         }
 234         if (action == null)
 235             usage();
 236 
 237         int fileArg = (action == Action.PRINT) ? 0 : 1;
 238         Path file = Paths.get(args[fileArg]);
 239 
 240         // read file's ACL
 241         AclFileAttributeView view =
 242             Files.getFileAttributeView(file, AclFileAttributeView.class);
 243         if (view == null) {
 244             System.err.println("ACLs not supported on this platform");
 245             System.exit(-1);
 246         }
 247         List<AclEntry> acl = view.getAcl();
 248 
 249         switch (action) {
 250             // print ACL
 251             case PRINT : {
 252                 for (int i=0; i<acl.size(); i++) {
 253                     System.out.format("%5d: %s\n", i, acl.get(i));
 254                 }
 255                 break;
 256             }
 257 
 258             // add ACE to existing ACL
 259             case ADD: {
 260                 AclEntry entry = parseAceString(entryString, file
 261                     .getFileSystem().getUserPrincipalLookupService());
 262                 if (index >= acl.size()) {
 263                     acl.add(entry);
 264                 } else {
 265                     acl.add(index, entry);
 266                 }
 267                 view.setAcl(acl);
 268                 break;
 269             }
 270 
 271             // remove ACE
 272             case REMOVE: {
 273                 if (index >= acl.size()) {
 274                     System.err.format("Index '%d' is invalid", index);
 275                     System.exit(-1);
 276                 }
 277                 acl.remove(index);
 278                 view.setAcl(acl);
 279                 break;
 280             }
 281 
 282             // replace ACE
 283             case REPLACE: {
 284                 if (index >= acl.size()) {
 285                     System.err.format("Index '%d' is invalid", index);
 286                     System.exit(-1);
 287                 }
 288                 AclEntry entry = parseAceString(entryString, file
 289                     .getFileSystem().getUserPrincipalLookupService());
 290                 acl.set(index, entry);
 291                 view.setAcl(acl);
 292                 break;
 293             }
 294         }
 295     }
 296 }