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}