/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt * or http://forgerock.org/license/CDDLv1.0.html. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at legal-notices/CDDLv1_0.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: * Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END * * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2013-2015 ForgeRock AS. */ package org.opends.server.types; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.AclFileAttributeView; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.Set; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.ldap.ResultCode; import static org.opends.messages.UtilityMessages.*; /** * This class provides a mechanism for setting file permissions in a * more abstract manner than is provided by the underlying operating * system and/or filesystem. It uses a traditional UNIX-style rwx/ugo * representation for the permissions and converts them as necessary * to the scheme used by the underlying platform. It does not provide * any mechanism for getting file permissions, nor does it provide any * way of dealing with file ownership or ACLs. */ @org.opends.server.types.PublicAPI( stability=org.opends.server.types.StabilityLevel.VOLATILE, mayInstantiate=true, mayExtend=false, mayInvoke=true) public class FilePermission { /** * The bitmask that should be used for indicating whether a file is * readable by its owner. */ public static final int OWNER_READABLE = 0x0100; /** * The bitmask that should be used for indicating whether a file is * writable by its owner. */ public static final int OWNER_WRITABLE = 0x0080; /** * The bitmask that should be used for indicating whether a file is * executable by its owner. */ public static final int OWNER_EXECUTABLE = 0x0040; /** * The bitmask that should be used for indicating whether a file is * readable by members of its group. */ public static final int GROUP_READABLE = 0x0020; /** * The bitmask that should be used for indicating whether a file is * writable by members of its group. */ public static final int GROUP_WRITABLE = 0x0010; /** * The bitmask that should be used for indicating whether a file is * executable by members of its group. */ public static final int GROUP_EXECUTABLE = 0x0008; /** * The bitmask that should be used for indicating whether a file is * readable by users other than the owner or group members. */ public static final int OTHER_READABLE = 0x0004; /** * The bitmask that should be used for indicating whether a file is * writable by users other than the owner or group members. */ public static final int OTHER_WRITABLE = 0x0002; /** * The bitmask that should be used for indicating whether a file is * executable by users other than the owner or group members. */ public static final int OTHER_EXECUTABLE = 0x0001; /** The encoded representation for this file permission. */ private int encodedPermission; /** * Creates a new file permission object with the provided encoded * representation. * * @param encodedPermission The encoded representation for this * file permission. */ public FilePermission(int encodedPermission) { this.encodedPermission = encodedPermission; } /** * Creates a new file permission with the specified rights for the * file owner. Users other than the owner will not have any rights. * * @param ownerReadable Indicates whether the owner should have * the read permission. * @param ownerWritable Indicates whether the owner should have * the write permission. * @param ownerExecutable Indicates whether the owner should have * the execute permission. */ public FilePermission(boolean ownerReadable, boolean ownerWritable, boolean ownerExecutable) { encodedPermission = 0x0000; if (ownerReadable) { encodedPermission |= OWNER_READABLE; } if (ownerWritable) { encodedPermission |= OWNER_WRITABLE; } if (ownerExecutable) { encodedPermission |= OWNER_EXECUTABLE; } } /** * Creates a new file permission with the specified rights for the * file owner, group members, and other users. * * @param ownerReadable Indicates whether the owner should have * the read permission. * @param ownerWritable Indicates whether the owner should have * the write permission. * @param ownerExecutable Indicates whether the owner should have * the execute permission. * @param groupReadable Indicates whether members of the file's * group should have the read permission. * @param groupWritable Indicates whether members of the file's * group should have the write permission. * @param groupExecutable Indicates whether members of the file's * group should have the execute * permission. * @param otherReadable Indicates whether other users should * have the read permission. * @param otherWritable Indicates whether other users should * have the write permission. * @param otherExecutable Indicates whether other users should * have the execute permission. */ public FilePermission(boolean ownerReadable, boolean ownerWritable, boolean ownerExecutable, boolean groupReadable, boolean groupWritable, boolean groupExecutable, boolean otherReadable, boolean otherWritable, boolean otherExecutable) { encodedPermission = 0x0000; if (ownerReadable) { encodedPermission |= OWNER_READABLE; } if (ownerWritable) { encodedPermission |= OWNER_WRITABLE; } if (ownerExecutable) { encodedPermission |= OWNER_EXECUTABLE; } if (groupReadable) { encodedPermission |= GROUP_READABLE; } if (groupWritable) { encodedPermission |= GROUP_WRITABLE; } if (groupExecutable) { encodedPermission |= GROUP_EXECUTABLE; } if (otherReadable) { encodedPermission |= OTHER_READABLE; } if (otherWritable) { encodedPermission |= OTHER_WRITABLE; } if (otherExecutable) { encodedPermission |= OTHER_EXECUTABLE; } } /** * Indicates whether this file permission includes the owner read * permission. * * @return true if this file permission includes the * owner read permission, or false if not. */ public boolean isOwnerReadable() { return is(encodedPermission, OWNER_READABLE); } /** * Indicates whether this file permission includes the owner write * permission. * * @return true if this file permission includes the * owner write permission, or false if not. */ public boolean isOwnerWritable() { return is(encodedPermission, OWNER_WRITABLE); } /** * Indicates whether this file permission includes the owner execute * permission. * * @return true if this file permission includes the * owner execute permission, or false if not. */ public boolean isOwnerExecutable() { return is(encodedPermission, OWNER_EXECUTABLE); } /** * Indicates whether this file permission includes the group read * permission. * * @return true if this file permission includes the * group read permission, or false if not. */ public boolean isGroupReadable() { return is(encodedPermission, GROUP_READABLE); } /** * Indicates whether this file permission includes the group write * permission. * * @return true if this file permission includes the * group write permission, or false if not. */ public boolean isGroupWritable() { return is(encodedPermission, GROUP_WRITABLE); } /** * Indicates whether this file permission includes the group execute * permission. * * @return true if this file permission includes the * group execute permission, or false if not. */ public boolean isGroupExecutable() { return is(encodedPermission, GROUP_EXECUTABLE); } /** * Indicates whether this file permission includes the other read * permission. * * @return true if this file permission includes the * other read permission, or false if not. */ public boolean isOtherReadable() { return is(encodedPermission, OTHER_READABLE); } /** * Indicates whether this file permission includes the other write * permission. * * @return true if this file permission includes the * other write permission, or false if not. */ public boolean isOtherWritable() { return is(encodedPermission, OTHER_WRITABLE); } /** * Indicates whether this file permission includes the other execute * permission. * * @return true if this file permission includes the * other execute permission, or false if not. */ public boolean isOtherExecutable() { return is(encodedPermission, OTHER_EXECUTABLE); } private boolean is(int encodedPermissions, int permission) { return (encodedPermissions & permission) == permission; } /** * Attempts to set the given permissions on the specified file. If * the underlying platform does not allow the full level of * granularity specified in the permissions, then an attempt will be * made to set them as closely as possible to the provided * permissions, erring on the side of security. * * @param f The file to which the permissions should be applied. * @param p The permissions to apply to the file. * * @return true if the permissions (or the nearest * equivalent) were successfully applied to the specified * file, or false if was not possible to set * the permissions on the current platform. * * @throws FileNotFoundException If the specified file does not * exist. * * @throws DirectoryException If a problem occurs while trying to * set the file permissions. */ public static boolean setPermissions(File f, FilePermission p) throws FileNotFoundException, DirectoryException { if (!f.exists()) { throw new FileNotFoundException(ERR_FILEPERM_SET_NO_SUCH_FILE.get(f.getAbsolutePath()).toString()); } Path filePath = f.toPath(); PosixFileAttributeView posix = Files.getFileAttributeView(filePath, PosixFileAttributeView.class); if (posix != null) { StringBuilder posixMode = new StringBuilder(); toPOSIXString(p, posixMode, "", "", ""); Set perms = PosixFilePermissions.fromString(posixMode.toString()); try { Files.setPosixFilePermissions(filePath, perms); } catch (UnsupportedOperationException | ClassCastException | IOException | SecurityException ex) { throw new DirectoryException(ResultCode.OTHER, ERR_FILEPERM_SET_JAVA_EXCEPTION.get(f.getAbsolutePath()), ex); } return true; } return Files.getFileAttributeView(filePath, AclFileAttributeView.class) != null; } /** * Attempts to set the given permissions on the specified file. If * the underlying platform does not allow the full level of * granularity specified in the permissions, then an attempt will be * made to set them as closely as possible to the provided * permissions, erring on the side of security. * * @param f The file to which the permissions should be applied. * @param p The permissions to apply to the file. * * @return true if the permissions (or the nearest * equivalent) were successfully applied to the specified * file, or false if was not possible to set * the permissions on the current platform. * * The file is known to exist therefore there is no need for * exists() checks. */ public static boolean setSafePermissions(File f, Integer p) { Path filePath = f.toPath(); PosixFileAttributeView posix = Files.getFileAttributeView(filePath, PosixFileAttributeView.class); if (posix != null) { StringBuilder posixMode = new StringBuilder(); toPOSIXString(new FilePermission(p), posixMode, "", "", ""); Set perms = PosixFilePermissions.fromString(posixMode.toString()); try { Files.setPosixFilePermissions(filePath, perms); } catch (Exception ex) { return false; } return true; } return Files.getFileAttributeView(filePath, AclFileAttributeView.class) != null; } /** * Retrieves a three-character string that is the UNIX mode for the * provided file permission. Each character of the string will be a * numeric digit from zero through seven. * * @param p The permission to retrieve as a UNIX mode string. * * @return The UNIX mode string for the provided permission. */ public static String toUNIXMode(FilePermission p) { StringBuilder buffer = new StringBuilder(3); toUNIXMode(buffer, p); return buffer.toString(); } /** * Appends a three-character string that is the UNIX mode for the * provided file permission to the given buffer. Each character of * the string will be a numeric digit from zero through seven. * * @param buffer The buffer to which the mode string should be * appended. * @param p The permission to retrieve as a UNIX mode string. */ public static void toUNIXMode(StringBuilder buffer, FilePermission p) { byte modeByte = 0x00; if (p.isOwnerReadable()) { modeByte |= 0x04; } if (p.isOwnerWritable()) { modeByte |= 0x02; } if (p.isOwnerExecutable()) { modeByte |= 0x01; } buffer.append(modeByte); modeByte = 0x00; if (p.isGroupReadable()) { modeByte |= 0x04; } if (p.isGroupWritable()) { modeByte |= 0x02; } if (p.isGroupExecutable()) { modeByte |= 0x01; } buffer.append(modeByte); modeByte = 0x00; if (p.isOtherReadable()) { modeByte |= 0x04; } if (p.isOtherWritable()) { modeByte |= 0x02; } if (p.isOtherExecutable()) { modeByte |= 0x01; } buffer.append(modeByte); } /** * Decodes the provided string as a UNIX mode and retrieves the * corresponding file permission. The mode string must contain * three digits between zero and seven. * * @param modeString The string representation of the UNIX mode to * decode. * * @return The file permission that is equivalent to the given UNIX * mode. * * @throws DirectoryException If the provided string is not a * valid three-digit UNIX mode. */ public static FilePermission decodeUNIXMode(String modeString) throws DirectoryException { if (modeString == null || modeString.length() != 3) { LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); throw new DirectoryException(ResultCode.OTHER, message); } int encodedPermission = 0x0000; switch (modeString.charAt(0)) { case '0': break; case '1': encodedPermission |= OWNER_EXECUTABLE; break; case '2': encodedPermission |= OWNER_WRITABLE; break; case '3': encodedPermission |= OWNER_WRITABLE | OWNER_EXECUTABLE; break; case '4': encodedPermission |= OWNER_READABLE; break; case '5': encodedPermission |= OWNER_READABLE | OWNER_EXECUTABLE; break; case '6': encodedPermission |= OWNER_READABLE | OWNER_WRITABLE; break; case '7': encodedPermission |= OWNER_READABLE | OWNER_WRITABLE | OWNER_EXECUTABLE; break; default: LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); throw new DirectoryException(ResultCode.OTHER, message); } switch (modeString.charAt(1)) { case '0': break; case '1': encodedPermission |= GROUP_EXECUTABLE; break; case '2': encodedPermission |= GROUP_WRITABLE; break; case '3': encodedPermission |= GROUP_WRITABLE | GROUP_EXECUTABLE; break; case '4': encodedPermission |= GROUP_READABLE; break; case '5': encodedPermission |= GROUP_READABLE | GROUP_EXECUTABLE; break; case '6': encodedPermission |= GROUP_READABLE | GROUP_WRITABLE; break; case '7': encodedPermission |= GROUP_READABLE | GROUP_WRITABLE | GROUP_EXECUTABLE; break; default: LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); throw new DirectoryException(ResultCode.OTHER, message); } switch (modeString.charAt(2)) { case '0': break; case '1': encodedPermission |= OTHER_EXECUTABLE; break; case '2': encodedPermission |= OTHER_WRITABLE; break; case '3': encodedPermission |= OTHER_WRITABLE | OTHER_EXECUTABLE; break; case '4': encodedPermission |= OTHER_READABLE; break; case '5': encodedPermission |= OTHER_READABLE | OTHER_EXECUTABLE; break; case '6': encodedPermission |= OTHER_READABLE | OTHER_WRITABLE; break; case '7': encodedPermission |= OTHER_READABLE | OTHER_WRITABLE | OTHER_EXECUTABLE; break; default: LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); throw new DirectoryException(ResultCode.OTHER, message); } return new FilePermission(encodedPermission); } /** * Build a file permissions string in the "rwx" form expected by NIO, * but with optional prefix strings before each three character block. *

