/***************************************************************************
%name: Acl.java
%version: 1.0
%date_modified: 081598
%dependencies: none

Copyright (c) 1998 Novell, Inc. All Rights Reserved.

THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES.
USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT
ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK.
PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A
ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS
PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET,
DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S
PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S
CUSTOMERS WITH RESPECT TO THIS CODE.
****************************************************************************/

import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.NamingEnumeration;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

import com.novell.utility.naming.Environment;
import com.novell.service.nds.NdsObject;
import com.novell.service.nds.NdsObjectACL;
import com.novell.service.nds.NdsObjectRights;

/**
 * Determines what trustees are already in an object's ACL ,and
 * allows users to add and delete trustees from it. Most of the
 * work is done using the JNDI methods Acl.createInitialContext(),
 * Acl.addTrustee() and deleteTrustee(). It also shows some NJCL
 * functionality in Acl.printACL().
 *
 * An important thing to remember is that there are two interfaces here,
 * JNDI and NJCL, and that one is for generic naming and directory
 * operations, whereas the other is for Novell-centric operations.
 * Sometimes the twain meet, especially when creating new attributes,
 * because in order to new up attrs, you need to delve into NJCL to
 * get instances of attrs and their values that are appropriate for
 * the service provider.
 *
 * To be more than a mere sample, the message strings need to be located
 * external to the class, but that's an I18N issue, not necessary a flaw
 * with the code.
 */

class Acl
{
   static boolean       ADD_OPTION, 
                        DELETE_OPTION, 
                        PRINT_STACK_TRACE, 
                        RESULT, 
                        URL_SPECIFIED = false;
   static int           ENTRY_RIGHTS;
   static StringBuffer  OBJECT_1, OBJECT_2, RIGHTS;
   static StringBuffer  PROVIDER_URL;

   static String        ACL_ATTR_ID = "acl";
   static String        ENTRY_RIGHTS_ACL_ATTR_VALUE = "[Entry Rights]";

   static
   {
      /*ADD_OPTION        = false;
      DELETE_OPTION     = false;
      PRINT_STACK_TRACE = false;
      RESULT            = false;
      URL_SPECIFIED     = false;*/
      ENTRY_RIGHTS      = NdsObjectRights.DS_ENTRY_BROWSE; // default right is browse
      OBJECT_1          = new StringBuffer("");
      OBJECT_2          = new StringBuffer("");
      PROVIDER_URL      = new StringBuffer("");
      RIGHTS            = new StringBuffer("browse");
   }

   public static void main(String[] args)
   {
      parseArgs(args);

      // The provider URL either is empty or contains a bona fide URL;
      // object 2 is empty, contains a name, or contains a URL.
      // We can prepend the String value of the provider URL
      // to the String value of object 2 and come up with a valid URL
      // for object 2

      String object1URL =
         PROVIDER_URL.toString()
            + OBJECT_1.toString();

      String object2URL =
         PROVIDER_URL.toString()
            + OBJECT_2.toString();

      if (URL_SPECIFIED)
         System.out.println("\nUsing "
            + PROVIDER_URL
            + " as the base URL");

      // This is a rather lengthy logic structure designed to
      // perform the appropriate methods that the user has performed.

      // Add the trustee to the target ACL...
      if (ADD_OPTION)
      {
         DirContext target = Acl.createInitialContext(object1URL);
         DirContext trustee = Acl.createInitialContext(object2URL);
         RESULT = Acl.addTrustee(target, trustee);
      }

      // ... delete the trustee from the target ACL ...
      if (DELETE_OPTION)
      {
         DirContext target = Acl.createInitialContext(object1URL);
         DirContext trustee = Acl.createInitialContext(object2URL);
         RESULT = Acl.deleteTrustee(target, trustee);
      }

      // ... or print the target's ACL....
      if (!(ADD_OPTION || DELETE_OPTION))
      {
         DirContext target = Acl.createInitialContext(object1URL);
         RESULT = Acl.printACL(target);
      }
      if (RESULT)
         System.out.println("Operation succeeded");
      else
         System.out.println("Operation failed");
   }

