/***********************************************************************
 *
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 *
 *  To contact Novell about this file by physical or electronic mail,
 *  you may find current contact information at www.novell.com.
 *
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *
 ***********************************************************************/

package com.novell.casa.authtoksvc;

import org.bandit.util.config.gen.*;
import org.bandit.util.config.gen.types.ConnectorTypeType;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.apache.xerces.parsers.DOMParser;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;

import java.io.*;

/**
 *
 * Class for the creation and editing of iaRealms.xml files.
 *
 */
public final class IaRealmsEditor
{
   private static final String usage =
      "usage: IaRealmsEditor -op [realmId [-type dirType -url ldapUrl -sr ldapSearchRootCtx ...]] -file iaRealmsFilePath\n\n" +
      "   where:\n" +
      "      -op - Corresponds to one of the following operations:\n" +
      "         -create  - Create new iaRealms.xml file\n" +
      "         -list    - List the realm identifiers\n" +
      "         -get    - Get realm info, must be followed by realmId parameter\n" +
      "         -set    - Set realm info, must be followed by realmId parameters\n" +
      "      -type - Directory type\n" +
      "      -url - LDAP Server URL\n" +
      "      -sr - LDAP Search Root Context\n" +
      "      -file - Path the iaRealms.xml file\n" +
      "      realmId - Identifier of realm being retrieved or set\n" +
      "      dirType - Directory type specifier (eDir or ActiveDirectory)\n" +
      "      ldapUrl - URL of LDAP Server\n" +
      "      ldapSearchRootCtx - LDAP Search Root Context (LDAP Notation)\n" +
      "      Notes: The \"type\", \"url\", and \"sr\" settings are only valid for set operations.\n" +
      "      The \"url\" and \"sr\" parameters can be specified multiple times.\n";

    // iaRealms.xml file template
    private static final String iaRealms =
            "<bci:realms" +
            " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
            " xmlns:bci=\"http://www.bandit-project.org/commonidentity\">" +
            "</bci:realms>";

    /**
     * Gets a starting iaRealms document.
     *
     * @return           Starting iaRealms document.
     */
    private static Document getIaRealmsDoc()
    {
       Document doc = null;
       StringReader reader = null;

       try
       {
          reader = new StringReader(iaRealms);
          InputSource source = new InputSource(reader);

          DOMParser parser = new DOMParser();
          parser.parse(source);
          doc = parser.getDocument();
       }
       catch (Exception e)
       {
         System.err.println("Program error, exception: " + e.toString());
       }
       finally
       {
          if (reader != null)
          {
             try
             {
                reader.close();
             }
             catch (Exception e)
             {
                // Do nothing
             }
          }
       }

       return doc;
    }

   /**
    * List the realms configured in the specified iaRealms.xml file.
    *
    * @param filePath   Path to the iaRealms.xml file.
    * @return           True if the operation is successfully performed.
    */
   private static boolean performListOperation(String filePath)
   {
      boolean opPerformed = false;

      // List the id of the realms present in the iaRealms.xml file
      //
      // Go through all of the configured realms
      FileReader fileReader = null;
      try
      {
         // IMPORTANT WARNING - Do not change the output format without changing utilities
         // that depend on it such as the yast2-ats module.
         fileReader = new FileReader(filePath);
         RealmsType realmsType = Realms.unmarshal(fileReader);
         for (int i = 0; i < realmsType.getRealmsTypeItemCount(); i++)
         {
            RealmsTypeItem realmsTypeItem = realmsType.getRealmsTypeItem(i);
            org.bandit.util.config.gen.Realm realm = realmsTypeItem.getRealm();
            if (realm.getDesc().equalsIgnoreCase("Directory"))
            {
               // We are dealing with a directory realm, display its Id.
               System.out.println(realm.getId());
            }

            // Success
            opPerformed = true;
         }
      }
      catch (Exception e)
      {
         System.err.println("Error: " + e.getMessage());
      }
      finally
      {
         if (fileReader != null)
         {
            try
            {
               fileReader.close();
            }
            catch (IOException e)
            {
               // Do nothing
            }
         }
      }

      return opPerformed;
   }