* For example: "rwxr-xrw-" and "Owner=rwx, Group=r-x", Other=rw-". * * @param p The file permissions to use. * @param buffer The buffer being appended to. * @param owner The owner prefix, must not be null. * @param group The group prefix, must not be null. * @param other The other prefix, must not be null. */ private static void toPOSIXString(FilePermission p, StringBuilder buffer, String owner, String group, String other) { buffer.append(owner); buffer.append(p.isOwnerReadable() ? "r" : "-"); buffer.append(p.isOwnerWritable() ? "w" : "-"); buffer.append(p.isOwnerExecutable() ? "x" : "-"); buffer.append(group); buffer.append(p.isGroupReadable() ? "r" : "-"); buffer.append(p.isGroupWritable() ? "w" : "-"); buffer.append(p.isGroupExecutable() ? "x" : "-"); buffer.append(other); buffer.append(p.isOtherReadable() ? "r" : "-"); buffer.append(p.isOtherWritable() ? "w" : "-"); buffer.append(p.isOtherExecutable() ? "x" : "-"); } /** * Retrieves a string representation of this file permission. * * @return A string representation of this file permission. */ @Override public String toString() { StringBuilder buffer = new StringBuilder(); toString(buffer); return buffer.toString(); } /** * Appends a string representation of this file permission to the * given buffer. * * @param buffer The buffer to which the data should be appended. */ public void toString(StringBuilder buffer) { toPOSIXString(this, buffer, "Owner=", ", Group=", ", Other="); } }