   private static boolean addTrustee(DirContext target, DirContext trustee)
   {
      System.out.println("\nAttempting to add "
         + OBJECT_2
         + " to ACL of "
         + OBJECT_1
         );

      // There are two types of rights; entry rights and attribute rights. 
      // Forthis sample code entry rights are being modified, but if a
      // particular attribute needs to be modified you must replace
      // ENTRY_RIGHTS with "Your Attribute." 

      // this is where we hit NJCL
      NdsObjectACL trusteeACL = new NdsObjectACL(
         ENTRY_RIGHTS_ACL_ATTR_VALUE,
         ((NdsObject)trustee).getDistinguishedName(),
         ENTRY_RIGHTS);

      // this is where the NJCL and JNDI twain meet
      Attributes attrs = new BasicAttributes(ACL_ATTR_ID, trusteeACL);
      try
      {
         target.modifyAttributes("", DirContext.ADD_ATTRIBUTE, attrs);
         return true;
      }
      catch (NamingException ne)
      {
         System.out.println("\nCouldn't add the trustee to the ACL\n");
         if (PRINT_STACK_TRACE)
            ne.printStackTrace();
         return false;
      }
   }

   private static boolean deleteTrustee(DirContext target, DirContext trustee)
   {
      System.out.println("\nAttempting to delete "
         + OBJECT_2
         + " from ACL of "
         + OBJECT_1
         + "."
         );
      try
      {
         // We need an NdsObject ref to the trustee passed into the method
         // so we can do string matching to see if a particular ACL entry is
         // the one we want, and therefore should be removed; the string we
         // will match is the trustee name.

         NdsObject scoped_trustee = (NdsObject)trustee;
         String trusteeName = scoped_trustee.getDistinguishedName();
         
         // the following anonymous class is an optimization to cause JNDI 
         // to only return the attributes with the attrID we want
         Attributes attrs = target.getAttributes("", new String[] { ACL_ATTR_ID });
         Attribute acl = attrs.get(ACL_ATTR_ID);
         NamingEnumeration trustees = acl.getAll();

         boolean SUCCESSFUL_DELETION = false;
         while (trustees.hasMore())
         {
            NdsObjectACL aclEntry = (NdsObjectACL)trustees.next();
            String subjectName = aclEntry.getSubjectName();
            String attrName = aclEntry.getProtectedAttrName();

            if ((subjectName.equalsIgnoreCase(trusteeName)) // is it the same trustee?
               &&
               (attrName.equals(ENTRY_RIGHTS_ACL_ATTR_VALUE))) // is it the [Entry Rights] value?
            {
               Attributes attrs2 = new BasicAttributes("ACL", aclEntry);

               target.modifyAttributes("", DirContext.REMOVE_ATTRIBUTE, attrs2);
               SUCCESSFUL_DELETION = true;
            }
         }
         if (SUCCESSFUL_DELETION)
            System.out.println("The trustee was removed from the ACL");
         else
            System.out.println("The trustee was not removed from the ACL");
         return SUCCESSFUL_DELETION;
      }
      catch (NamingException ne)
      {
         System.out.println("\nCouldn't delete the trustee from the ACL\n");
         if (PRINT_STACK_TRACE)
            ne.printStackTrace();
         return false;
      }
   }