   /**
    * Create iaRealms.xml file.
    *
    * @param filePath   Path to the iaRealms.xml file.
    * @return           True if the operation is successfully performed.
    */
   private static boolean performCreateOperation(String filePath)
   {
      boolean opPerformed = false;

      // Create a iaRealms.xml file
      Document doc = getIaRealmsDoc();
      if (doc != null)
      {
         FileOutputStream out = null;

         try
         {
            File f = new File(filePath);
            boolean createStatus = f.createNewFile();
            if (createStatus)
            {
               out = new FileOutputStream(f);
               OutputFormat format = new OutputFormat(doc);
               XMLSerializer serializer = new XMLSerializer(out, format);
               serializer.serialize(doc.getDocumentElement());

               opPerformed = true;
            }
            else
            {
               System.err.println("File " + filePath + " already exists");
            }
         }
         catch (IOException e)
         {
            System.err.println("Error creating file " + filePath + ", exception: " + e.toString());
         }
         catch (SecurityException e)
         {
            System.err.println("SecurityException creating " + filePath);
         }
         finally
         {
            if (out != null)
            {
               try
               {
                  out.flush();
                  out.close();
               }
               catch (IOException e)
               {
                  // Do nothing
               }
            }
         }
      }

      return opPerformed;
   }

   /**
    * Get realm entry information from specified iaRealms.xml file.
    *
    * @param filePath   Path to the iaRealms.xml file.
    * @param realmId   Realm identifier.
    * @return           True if operation is successfully performed.
    */
   private static boolean performGetOperation(String filePath, String realmId)
   {
      boolean opPerformed = false;

      // Get realm information from iaRealms.xml file
      //
      // Go through all of the configured realms
      FileReader fileReader = null;
      try
      {
         fileReader = new FileReader(filePath);
         RealmsType realmsType = Realms.unmarshal(fileReader);
         for (int i = 0; i < realmsType.getRealmsTypeItemCount(); i++)
         {
            RealmsTypeItem realmsTypeItem = realmsType.getRealmsTypeItem(i);
            org.bandit.util.config.gen.Realm realm = realmsTypeItem.getRealm();
            if (realm.getDesc().equalsIgnoreCase("Directory"))
            {
               // We are dealing with a directory realm, check if it is the one that we
               // are looking for.
               if (realm.getId().equals(realmId))
               {
                  // Extract the needed information about the realm
                  String dirType = null;
                  String[] searchRoots = null;
                  String[] ldapUrls = null;
                  RealmTypeItem[] realmTypeItems = realm.getRealmTypeItem();
                  for (int ii = 0; ii < realmTypeItems.length; ii++)
                  {
                     // Find the configure Proxy User Name for the realm and any configured
                     // search roots.
                     if (realmTypeItems[ii].getConnection() == null)
                     {
                        Env env[] = realmTypeItems[ii].getEnv();
                        if (env != null)
                        {
                           for (int iii = 0; iii < env.length; iii++)
                           {
                              if (env[iii].getProp().equalsIgnoreCase("com.novell.casa.authtoksvc.searchroot"))
                              {
                                 // We are dealing with a search root, keep track of it. Make sure to preserve the order
                                 // in which they are found.
                                 if (searchRoots == null)
                                 {
                                    searchRoots = new String[1];
                                    searchRoots[0] = env[iii].getValue();
                                 }
                                 else
                                 {
                                    String[] newSearchRoots = new String[searchRoots.length + 1];
                                    System.arraycopy(searchRoots, 0, newSearchRoots, 0, searchRoots.length);
                                    newSearchRoots[searchRoots.length] = env[iii].getValue();
                                    searchRoots = newSearchRoots;
                                 }
                              }
                              else if (env[iii].getProp().equalsIgnoreCase("com.novell.casa.authtoksvc.directory_type"))
                              {
                                 // We are dealing with a directory type, decode it and record the result.
                                 dirType = env[iii].getValue();
                              }
                           }
                        }
                     }
                     else
                     {
                        // Obtain the LDAP Url information
                        LDAPConnector connector = (LDAPConnector) realmTypeItems[ii].getConnection();
                        ldapUrls = new String[connector.getAddressCount()];
                        for (int iii = 0; iii < ldapUrls.length; iii++)
                        {
                           ldapUrls[iii] = connector.getAddress(iii);
                        }
                     }
                  }

                  // Display the information if successful
                  if (dirType != null
                      && ldapUrls != null)
                  {
                     // IMPORTANT WARNING - Do not change the output format without changing utilities
                     // that depend on it such as the yast2-ats module.
                     System.out.println("DirType");
                     System.out.println("\t" + dirType);
                     if (searchRoots != null)
                     {
                        System.out.println("SearchRoots");
                        for (int ii = 0; ii < searchRoots.length; ii++)
                           System.out.println("\t" + searchRoots[ii]);
                     }
                     System.out.println("LdapUrls");
                     for (int ii = 0; ii < ldapUrls.length; ii++)
                        System.out.println("\t" + ldapUrls[ii]);

                     // Success
                     opPerformed = true;
                  }

                  // No need to look any further
                  break;
               }
            }
         }
      }
      catch (Exception e)
      {
         System.err.println("Error: " + e.getMessage());
      }
      finally
      {
         if (fileReader != null)
         {
            try
            {
               fileReader.close();
            }
            catch (IOException e)
            {
               // Do nothing
            }
         }
      }

      return opPerformed;
   }

