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