  /**
   * Demonstrates how to access attributes. The DirContext is passed
   * in as the target parameter.
   */
   private static boolean printACL(DirContext target)
   {
      try
      {

        /**
         * Call the getAttributes() method with an empty string
         * passed in to get your own context's attributes. An
         * Attributes interface is returned.
         */
         Attributes attrs = target.getAttributes("");
         Attribute acl = attrs.get(ACL_ATTR_ID);
         if (acl == null)
         {
            System.out.println("\nCan't get the ACL, probably because you do not have browse rights to the object.");
            System.exit(-1);
            return false; // compiler does not like omitting this.
         }
         else
         {
            /**
             * Call the getAll() method, which returns a NamingEnumeration
             * containing all the attributes of a group of Attributes.
             */
            NamingEnumeration trustees = acl.getAll();
            System.out.println("\n==============================================");
            System.out.println("ACL of "
               + OBJECT_1 
               + " [Entry Rights]"
               );
            System.out.println("==============================================");
           /**
            * Traverse through the group of Attributes or Strings until
            * the one you want is found, and then get its values.
            */
            while (trustees.hasMore())
            {
               NdsObjectACL trustee = (NdsObjectACL)trustees.next();
               String attrName = trustee.getProtectedAttrName();
               String subjectName = trustee.getSubjectName();
               if (attrName.equalsIgnoreCase("[Entry Rights]"))
               {
                  StringBuffer privs = new StringBuffer();
                  int rawprivs = (int)trustee.getPrivileges();
                  if ((rawprivs & 0x1) != 0)
                     privs.append("Browse ");
                  if ((rawprivs & 0x2) != 0)
                     privs.append("Add ");
                  if ((rawprivs & 0x4) != 0)
                     privs.append("Delete ");
                  if ((rawprivs & 0x8) != 0)
                     privs.append("Rename ");
                  if ((rawprivs & 0x10) != 0)
                     privs.append("Supervisor ");
                  System.out.println(
                     "Subject name:\t"
                     + subjectName
                     + "\nAttribute:\t"
                     + attrName
                     + "\nPrivileges:\t"
                     + privs);
                  System.out.println("----------------------------------------------");
               }
            }
         }
         System.out.println("");
         return true;
      }
      catch (Throwable t)
      {
         System.out.println("Can't get the trustee list");
         if (PRINT_STACK_TRACE)
            t.printStackTrace();
         return false;
      }
   }

  /**
   * Shows how to create an initial context using _providerURL as
   * the input String parameter. A DirContext object is returned.
   */
   private static DirContext createInitialContext(String _providerURL)
   {
      System.out.println("\nLooking up "
         + _providerURL
         );
      try
      {
        /**
         * Create a Hashtable object and put the specified initial
         * context factory and provider URL into it.
         */
         Hashtable hash = new Hashtable(11);
         hash.put(Context.INITIAL_CONTEXT_FACTORY,
            Environment.NDS_INITIAL_CONTEXT_FACTORY);
         hash.put(Context.PROVIDER_URL, _providerURL);
        /**
         * Create a new initial context using the Hashtable
         * object.
         */
         InitialDirContext initDirCtx = new InitialDirContext(hash);
         return ((DirContext)initDirCtx.lookup(""));
      }
      catch (NamingException ne)
      {
         System.out.println("\nCouldn't lookup "
            + _providerURL);
         System.out.println("\nOperation failed");
         if (PRINT_STACK_TRACE)
            ne.printStackTrace();
         System.exit(-1);
      }
      return null; // compiler gets upset if we leave this out
   }