    /**
     * Set realm entry in specified iaRealms.xml file.
     *
     * @param filePath   Path to the iaRealms.xml file.
     * @param realmId   Realm identifier.
     * @param dirType   Directory type.
     * @param ldapUrls  List of LDAP URLs.
     * @param searchRoots List of Search Roots (may be null).
     * @return           True if operation is successfully performed.
     */
    private static boolean performSetOperation(String filePath,
                                               String realmId,
                                               String dirType,
                                               String[] ldapUrls,
                                               String[] searchRoots)
    {
       boolean opPerformed = false;

       // Set realm information in iaRealms.xml file
       FileReader fileReader = null;
       try
       {
          fileReader = new FileReader(filePath);
          RealmsType realmsType = Realms.unmarshal(fileReader);

          // Start RealmsTypeItem setup
          RealmsTypeItem realmsTypeItem = new RealmsTypeItem();
          org.bandit.util.config.gen.Realm realm = new org.bandit.util.config.gen.Realm();
          realm.setDesc("Directory");
          realm.setConnectorType(ConnectorTypeType.ORG_BANDIT_IA_CONNECTORS_LDAPCONNECTORINITIALCTXFACTORY);
          realm.setId(realmId);

          // Set the RealmTypeItems
          RealmTypeItem[] realmTypeItems = new RealmTypeItem[2];

          RealmTypeItem envRealmTypeItem = new RealmTypeItem();
          Env[] env;
          if (searchRoots != null)
            env = new Env[4 + searchRoots.length];
          else
            env = new Env[4];
          for (int i = 0; i < env.length; i++)
            env[i] = new Env();

          env[0].setProp("java.naming.security.authentication");
          env[0].setValue("simple");
          env[1].setProp("java.naming.referral");
          env[1].setValue("follow");
          env[2].setProp("com.novell.casa.authtoksvc.directory_type");
          env[2].setValue(dirType);
          if (dirType.equals("eDir"))
          {
             env[3].setProp("java.naming.ldap.attribute.binary");
             env[3].setValue("guid");
          }
          else
          {
             env[3].setProp("java.naming.ldap.attribute.binary");
             env[3].setValue("objectGUID");
          }
          if (searchRoots != null)
          {
             for (int i = 0; i < searchRoots.length; i++)
             {
                env[4 + i].setProp("com.novell.casa.authtoksvc.searchroot");
                env[4 + i].setValue(searchRoots[i]);
             }
          }
          envRealmTypeItem.setEnv(env);
          realmTypeItems[0] = envRealmTypeItem;

          RealmTypeItem connRealmTypeItem = new RealmTypeItem();
          LDAPConnector connector = new LDAPConnector();
          connector.setAddress(ldapUrls);
          connRealmTypeItem.setConnection(connector);
          realmTypeItems[1] = connRealmTypeItem;

          // Associate the items
          realm.setRealmTypeItem(realmTypeItems);
          realmsTypeItem.setRealm(realm);
          realmsType.addRealmsTypeItem(realmsTypeItem);

          // Write the file out
          Writer fileWriter = null;
          try
          {
             File f = new File(filePath);
             fileWriter = new FileWriter(f);
             realmsType.marshal(fileWriter);

             opPerformed = true;
          }
          catch (IOException e)
          {
             System.err.println("Error creating file " + filePath + ", exception: " + e.toString());
          }
          catch (SecurityException e)
          {
             System.err.println("SecurityException creating " + filePath);
          }
          finally
          {
             if (fileWriter != null)
             {
                try
                {
                   fileWriter.flush();
                   fileWriter.close();
                }
                catch (IOException e)
                {
                   // Do nothing
                }
             }
          }
       }
       catch (Exception e)
       {
          System.err.println("Error: " + e.getMessage());
       }
       finally
       {
          if (fileReader != null)
          {
             try
             {
                fileReader.close();
             }
             catch (IOException e)
             {
                // Do nothing
             }
          }
       }

       return opPerformed;
    }

