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 /*
33  * This source code is provided to illustrate the usage of a given feature
34  * or technique and has been deliberately simplified. Additional steps
35  * required for a production-quality application, such as security checks,
36  * input validation and proper error handling, might not be present in
37  * this sample code.
38  */
39 
40 
41 import java.nio.file.*;
42 import java.nio.file.attribute.*;
43 import java.io.IOException;
44 import java.util.*;
45 import java.util.regex.Pattern;
46 
47 /**
48  * Sample utility for editing a file's ACL.
49  */
50 
51 public class AclEdit {
52 
53     // parse string as list of ACE permissions separated by /
parsePermissions(String permsString)54     static Set<AclEntryPermission> parsePermissions(String permsString) {
55         Set<AclEntryPermission> perms = new HashSet<AclEntryPermission>();
56         String[] result = permsString.split("/");
57         for (String s : result) {
58             if (s.equals(""))
59                 continue;
60             try {
61                 perms.add(AclEntryPermission.valueOf(s.toUpperCase()));
62             } catch (IllegalArgumentException x) {
63                 System.err.format("Invalid permission '%s'\n", s);
64                 System.exit(-1);
65             }
66         }
67         return perms;
68     }
69 
70     // parse string as list of ACE flags separated by /
parseFlags(String flagsString)71     static Set<AclEntryFlag> parseFlags(String flagsString) {
72         Set<AclEntryFlag> flags = new HashSet<AclEntryFlag>();
73         String[] result = flagsString.split("/");
74         for (String s : result) {
75             if (s.equals(""))
76                 continue;
77             try {
78                 flags.add(AclEntryFlag.valueOf(s.toUpperCase()));
79             } catch (IllegalArgumentException x) {
80                 System.err.format("Invalid flag '%s'\n", s);
81                 System.exit(-1);
82             }
83         }
84         return flags;
85     }
86 
87     // parse ACE type
parseType(String typeString)88     static AclEntryType parseType(String typeString) {
89         // FIXME: support audit and alarm types in the future
90         if (typeString.equalsIgnoreCase("allow"))
91             return AclEntryType.ALLOW;
92         if (typeString.equalsIgnoreCase("deny"))
93             return AclEntryType.DENY;
94         System.err.format("Invalid type '%s'\n", typeString);
95         System.exit(-1);
96         return null;    // keep compiler happy
97     }
98 
99     /**
100      * Parse string of the form:
101      *   [user|group:]<username|groupname>:<perms>[:flags]:<allow|deny>
102      */
parseAceString(String s, UserPrincipalLookupService lookupService)103     static AclEntry parseAceString(String s,
104                                    UserPrincipalLookupService lookupService)
105     {
106         String[] result = s.split(":");
107 
108         // must have at least 3 components (username:perms:type)
109         if (result.length < 3)
110             usage();
111 
112         int index = 0;
113         int remaining = result.length;
114 
115         // optional first component can indicate user or group type
116         boolean isGroup = false;
117         if (result[index].equalsIgnoreCase("user") ||
118             result[index].equalsIgnoreCase("group"))
119         {
120             if (--remaining < 3)
121                 usage();
122             isGroup = result[index++].equalsIgnoreCase("group");
123         }
124 
125         // user and permissions required
126         String userString = result[index++]; remaining--;
127         String permsString = result[index++]; remaining--;
128 
129         // flags are optional
130         String flagsString = "";
131         String typeString = null;
132         if (remaining == 1) {
133             typeString = result[index++];
134         } else {
135             if (remaining == 2) {
136                 flagsString = result[index++];
137                 typeString = result[index++];
138             } else {
139                 usage();
140             }
141         }
142 
143         // lookup UserPrincipal
144         UserPrincipal user = null;
145         try {
146             user = (isGroup) ?
147                 lookupService.lookupPrincipalByGroupName(userString) :
148                 lookupService.lookupPrincipalByName(userString);
149         } catch (UserPrincipalNotFoundException x) {
150             System.err.format("Invalid %s '%s'\n",
151                 ((isGroup) ? "group" : "user"),
152                 userString);
153             System.exit(-1);
154         } catch (IOException x) {
155             System.err.format("Lookup of '%s' failed: %s\n", userString, x);
156             System.exit(-1);
157         }
158 
159         // map string representation of permissions, flags, and type
160         Set<AclEntryPermission> perms = parsePermissions(permsString);
161         Set<AclEntryFlag> flags = parseFlags(flagsString);
162         AclEntryType type = parseType(typeString);
163 
164         // build the ACL entry
165         return AclEntry.newBuilder()
166             .setType(type)
167             .setPrincipal(user)
168             .setPermissions(perms).setFlags(flags).build();
169     }
170 
usage()171     static void usage() {
172         System.err.println("usage: java AclEdit [ACL-operation] file");
173         System.err.println("");
174         System.err.println("Example 1: Prepends access control entry to the begining of the myfile's ACL");
175         System.err.println("       java AclEdit A+alice:read_data/read_attributes:allow myfile");
176         System.err.println("");
177         System.err.println("Example 2: Remove the entry at index 6 of myfile's ACL");
178         System.err.println("       java AclEdit A6- myfile");
179         System.err.println("");
180         System.err.println("Example 3: Replace the entry at index 2 of myfile's ACL");
181         System.err.println("       java AclEdit A2=bob:write_data/append_data:deny myfile");
182         System.exit(-1);
183     }
184 
185     static enum Action {
186         PRINT,
187         ADD,
188         REMOVE,
189         REPLACE;
190     }
191 
192     /**
193      * Main class: parses arguments and prints or edits ACL
194      */
main(String[] args)195     public static void main(String[] args) throws IOException {
196         Action action = null;
197         int index = -1;
198         String entryString = null;
199 
200         // parse arguments
201         if (args.length < 1 || args[0].equals("-help") || args[0].equals("-?"))
202             usage();
203 
204         if (args.length == 1) {
205             action = Action.PRINT;
206         } else {
207             String s = args[0];
208 
209             // A[index]+entry
210             if (Pattern.matches("^A[0-9]*\\+.*", s)) {
211                 String[] result = s.split("\\+", 2);
212                 if (result.length == 2) {
213                     if (result[0].length() < 2) {
214                         index = 0;
215                     } else {
216                         index = Integer.parseInt(result[0].substring(1));
217                     }
218                     entryString = result[1];
219                     action = Action.ADD;
220                 }
221             }
222 
223             // Aindex-
224             if (Pattern.matches("^A[0-9]+\\-", s)) {
225                 String[] result = s.split("\\-", 2);
226                 if (result.length == 2) {
227                     index = Integer.parseInt(result[0].substring(1));
228                     entryString = result[1];
229                     action = Action.REMOVE;
230                 }
231             }
232 
233             // Aindex=entry
234             if (Pattern.matches("^A[0-9]+=.*", s)) {
235                 String[] result = s.split("=", 2);
236                 if (result.length == 2) {
237                     index = Integer.parseInt(result[0].substring(1));
238                     entryString = result[1];
239                     action = Action.REPLACE;
240                 }
241             }
242         }
243         if (action == null)
244             usage();
245 
246         int fileArg = (action == Action.PRINT) ? 0 : 1;
247         Path file = Paths.get(args[fileArg]);
248 
249         // read file's ACL
250         AclFileAttributeView view =
251             Files.getFileAttributeView(file, AclFileAttributeView.class);
252         if (view == null) {
253             System.err.println("ACLs not supported on this platform");
254             System.exit(-1);
255         }
256         List<AclEntry> acl = view.getAcl();
257 
258         switch (action) {
259             // print ACL
260             case PRINT : {
261                 for (int i=0; i<acl.size(); i++) {
262                     System.out.format("%5d: %s\n", i, acl.get(i));
263                 }
264                 break;
265             }
266 
267             // add ACE to existing ACL
268             case ADD: {
269                 AclEntry entry = parseAceString(entryString, file
270                     .getFileSystem().getUserPrincipalLookupService());
271                 if (index >= acl.size()) {
272                     acl.add(entry);
273                 } else {
274                     acl.add(index, entry);
275                 }
276                 view.setAcl(acl);
277                 break;
278             }
279 
280             // remove ACE
281             case REMOVE: {
282                 if (index >= acl.size()) {
283                     System.err.format("Index '%d' is invalid", index);
284                     System.exit(-1);
285                 }
286                 acl.remove(index);
287                 view.setAcl(acl);
288                 break;
289             }
290 
291             // replace ACE
292             case REPLACE: {
293                 if (index >= acl.size()) {
294                     System.err.format("Index '%d' is invalid", index);
295                     System.exit(-1);
296                 }
297                 AclEntry entry = parseAceString(entryString, file
298                     .getFileSystem().getUserPrincipalLookupService());
299                 acl.set(index, entry);
300                 view.setAcl(acl);
301                 break;
302             }
303         }
304     }
305 }
306