   private static void parseArgs(String[] args)
   {
      if (args.length < 1)
         help();

      if ((args[0].equals("/?")) || (args[0].equals("-h")))
         help();

      for (int i=0; i < args.length; i++)
      {
         if (args[i].startsWith("-", 0))
         {
            if (args[i].equalsIgnoreCase("-a"))
               ADD_OPTION = true;
            else if (args[i].equalsIgnoreCase("-d"))
               DELETE_OPTION = true;
            else if (args[i].equalsIgnoreCase("-u"))
            {
               URL_SPECIFIED = true;
               if (i == args.length -1)
                  help();  // we are at the end of the args and didn't get a url
                           // so we need to punt
               i++;
               PROVIDER_URL = new StringBuffer(args[i]);
            }
            // rights get assigned here; browse is the default
            else if (args[i].equalsIgnoreCase("-r"))
            {
               if (i == args.length -1)
                  help();
               i++;
               
               System.out.println("\nAssigning the following rights:");
               System.out.print("[Browse] ");

               String rights = args[i].toLowerCase();
               int rights_count = rights.length();
               
               for (int c=0; c < rights_count; c++)
               {
                  if (rights.charAt(c) == new String("a").charAt(0))
                  {
                     System.out.print("[Add] ");
                     ENTRY_RIGHTS |= NdsObjectRights.DS_ENTRY_ADD;
                  }
                  else if (rights.charAt(c) == new String("d").charAt(0))
                  {
                     System.out.print("[Delete] ");
                     ENTRY_RIGHTS |= NdsObjectRights.DS_ENTRY_DELETE;
                  }
                  else if (rights.charAt(c) == new String("r").charAt(0))
                  {
                     System.out.print("[Rename] ");
                     ENTRY_RIGHTS |= NdsObjectRights.DS_ENTRY_RENAME;
                  }
                  else if (rights.charAt(c) == new String("s").charAt(0))
                  {
                     System.out.print("[Supervisor] ");
                     ENTRY_RIGHTS |= NdsObjectRights.DS_ENTRY_SUPERVISOR;
                  }
               }
               System.out.println("");
            }
            else if (args[i].equalsIgnoreCase("-p"))
               PRINT_STACK_TRACE = true;
         }
         else
         {
            if ((ADD_OPTION || DELETE_OPTION || URL_SPECIFIED)
               &&
               ((OBJECT_1.toString().equals("")))
               ||
               (args.length == 1)
            )
               OBJECT_1 = new StringBuffer(args[i]);
            else
               OBJECT_2 = new StringBuffer(args[i]);
         }
      }
   }

   private static void help()
   {
      System.out.println("Acl -- access to NDS object ACLs through Java * ");
      System.out.println("usage: java Acl [-a] [-d] [-u url] object [trustee]");
      System.out.println("\nOPTIONS\n");
      System.out.println("-a\tadds trustee to ACL of object");
      System.out.println("-d\tdeletes trustee from ACL of object");
      System.out.println("-p\tprints stack traces (no stack traces by default)");
      System.out.println("-r\tsets the entry rights for trustee (only with the -a option)");
      System.out.println("\tPermissible values:\n\ta (add)");
      System.out.println("\tb (browse)");
      System.out.println("\td (delete)");
      System.out.println("\tr (rename)");
      System.out.println("\ts (supervisor)");
      System.out.println("-u\tspecifies the NDS URL as a base");
      System.out.println("\nPARAMETERS\n");
      System.out.println("url\tthe URL of the tree that contains object1");
      System.out.println("\t(and if specified, object2) takes the form of nds://[treename]/");
      System.out.println("object1\tobject with the target ACL");
      System.out.println("object2\tobject to be added to object1's ACL");
      System.out.println("\t(used in conjunction with [-a] or [-d])");
      System.out.println("\nCAVEATS\n");
      System.out.println(" * Requires an authenticated connection as a user with sufficient rights.");
      System.out.println(" * Parms can be specified in any order.");
      System.out.println(" * URL requires a trailing slash.");
      System.out.println(" * To print out the ACL for an object, only specify that object as a parm.");
      System.out.println(" * Granting rights only affects the entry, not the attributes.");
      System.out.println(" * In an add operation, omitting the -r option sets browse rights by default.");
      System.out.println("   Also, any addition of rights includes the browse option.");
      System.out.println("\nEXAMPLES\n");
      System.out.println("java Acl nds://superstring/admin.command");
      System.out.println("\tPrints out ACL for admin.command");
      System.out.println("java Acl -a -u nds://superstring/ admin.command resonance.command");
      System.out.println("\tAdds resonance.command to admin.command's ACL with browse rights");
      System.out.println("java Acl -d -u nds://superstring/ admin.command resonance.command");
      System.out.println("\tDeletes resonance.command from admin.command's ACL");
      System.out.println("java Acl -a -r s -u nds://superstring/ admin.command resonance.command");
      System.out.println("\tAdds resonance.command to admin.command's ACL with supervisor rights");
      System.out.println("java Acl -a -r ad -u nds://superstring/ admin.command resonance.command");
      System.out.println("\tAdds resonance.command to admin.command's ACL with add and delete rights");
      System.exit(-1);
   }
}