    /**
    *  Applications Entry Point
    *
    * @param args  Argument.
    */
    public static void main(String[] args)
    {
       String op = null;
       boolean opPerformed = false;
       boolean argumentsError = false;
       String filePath = null;
       String realmId = null;
       String dirType = null;
       String[] searchRoots = null;
       String[] ldapUrls = null;

       // Process the command line arguments
       for (int i = 0; i < args.length; i++)
       {
          // Proceed based on the command
          if (args[i].equalsIgnoreCase("-file"))
          {
             // The next argument should contain the filepath
             if (args.length > (i + 1))
             {
                filePath = args[i + 1];
                i++;
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-list"))
          {
             // List operation requested
             if (op == null)
             {
                op = "list";
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-create"))
          {
             // List operation requested
             if (op == null)
             {
                op = "create";
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-get"))
          {
             // Get setting operation requested
             if (op == null)
             {
                op = "get";

                // The next argument should contain the realm identifier
                if (args.length > (i + 1))
                {
                   realmId = args[i + 1];
                   i++;
                }
                else
                {
                   argumentsError = true;
                   break;
                }
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-set"))
          {
             // Set setting operation requested
             if (op == null)
             {
                op = "set";

                // The next argument should contain the realm identifier
                if (args.length > (i + 1))
                {
                   realmId = args[i + 1];
                   i++;
                }
                else
                {
                   argumentsError = true;
                   break;
                }
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-type"))
          {
             // Directory type specified
             //
             // The next argument should contain the type
             if (args.length > (i + 1))
             {
                dirType = args[i + 1];
                i++;
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-url"))
          {
             // LDAP URL specified
             //
             // The next argument should contain the URL
             if (args.length > (i + 1))
             {
                // Make sure to preserve the order in which they are found
                if (ldapUrls == null)
                {
                   ldapUrls = new String[1];
                   ldapUrls[0] = args[i + 1];
                }
                else
                {
                   String[] newLdapUrls = new String[ldapUrls.length + 1];
                   System.arraycopy(ldapUrls, 0, newLdapUrls, 0, ldapUrls.length);
                   newLdapUrls[ldapUrls.length] = args[i + 1];
                   ldapUrls = newLdapUrls;
                }

                i++;
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else if (args[i].equalsIgnoreCase("-sr"))
          {
             // Search Root specified
             //
             // The next argument should contain the Search Root
             if (args.length > (i + 1))
             {
                // Make sure to preserve the order in which they are found
                if (searchRoots == null)
                {
                   searchRoots = new String[1];
                   searchRoots[0] = args[i + 1];
                }
                else
                {
                   String[] newSearchRoots = new String[searchRoots.length + 1];
                   System.arraycopy(searchRoots, 0, newSearchRoots, 0, searchRoots.length);
                   newSearchRoots[searchRoots.length] = args[i + 1];
                   searchRoots = newSearchRoots;
                }

                i++;
             }
             else
             {
                argumentsError = true;
                break;
             }
          }
          else
          {
             argumentsError = true;
          }
       }

       // Proceed based on the specified parameters
       if (argumentsError == false)
       {
          if (filePath != null && op != null)
          {
             System.err.println("Dealing with iaRealms.xml file: " + filePath);

             // Proceed based on the operation requested
             if (op.compareTo("list") == 0)
             {
                opPerformed = performListOperation(filePath);
             }
             else if (op.compareTo("create") == 0)
             {
                opPerformed = performCreateOperation(filePath);
             }
             else if (op.compareTo("get") == 0)
             {
                opPerformed = performGetOperation(filePath, realmId);
             }
             else if (op.compareTo("set") == 0)
             {
                if (dirType != null && ldapUrls != null)
                {
                   opPerformed = performSetOperation(filePath, realmId, dirType, ldapUrls, searchRoots);
                }
                else
                {
                   argumentsError = true;
                }
             }
             else
             {
                System.err.println("Tool error");
             }
          }
          else
          {
             argumentsError = true;
          }
       }

       // Display the usage string if we encountered an error with the
       // command line arguments.
       if (argumentsError)
          System.out.print(usage);

       // Set the exit code appropriatedly
       if (opPerformed)
          System.exit(0);
       else
          System.exit(1);
    }
}
