/* * eXist Open Source Native XML Database * Copyright (C) 2001-2013 The eXist-db Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * $Id: UnixStylePermission.java 14571 2011-05-29 12:34:48Z deliriumsky $ */ package org.exist.security; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.util.SyntaxException; /** * All code in this class must be side-effect free * and not carry state, thus ensuring that thus class * can be used in a local or remote scenario * * @author Adam Retter <adam@exist-db.org> */ public abstract class AbstractUnixStylePermission implements Permission { private final static Logger LOG = LogManager.getLogger(AbstractUnixStylePermission.class); /** * The symbolic mode is described by the following grammar: * * mode ::= clause [, clause ...] * clause ::= [who ...] [action ...] action * action ::= op [perm ...] * who ::= a | u | g | o * op ::= + | - | = * perm ::= r | s | t | w | x */ private void setUnixSymbolicMode(final String symbolicMode) throws SyntaxException, PermissionDeniedException { //TODO expand perm to full UNIX chmod i.e. perm ::= r | s | t | w | x | X | u | g | o final String clauses[] = symbolicMode.split(","); for(final String clause : clauses) { final String whoPerm[] = clause.split("[+\\-=]"); int perm = 0; boolean uidgid = false; boolean sticky = false; //process the op first for(final char c : whoPerm[1].toCharArray()) { switch(c) { case READ_CHAR: perm |= READ; break; case WRITE_CHAR: perm |= WRITE; break; case EXECUTE_CHAR: perm |= EXECUTE; break; case SETUID_CHAR: uidgid = true; break; case STICKY_CHAR: sticky = true; break; default: throw new SyntaxException("Unrecognised mode char '" + c + "'"); } } final char whoose[]; if(whoPerm[0].length() > 0) { whoose = whoPerm[0].toCharArray(); } else { whoose = new char[]{ ALL_CHAR }; } for(final char c: whoose) { switch(c) { case ALL_CHAR: final int newMode = (perm << 6) | (perm << 3) | perm | (sticky ? (STICKY << 9) : 0) | (uidgid ? ((SET_UID | SET_GID) << 9) : 0); if(clause.indexOf("+") > -1) { setMode(getMode() | newMode); } else if(clause.indexOf("-") > -1) { setMode(getMode() & ~newMode); } else if(clause.indexOf("=") > -1) { setMode(newMode); } break; case USER_CHAR: if(clause.indexOf("+") > -1) { setOwnerMode(getOwnerMode() | perm); if(uidgid) { setSetUid(true); } } else if(clause.indexOf("-") > -1) { setOwnerMode(getOwnerMode() & ~perm); if(uidgid) { setSetUid(false); } } else if(clause.indexOf("=") > -1) { setOwnerMode(perm); if(uidgid) { setSetUid(true); } } break; case GROUP_CHAR: if(clause.indexOf("+") > -1) { setGroupMode(getGroupMode() | perm); if(uidgid) { setSetGid(true); } } else if(clause.indexOf("-") > -1) { setGroupMode(getGroupMode() & ~perm); if(uidgid) { setSetGid(false); } } else if(clause.indexOf("=") > -1) { setGroupMode(perm); if(uidgid) { setSetGid(true); } } break; case OTHER_CHAR: if(clause.indexOf("+") > -1) { setOtherMode(getOtherMode() | perm); if(sticky) { setSticky(true); } } else if(clause.indexOf("-") > -1) { setOtherMode(getOtherMode() & ~perm); if(sticky) { setSticky(false); } } else if(clause.indexOf("=") > -1) { setOtherMode(perm); if(sticky) { setSticky(true); } } break; default: throw new SyntaxException("Unrecognised mode char '" + c + "'"); } } perm = 0; uidgid = false; sticky = false; } } /** * Set mode using a string. The string has the * following syntax: * * [user|group|other]=[+|-][read|write|execute] * * For example, to set read and write mode for the group, but * not for others: * * group=+read,+write,other=-read,-write * * The new settings are or'ed with the existing settings. * *@param existSymbolicMode The new mode *@exception SyntaxException Description of the Exception * * @deprecated setUnixSymbolicMode should be used instead */ @Deprecated private void setExistSymbolicMode(final String existSymbolicMode) throws SyntaxException, PermissionDeniedException { LOG.warn("Permission modes should not be set using this format '" + existSymbolicMode + "', consider using the UNIX symbolic mode instead"); int shift = 0; int mode = getMode(); for(final String s : existSymbolicMode.toLowerCase().split("=|,")){ if(s.equalsIgnoreCase(USER_STRING)) { shift = 6; } else if(s.equalsIgnoreCase(GROUP_STRING)) { shift = 3; } else if(s.equalsIgnoreCase(OTHER_STRING)) { shift = 0; } else { int perm = 0; if(s.endsWith(READ_STRING.toLowerCase())) { perm = READ; } else if(s.endsWith(WRITE_STRING.toLowerCase())) { perm = WRITE; } else if(s.endsWith(EXECUTE_STRING.toLowerCase())) { perm = EXECUTE; } else { throw new SyntaxException("Unrecognised mode char '" + s + "'"); } if(s.startsWith("+")) { mode |= (perm << shift); } else if(s.startsWith("-")) { mode &= (~(perm << shift)); } else { throw new SyntaxException("Unrecognised mode char '" + s + "'"); } } } setMode(mode); } /** * Simple symbolic mode is [rwxs-]{3}[rwxs-]{3}[rwxt-]{3} */ private void setSimpleSymbolicMode(final String simpleSymbolicMode) throws SyntaxException, PermissionDeniedException { setMode(simpleSymbolicModeToInt(simpleSymbolicMode)); } private final static Pattern unixSymbolicModePattern = Pattern.compile("((?:[augo]*(?:[+\\-=](?:[" + READ_CHAR + SETUID_CHAR + STICKY_CHAR + WRITE_CHAR + EXECUTE_CHAR + "])+)+),?)+"); private final static Pattern existSymbolicModePattern = Pattern.compile("(?:(?:" + USER_STRING + "|" + GROUP_STRING + "|" + OTHER_STRING + ")=(?:[+-](?:" + READ_STRING + "|" + WRITE_STRING + "|" + EXECUTE_STRING + "),?)+)+"); private final static Pattern simpleSymbolicModePattern = Pattern.compile("(?:(?:" + READ_CHAR + "|" + UNSET_CHAR + ")(?:" + WRITE_CHAR + "|" + UNSET_CHAR + ")(?:[" + EXECUTE_CHAR + SETUID_CHAR + SETUID_CHAR_NO_EXEC + "]|" + UNSET_CHAR + ")){2}(?:" + READ_CHAR + "|" + UNSET_CHAR + ")(?:" + WRITE_CHAR + "|" + UNSET_CHAR + ")(?:[" + EXECUTE_CHAR + STICKY_CHAR + "]|" + UNSET_CHAR + ")"); /** * Note: we don't need @PermissionRequired(user = IS_DBA | IS_OWNER) here * because all of these methods delegate to the subclass implementation. * * @param modeStr The String representing a mode to set * * @throws org.exist.util.SyntaxException If the string syntax for the mode * is not recognised. The following syntaxes are supported. Simple symbolic, * Unix symbolic, eXist symbolic. * * @throws org.exist.security.PermissionDeniedException If you do not have * permission to set the mode */ @Override public final void setMode(final String modeStr) throws SyntaxException, PermissionDeniedException { final Matcher simpleSymbolicModeMatcher = simpleSymbolicModePattern.matcher(modeStr); if(simpleSymbolicModeMatcher.matches()) { setSimpleSymbolicMode(modeStr); } else { final Matcher unixSymbolicModeMatcher = unixSymbolicModePattern.matcher(modeStr); if(unixSymbolicModeMatcher.matches()){ setUnixSymbolicMode(modeStr); } else { final Matcher existSymbolicModeMatcher = existSymbolicModePattern.matcher(modeStr); if(existSymbolicModeMatcher.matches()) { setExistSymbolicMode(modeStr); } else { throw new SyntaxException("Unknown mode String: " + modeStr); } } } } public static int simpleSymbolicModeToInt(final String simpleModeStr) throws SyntaxException { int mode = 0; final char modeArray[] = simpleModeStr.toCharArray(); for(int i = 0; i < modeArray.length; i++) { final char c = modeArray[i]; final int shift = (i < 3 ? 6 : (i < 6 ? 3 : 0)); switch(c) { case READ_CHAR: mode |= (READ << shift); break; case WRITE_CHAR: mode |= (WRITE << shift); break; case EXECUTE_CHAR: mode |= (EXECUTE << shift); break; case SETUID_CHAR_NO_EXEC: case SETUID_CHAR: if(i < 3) { mode |= (SET_UID << 9); } else { mode |= (SET_GID << 9); } if(c == SETUID_CHAR) { mode |= (EXECUTE << shift); } break; case STICKY: mode |= (STICKY << 9); break; case UNSET_CHAR: break; default: throw new SyntaxException("Unrecognised mode char '" + c + "'"); } } return mode; } public static String modeToSimpleSymbolicMode(final int mode) { final char ch[] = new char[] { (mode & (READ << 6)) == 0 ? UNSET_CHAR : READ_CHAR, (mode & (WRITE << 6)) == 0 ? UNSET_CHAR : WRITE_CHAR, (mode & (EXECUTE << 6)) == 0 ? UNSET_CHAR : EXECUTE_CHAR, (mode & (READ << 3)) == 0 ? UNSET_CHAR : READ_CHAR, (mode & (WRITE << 3)) == 0 ? UNSET_CHAR : WRITE_CHAR, (mode & (EXECUTE << 3)) == 0 ? UNSET_CHAR : EXECUTE_CHAR, (mode & READ) == 0 ? UNSET_CHAR : READ_CHAR, (mode & WRITE) == 0 ? UNSET_CHAR : WRITE_CHAR, (mode & EXECUTE) == 0 ? UNSET_CHAR : EXECUTE_CHAR }; return String.valueOf(ch); } /** * Utility function for external use * * @param types Types to convert to a string representation * @return The string representation of the types */ public static String typesToString(final int types) { final StringBuilder builder = new StringBuilder(); if((types >> 8) > 0) { if(((types >> 8) & READ) == READ) { builder.append(READ_CHAR); } else { builder.append(UNSET_CHAR); } if(((types >> 8) & WRITE) == WRITE) { builder.append(WRITE_CHAR); } else { builder.append(UNSET_CHAR); } if(((types >> 8) & EXECUTE) == EXECUTE) { builder.append(EXECUTE_CHAR); } else { builder.append(UNSET_CHAR); } } if((types >> 4) > 0) { if(((types >> 4) & READ) == READ) { builder.append(READ_CHAR); } else { builder.append(UNSET_CHAR); } if(((types >> 4) & WRITE) == WRITE) { builder.append(WRITE_CHAR); } else { builder.append(UNSET_CHAR); } if(((types >> 4) & EXECUTE) == EXECUTE) { builder.append(EXECUTE_CHAR); } else { builder.append(UNSET_CHAR); } } if((types & READ) == READ) { builder.append(READ_CHAR); } else { builder.append(UNSET_CHAR); } if((types & WRITE) == WRITE) { builder.append(WRITE_CHAR); } else { builder.append(UNSET_CHAR); } if((types & EXECUTE) == EXECUTE) { builder.append(EXECUTE_CHAR); } else { builder.append(UNSET_CHAR); } return builder.toString(); } }