package org.dcache.acl.parser; import com.google.common.base.Splitter; import java.util.ArrayList; import java.util.List; import org.dcache.acl.ACE; import org.dcache.acl.enums.AccessMask; import org.dcache.acl.enums.AceFlags; import org.dcache.acl.enums.AceType; import org.dcache.acl.enums.Who; import static com.google.common.base.Preconditions.checkArgument; public class ACEParser { private static final String LINE_SEPARATOR = System.getProperty("line.separator", "\n"); private static final String SPACE_SEPARATOR = " "; private static final String SEPARATOR = ":"; private static final int ACES_MIN = 1; private static final int ACE_MIN = 3, ACE_MAX = 6; private static final int ACE_MIN_ADM = 2, ACE_MAX_ADM = 5; private static ACEParser _SINGLETON; static { _SINGLETON = new ACEParser(); } private ACEParser() { super(); } public static ACEParser instance() { return _SINGLETON; } /** * ace_spec format: * who[:who_id]:access_msk[:flags]:type * * ace_spec examples: * USER:7:rlwfx:o:ALLOW * EVERYONE@:w:DENY * * @param ace_spec * String representation of ACE */ public static ACE parse(String ace_spec) throws IllegalArgumentException { if ( ace_spec == null || ace_spec.length() == 0 ) { throw new IllegalArgumentException("ace_spec is " + (ace_spec == null ? "NULL" : "Empty")); } if ( ace_spec.endsWith(SEPARATOR) ) { throw new IllegalArgumentException("ace_spec ends with \"" + SEPARATOR + "\""); } if (ace_spec.contains(":+") || ace_spec.contains(":-")) { return parseAdmACE(ace_spec); } String[] split = ace_spec.split(SEPARATOR); if ( split == null ) { throw new IllegalArgumentException("ace_spec can't be splitted."); } int len = split.length; if ( len < ACE_MIN || len > ACE_MAX ) { throw new IllegalArgumentException("Count tags invalid."); } int index = 0; String sWho = split[index++]; Who who = Who.fromAbbreviation(sWho); if ( who == null ) { throw new IllegalArgumentException("Invalid who abbreviation: " + sWho); } int whoID = -1; if ( who == Who.USER || who == Who.GROUP ) { String sWhoID = split[index++]; try { whoID = Integer.parseInt(sWhoID); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid whoID. NumberFormatException: " + e.getMessage()); } } String sAccessMsk = split[index++]; int accessMsk = AccessMask.parseInt(sAccessMsk); if ( accessMsk == 0 ) { throw new IllegalArgumentException("Invalid accessMask: " + sAccessMsk); } if ( index >= len ) { throw new IllegalArgumentException("Unspecified ACE type."); } String s = split[index++]; int flags = 0; try { flags = AceFlags.parseInt(s); s = null; } catch (IllegalArgumentException Ignore) { } if ( s == null ) { if ( index >= len ) { throw new IllegalArgumentException("Unspecified ACE type."); } s = split[index++]; } AceType type = AceType.fromAbbreviation(s); if ( index != len ) { throw new IllegalArgumentException("Check index failure. Invalid ace_spec: " + ace_spec); } return new ACE(type, flags, accessMsk, who, whoID); } /** * ace_spec format: * who[:who_id]:+/-access_msk[:flags] * * ace_spec examples: * USER:3750:+rlx:o * EVERYONE@:-w * * @param order * ACE's order in list * @param ace_spec * String representation of ACE (without 'order') */ public static ACE parseAdmACE(String ace_spec) throws IllegalArgumentException { if ( ace_spec == null || ace_spec.length() == 0 ) { throw new IllegalArgumentException("ace_spec is " + (ace_spec == null ? "NULL" : "Empty")); } if ( ace_spec.endsWith(SEPARATOR) ) { throw new IllegalArgumentException("ace_spec ends with \"" + SEPARATOR + "\""); } String[] split = ace_spec.split(SEPARATOR); if ( split == null ) { throw new IllegalArgumentException("ace_spec can't be splitted."); } int len = split.length; if ( len < ACE_MIN_ADM || len > ACE_MAX_ADM ) { throw new IllegalArgumentException("Count tags invalid."); } int index = 0; String sWho = split[index++]; Who who = Who.fromAbbreviation(sWho); if ( who == null ) { throw new IllegalArgumentException("Invalid who abbreviation: " + sWho); } int whoID = -1; if ( Who.USER.equals(who) || Who.GROUP.equals(who) ) { String sWhoID = split[index++]; if ( index >= len ) { throw new IllegalArgumentException("Unspecified accessMask."); } try { whoID = Integer.parseInt(sWhoID); } catch (NumberFormatException e) { throw new IllegalArgumentException("Invalid whoID. NumberFormatException: " + e.getMessage()); } } String sAccessMsk = split[index++]; AceType type; char operator = sAccessMsk.charAt(0); if ( operator == '+' ) { type = AceType.ACCESS_ALLOWED_ACE_TYPE; } else if ( operator == '-' ) { type = AceType.ACCESS_DENIED_ACE_TYPE; } else { throw new IllegalArgumentException("Invalid operator: \"" + operator + "\""); } int accessMsk = AccessMask.parseInt(sAccessMsk.substring(1)); if ( accessMsk == 0 ) { throw new IllegalArgumentException("Invalid accessMask: " + sAccessMsk); } int flags = 0; if ( index < len ) { String s = split[index++]; if ( s.trim().length() == 0 ) { throw new IllegalArgumentException("ACE flags is Empty."); } try { flags = AceFlags.parseInt(s); s = null; } catch (IllegalArgumentException Ignore) { } if ( s == null && index < len ) { s = split[index++]; } } if ( index != len ) { throw new IllegalArgumentException("Check index failure. Invalid ace_spec: " + ace_spec); } return new ACE(type, flags, accessMsk, who, whoID); } /** * aces_spec format: * who[:who_id]:+/-access_msk[:flags][:address_msk] who[:who_id]:+/-access_msk[:flags][:address_msk] * * aces_spec example: * USER:3750:+rlx:o:FFFF EVERYONE@:-w * * @param aces_spec * String representation of ACEs */ public static List<ACE> parseAdm(String aces_spec) throws IllegalArgumentException { if ( aces_spec == null || aces_spec.length() == 0 ) { throw new IllegalArgumentException("aces_spec is " + (aces_spec == null ? "NULL" : "Empty")); } String[] split = aces_spec.split(SPACE_SEPARATOR); if ( split == null ) { throw new IllegalArgumentException("aces_spec can't be splitted."); } int len = split.length; if ( len < ACES_MIN ) { throw new IllegalArgumentException("Count ACEs invalid."); } List<ACE> aces = new ArrayList<>(len); for (String ace: split) { aces.add(ACEParser.parseAdmACE(ace)); } return aces; } /** * Create an {@link ACE} from string representation. The string * must be in linux nfs4_acl format. The format refined as: * * <pre> * 4-field string in the following format: * <i>type</i>:<i>flags</i>:<i>principal</i>:<i>permissions</i> * <b>ACE</b> <b>TYPES:</b> * There are four <i>types</i> of ACEs, each represented by a single character. * An ACE must have exactly one <i>type</i>. * <b>A</b> Allow - allow <i>principal</i> to perform actions requiring <i>permis-</i> * <i>sions</i>. * <b>D</b> Deny - prevent <i>principal</i> from performing actions requiring <i>per-</i> * <i>missions</i>. * <b>U</b> Audit - log any attempted access by <i>principal</i> which requires * <i>permissions</i>. Requires one or both of the successful-access and * failed-access <i>flags</i>. System-dependent; not supported by all * servers. * <b>L</b> Alarm - generate a system alarm at any attempted access by <i>prin-</i> * <i>cipal</i> which requires <i>permissions</i>. Requires one or both of the * successful-access and failed-access <i>flags</i>. System-dependent; * not supported by all servers. * <b>ACE</b> <b>FLAGS:</b> * There are three kinds of ACE <i>flags</i>: group, inheritance, and administra- * tive. An Allow or Deny ACE may contain zero or more <i>flags</i>, while an * Audit or Alarm ACE must contain at least one of the successful-access * and failed-access <i>flags</i>. * Note that ACEs are inherited from the parent directory's ACL at the * time a file or subdirectory is created. Accordingly, inheritance flags * can be used only in ACEs in a directory's ACL (and are therefore * stripped from inherited ACEs in a new file's ACL). Please see the * <b>INHERITANCE</b> <b>FLAGS</b> <b>COMMENTARY</b> section for more information. * <b>GROUP</b> <b>FLAG</b> - can be used in any ACE * <b>g</b> group - indicates that <i>principal</i> represents a group instead of a * user. * <b>INHERITANCE</b> <b>FLAGS</b> - can be used in any directory ACE * <b>d</b> directory-inherit - newly-created subdirectories will inherit * the ACE. * <b>f</b> file-inherit - newly-created files will inherit the ACE, minus * its inheritance <i>flags</i>. Newly-created subdirectories will * inherit the ACE; if directory-inherit is not also specified in * the parent ACE, inherit-only will be added to the inherited ACE. * <b>n</b> no-propagate-inherit - newly-created subdirectories will inherit * the ACE, minus its inheritance <i>flags</i>. * <b>i</b> inherit-only - the ACE is not considered in permissions checks, * but it is heritable; however, the inherit-only <i>flag</i> is stripped * from inherited ACEs. * <b>ADMINISTRATIVE</b> <b>FLAGS</b> - can be used in <b>Audit</b> and <b>Alarm</b> ACEs * <b>S</b> successful-access - trigger an alarm/audit when <i>principal</i> is * allowed to perform an action covered by <i>permissions</i>. * <b>F</b> failed-access - trigger an alarm/audit when <i>principal</i> is pre- * vented from performing an action covered by <i>permissions</i>. * <b>ACE</b> <b>PRINCIPALS:</b> * A <i>principal</i> is either a named user (e.g., `myuser@nfsdomain.org') or * group (provided the group <i>flag</i> is also set), or one of three special * <i>principals</i>: `OWNER@', `GROUP@', and `EVERYONE@', which are, respec- * tively, analogous to the POSIX user/group/other distinctions used in, * e.g., <a href="?query=chmod&sektion=1&apropos=0&manpath=CentOS+Linux%2famd64+6.3"><b>chmod</b>(1)</a>. * <b>ACE</b> <b>PERMISSIONS:</b> * There are a variety of different ACE <i>permissions</i> (13 for files, 14 for * directories), each represented by a single character. An ACE should * have one or more of the following <i>permissions</i> specified: * <b>r</b> read-data (files) / list-directory (directories) * <b>w</b> write-data (files) / create-file (directories) * <b>a</b> append-data (files) / create-subdirectory (directories) * <b>x</b> execute (files) / change-directory (directories) * <b>d</b> delete - delete the file/directory. Some servers will allow a * delete to occur if either this <i>permission</i> is set in the * file/directory or if the delete-child <i>permission</i> is set in its * parent direcory. * <b>D</b> delete-child - remove a file or subdirectory from within the * given directory (directories only) * <b>t</b> read-attributes - read the attributes of the file/directory. * <b>T</b> write-attributes - write the attributes of the file/directory. * <b>n</b> read-named-attributes - read the named attributes of the * file/directory. * <b>N</b> write-named-attributes - write the named attributes of the * file/directory. * <b>c</b> read-ACL - read the file/directory NFSv4 ACL. * <b>C</b> write-ACL - write the file/directory NFSv4 ACL. * <b>o</b> write-owner - change ownership of the file/directory. * <b>y</b> synchronize - allow clients to use synchronous I/O with the * server. * * (from original man page by David Richter and J. Bruce Fields) * </pre> * @param s ace in linux nfs4_acl format * @return ACE represented by the provided string * @throws IllegalArgumentException if ACE can't be parsed */ public static ACE parseLinuxAce(String s) throws IllegalArgumentException { Splitter splitter = Splitter.on(':'); List<String> splitted = splitter.splitToList(s); checkArgument(splitted.size() == 4, "Invalid ACE format: expected <type:flags:principal:permissions> got: <" + s +">"); checkArgument(splitted.get(0).length() == 1, "Invalid ACE format: type must be a single character. Got : <" + splitted.get(0).length() + ">"); AceType type = AceType.fromAbbreviation(splitted.get(0).charAt(0)); int flags = 0; int accessMask = 0; Who who; int id = -1; String principal; for(char c: splitted.get(1).toCharArray()) { flags |= AceFlags.fromAbbreviation(c).getValue(); } principal = splitted.get(2); if (principal.charAt(principal.length() -1) == '@') { who = Who.fromAbbreviation(principal); } else { who = (flags & AceFlags.IDENTIFIER_GROUP.getValue()) == 0 ? Who.USER : Who.GROUP; id = Integer.parseInt(principal); } for (char c : splitted.get(3).toCharArray()) { accessMask |= AccessMask.fromAbbreviation(c).getValue(); } return new ACE(type, flags, accessMask, who, id); } }