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 }