001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions Copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2006-2008 Sun Microsystems, Inc. 015 * Portions Copyright 2013-2016 ForgeRock AS. 016 */ 017package org.opends.server.types; 018 019import static org.opends.messages.UtilityMessages.*; 020 021import java.io.File; 022import java.io.FileNotFoundException; 023import java.io.IOException; 024import java.nio.file.Files; 025import java.nio.file.Path; 026import java.nio.file.attribute.AclFileAttributeView; 027import java.nio.file.attribute.PosixFileAttributeView; 028import java.nio.file.attribute.PosixFilePermission; 029import java.nio.file.attribute.PosixFilePermissions; 030import java.util.Set; 031 032import org.forgerock.i18n.LocalizableMessage; 033import org.forgerock.opendj.ldap.ResultCode; 034 035/** 036 * This class provides a mechanism for setting file permissions in a 037 * more abstract manner than is provided by the underlying operating 038 * system and/or filesystem. It uses a traditional UNIX-style rwx/ugo 039 * representation for the permissions and converts them as necessary 040 * to the scheme used by the underlying platform. It does not provide 041 * any mechanism for getting file permissions, nor does it provide any 042 * way of dealing with file ownership or ACLs. 043 */ 044@org.opends.server.types.PublicAPI( 045 stability=org.opends.server.types.StabilityLevel.VOLATILE, 046 mayInstantiate=true, 047 mayExtend=false, 048 mayInvoke=true) 049public class FilePermission 050{ 051 /** The bitmask that should be used for indicating whether a file is readable by its owner. */ 052 private static final int OWNER_READABLE = 0x0100; 053 /** The bitmask that should be used for indicating whether a file is writable by its owner. */ 054 private static final int OWNER_WRITABLE = 0x0080; 055 /** The bitmask that should be used for indicating whether a file is executable by its owner. */ 056 private static final int OWNER_EXECUTABLE = 0x0040; 057 /** 058 * The bitmask that should be used for indicating whether a file is 059 * readable by members of its group. 060 */ 061 private static final int GROUP_READABLE = 0x0020; 062 /** 063 * The bitmask that should be used for indicating whether a file is 064 * writable by members of its group. 065 */ 066 private static final int GROUP_WRITABLE = 0x0010; 067 /** 068 * The bitmask that should be used for indicating whether a file is 069 * executable by members of its group. 070 */ 071 private static final int GROUP_EXECUTABLE = 0x0008; 072 /** 073 * The bitmask that should be used for indicating whether a file is 074 * readable by users other than the owner or group members. 075 */ 076 private static final int OTHER_READABLE = 0x0004; 077 /** 078 * The bitmask that should be used for indicating whether a file is 079 * writable by users other than the owner or group members. 080 */ 081 private static final int OTHER_WRITABLE = 0x0002; 082 /** 083 * The bitmask that should be used for indicating whether a file is 084 * executable by users other than the owner or group members. 085 */ 086 private static final int OTHER_EXECUTABLE = 0x0001; 087 088 /** The encoded representation for this file permission. */ 089 private int encodedPermission; 090 091 /** 092 * Creates a new file permission object with the provided encoded 093 * representation. 094 * 095 * @param encodedPermission The encoded representation for this 096 * file permission. 097 */ 098 public FilePermission(int encodedPermission) 099 { 100 this.encodedPermission = encodedPermission; 101 } 102 103 /** 104 * Indicates whether this file permission includes the owner read 105 * permission. 106 * 107 * @return <CODE>true</CODE> if this file permission includes the 108 * owner read permission, or <CODE>false</CODE> if not. 109 */ 110 public boolean isOwnerReadable() 111 { 112 return is(encodedPermission, OWNER_READABLE); 113 } 114 115 /** 116 * Indicates whether this file permission includes the owner write 117 * permission. 118 * 119 * @return <CODE>true</CODE> if this file permission includes the 120 * owner write permission, or <CODE>false</CODE> if not. 121 */ 122 public boolean isOwnerWritable() 123 { 124 return is(encodedPermission, OWNER_WRITABLE); 125 } 126 127 /** 128 * Indicates whether this file permission includes the owner execute 129 * permission. 130 * 131 * @return <CODE>true</CODE> if this file permission includes the 132 * owner execute permission, or <CODE>false</CODE> if not. 133 */ 134 public boolean isOwnerExecutable() 135 { 136 return is(encodedPermission, OWNER_EXECUTABLE); 137 } 138 139 /** 140 * Indicates whether this file permission includes the group read 141 * permission. 142 * 143 * @return <CODE>true</CODE> if this file permission includes the 144 * group read permission, or <CODE>false</CODE> if not. 145 */ 146 public boolean isGroupReadable() 147 { 148 return is(encodedPermission, GROUP_READABLE); 149 } 150 151 /** 152 * Indicates whether this file permission includes the group write 153 * permission. 154 * 155 * @return <CODE>true</CODE> if this file permission includes the 156 * group write permission, or <CODE>false</CODE> if not. 157 */ 158 public boolean isGroupWritable() 159 { 160 return is(encodedPermission, GROUP_WRITABLE); 161 } 162 163 /** 164 * Indicates whether this file permission includes the group execute 165 * permission. 166 * 167 * @return <CODE>true</CODE> if this file permission includes the 168 * group execute permission, or <CODE>false</CODE> if not. 169 */ 170 public boolean isGroupExecutable() 171 { 172 return is(encodedPermission, GROUP_EXECUTABLE); 173 } 174 175 /** 176 * Indicates whether this file permission includes the other read 177 * permission. 178 * 179 * @return <CODE>true</CODE> if this file permission includes the 180 * other read permission, or <CODE>false</CODE> if not. 181 */ 182 public boolean isOtherReadable() 183 { 184 return is(encodedPermission, OTHER_READABLE); 185 } 186 187 /** 188 * Indicates whether this file permission includes the other write 189 * permission. 190 * 191 * @return <CODE>true</CODE> if this file permission includes the 192 * other write permission, or <CODE>false</CODE> if not. 193 */ 194 public boolean isOtherWritable() 195 { 196 return is(encodedPermission, OTHER_WRITABLE); 197 } 198 199 /** 200 * Indicates whether this file permission includes the other execute 201 * permission. 202 * 203 * @return <CODE>true</CODE> if this file permission includes the 204 * other execute permission, or <CODE>false</CODE> if not. 205 */ 206 public boolean isOtherExecutable() 207 { 208 return is(encodedPermission, OTHER_EXECUTABLE); 209 } 210 211 private boolean is(int encodedPermissions, int permission) 212 { 213 return (encodedPermissions & permission) == permission; 214 } 215 216 /** 217 * Attempts to set the given permissions on the specified file. If 218 * the underlying platform does not allow the full level of 219 * granularity specified in the permissions, then an attempt will be 220 * made to set them as closely as possible to the provided 221 * permissions, erring on the side of security. 222 * 223 * @param f The file to which the permissions should be applied. 224 * @param p The permissions to apply to the file. 225 * 226 * @return <CODE>true</CODE> if the permissions (or the nearest 227 * equivalent) were successfully applied to the specified 228 * file, or <CODE>false</CODE> if was not possible to set 229 * the permissions on the current platform. 230 * 231 * @throws FileNotFoundException If the specified file does not 232 * exist. 233 * 234 * @throws DirectoryException If a problem occurs while trying to 235 * set the file permissions. 236 */ 237 public static boolean setPermissions(File f, FilePermission p) 238 throws FileNotFoundException, DirectoryException 239 { 240 if (!f.exists()) 241 { 242 throw new FileNotFoundException(ERR_FILEPERM_SET_NO_SUCH_FILE.get(f.getAbsolutePath()).toString()); 243 } 244 Path filePath = f.toPath(); 245 PosixFileAttributeView posix = Files.getFileAttributeView(filePath, PosixFileAttributeView.class); 246 if (posix != null) 247 { 248 StringBuilder posixMode = new StringBuilder(); 249 toPOSIXString(p, posixMode, "", "", ""); 250 Set<PosixFilePermission> perms = PosixFilePermissions.fromString(posixMode.toString()); 251 try 252 { 253 Files.setPosixFilePermissions(filePath, perms); 254 } 255 catch (UnsupportedOperationException | ClassCastException | IOException | SecurityException ex) 256 { 257 throw new DirectoryException(ResultCode.OTHER, ERR_FILEPERM_SET_JAVA_EXCEPTION.get(f.getAbsolutePath()), ex); 258 } 259 return true; 260 } 261 return Files.getFileAttributeView(filePath, AclFileAttributeView.class) != null; 262 } 263 264 /** 265 * Attempts to set the given permissions on the specified file. If 266 * the underlying platform does not allow the full level of 267 * granularity specified in the permissions, then an attempt will be 268 * made to set them as closely as possible to the provided 269 * permissions, erring on the side of security. 270 * 271 * @param f The file to which the permissions should be applied. 272 * @param p The permissions to apply to the file. 273 * 274 * @return <CODE>true</CODE> if the permissions (or the nearest 275 * equivalent) were successfully applied to the specified 276 * file, or <CODE>false</CODE> if was not possible to set 277 * the permissions on the current platform. 278 * 279 * The file is known to exist therefore there is no need for 280 * exists() checks. 281 */ 282 public static boolean setSafePermissions(File f, Integer p) 283 { 284 Path filePath = f.toPath(); 285 PosixFileAttributeView posix = Files.getFileAttributeView(filePath, PosixFileAttributeView.class); 286 if (posix != null) 287 { 288 StringBuilder posixMode = new StringBuilder(); 289 toPOSIXString(new FilePermission(p), posixMode, "", "", ""); 290 Set<PosixFilePermission> perms = PosixFilePermissions.fromString(posixMode.toString()); 291 try 292 { 293 Files.setPosixFilePermissions(filePath, perms); 294 } 295 catch (Exception ex) 296 { 297 return false; 298 } 299 return true; 300 } 301 return Files.getFileAttributeView(filePath, AclFileAttributeView.class) != null; 302 } 303 304 /** 305 * Retrieves a three-character string that is the UNIX mode for the 306 * provided file permission. Each character of the string will be a 307 * numeric digit from zero through seven. 308 * 309 * @param p The permission to retrieve as a UNIX mode string. 310 * 311 * @return The UNIX mode string for the provided permission. 312 */ 313 public static String toUNIXMode(FilePermission p) 314 { 315 StringBuilder buffer = new StringBuilder(3); 316 toUNIXMode(buffer, p); 317 return buffer.toString(); 318 } 319 320 /** 321 * Appends a three-character string that is the UNIX mode for the 322 * provided file permission to the given buffer. Each character of 323 * the string will be a numeric digit from zero through seven. 324 * 325 * @param buffer The buffer to which the mode string should be 326 * appended. 327 * @param p The permission to retrieve as a UNIX mode string. 328 */ 329 private static void toUNIXMode(StringBuilder buffer, 330 FilePermission p) 331 { 332 byte modeByte = 0x00; 333 if (p.isOwnerReadable()) 334 { 335 modeByte |= 0x04; 336 } 337 if (p.isOwnerWritable()) 338 { 339 modeByte |= 0x02; 340 } 341 if (p.isOwnerExecutable()) 342 { 343 modeByte |= 0x01; 344 } 345 buffer.append(modeByte); 346 347 modeByte = 0x00; 348 if (p.isGroupReadable()) 349 { 350 modeByte |= 0x04; 351 } 352 if (p.isGroupWritable()) 353 { 354 modeByte |= 0x02; 355 } 356 if (p.isGroupExecutable()) 357 { 358 modeByte |= 0x01; 359 } 360 buffer.append(modeByte); 361 362 modeByte = 0x00; 363 if (p.isOtherReadable()) 364 { 365 modeByte |= 0x04; 366 } 367 if (p.isOtherWritable()) 368 { 369 modeByte |= 0x02; 370 } 371 if (p.isOtherExecutable()) 372 { 373 modeByte |= 0x01; 374 } 375 buffer.append(modeByte); 376 } 377 378 /** 379 * Decodes the provided string as a UNIX mode and retrieves the 380 * corresponding file permission. The mode string must contain 381 * three digits between zero and seven. 382 * 383 * @param modeString The string representation of the UNIX mode to 384 * decode. 385 * 386 * @return The file permission that is equivalent to the given UNIX 387 * mode. 388 * 389 * @throws DirectoryException If the provided string is not a 390 * valid three-digit UNIX mode. 391 */ 392 public static FilePermission decodeUNIXMode(String modeString) 393 throws DirectoryException 394 { 395 if (modeString == null || modeString.length() != 3) 396 { 397 LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); 398 throw new DirectoryException(ResultCode.OTHER, message); 399 } 400 401 int encodedPermission = 0x0000; 402 switch (modeString.charAt(0)) 403 { 404 case '0': 405 break; 406 case '1': 407 encodedPermission |= OWNER_EXECUTABLE; 408 break; 409 case '2': 410 encodedPermission |= OWNER_WRITABLE; 411 break; 412 case '3': 413 encodedPermission |= OWNER_WRITABLE | OWNER_EXECUTABLE; 414 break; 415 case '4': 416 encodedPermission |= OWNER_READABLE; 417 break; 418 case '5': 419 encodedPermission |= OWNER_READABLE | OWNER_EXECUTABLE; 420 break; 421 case '6': 422 encodedPermission |= OWNER_READABLE | OWNER_WRITABLE; 423 break; 424 case '7': 425 encodedPermission |= OWNER_READABLE | OWNER_WRITABLE | 426 OWNER_EXECUTABLE; 427 break; 428 default: 429 LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); 430 throw new DirectoryException(ResultCode.OTHER, message); 431 } 432 433 switch (modeString.charAt(1)) 434 { 435 case '0': 436 break; 437 case '1': 438 encodedPermission |= GROUP_EXECUTABLE; 439 break; 440 case '2': 441 encodedPermission |= GROUP_WRITABLE; 442 break; 443 case '3': 444 encodedPermission |= GROUP_WRITABLE | GROUP_EXECUTABLE; 445 break; 446 case '4': 447 encodedPermission |= GROUP_READABLE; 448 break; 449 case '5': 450 encodedPermission |= GROUP_READABLE | GROUP_EXECUTABLE; 451 break; 452 case '6': 453 encodedPermission |= GROUP_READABLE | GROUP_WRITABLE; 454 break; 455 case '7': 456 encodedPermission |= GROUP_READABLE | GROUP_WRITABLE | 457 GROUP_EXECUTABLE; 458 break; 459 default: 460 LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); 461 throw new DirectoryException(ResultCode.OTHER, message); 462 } 463 464 switch (modeString.charAt(2)) 465 { 466 case '0': 467 break; 468 case '1': 469 encodedPermission |= OTHER_EXECUTABLE; 470 break; 471 case '2': 472 encodedPermission |= OTHER_WRITABLE; 473 break; 474 case '3': 475 encodedPermission |= OTHER_WRITABLE | OTHER_EXECUTABLE; 476 break; 477 case '4': 478 encodedPermission |= OTHER_READABLE; 479 break; 480 case '5': 481 encodedPermission |= OTHER_READABLE | OTHER_EXECUTABLE; 482 break; 483 case '6': 484 encodedPermission |= OTHER_READABLE | OTHER_WRITABLE; 485 break; 486 case '7': 487 encodedPermission |= OTHER_READABLE | OTHER_WRITABLE | 488 OTHER_EXECUTABLE; 489 break; 490 default: 491 LocalizableMessage message = ERR_FILEPERM_INVALID_UNIX_MODE_STRING.get(modeString); 492 throw new DirectoryException(ResultCode.OTHER, message); 493 } 494 495 return new FilePermission(encodedPermission); 496 } 497 498 /** 499 * Build a file permissions string in the "rwx" form expected by NIO, 500 * but with optional prefix strings before each three character block. 501 * <p> 502 * For example: "rwxr-xrw-" and "Owner=rwx, Group=r-x", Other=rw-". 503 * 504 * @param p The file permissions to use. 505 * @param buffer The buffer being appended to. 506 * @param owner The owner prefix, must not be null. 507 * @param group The group prefix, must not be null. 508 * @param other The other prefix, must not be null. 509 */ 510 private static void toPOSIXString(FilePermission p, StringBuilder buffer, 511 String owner, String group, String other) 512 { 513 buffer.append(owner); 514 buffer.append(p.isOwnerReadable() ? "r" : "-"); 515 buffer.append(p.isOwnerWritable() ? "w" : "-"); 516 buffer.append(p.isOwnerExecutable() ? "x" : "-"); 517 518 buffer.append(group); 519 buffer.append(p.isGroupReadable() ? "r" : "-"); 520 buffer.append(p.isGroupWritable() ? "w" : "-"); 521 buffer.append(p.isGroupExecutable() ? "x" : "-"); 522 523 buffer.append(other); 524 buffer.append(p.isOtherReadable() ? "r" : "-"); 525 buffer.append(p.isOtherWritable() ? "w" : "-"); 526 buffer.append(p.isOtherExecutable() ? "x" : "-"); 527 } 528 529 /** 530 * Retrieves a string representation of this file permission. 531 * 532 * @return A string representation of this file permission. 533 */ 534 @Override 535 public String toString() 536 { 537 StringBuilder buffer = new StringBuilder(); 538 toString(buffer); 539 return buffer.toString(); 540 } 541 542 /** 543 * Appends a string representation of this file permission to the 544 * given buffer. 545 * 546 * @param buffer The buffer to which the data should be appended. 547 */ 548 private void toString(StringBuilder buffer) 549 { 550 toPOSIXString(this, buffer, "Owner=", ", Group=", ", Other="); 551 } 552}