//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.kerberos.gssapi;

import java.io.FileInputStream;
import java.net.InetAddress;
import java.util.Vector;

import com.dstc.security.kerberos.Kerberos;
import com.dstc.security.kerberos.v5.base.PrincipalName;

/**
 * An object of this class encapsulates a single GSS-API principal
 * entity. Different name formats and their definitions are identified
 * with universal Object Identifiers (Oids). The format of the names can
 * be derived based on the unique oid of each name type.
 *
 * @author Anthony Ho
 */
public class GSSName
{
  public static final Oid NT_HOSTBASED_SERVICE =
                          new Oid("1.2.840.113554.1.2.1.4");
  public static final Oid NT_HOSTBASED_ALT = new Oid("1.3.6.1.5.6.2");
  public static final Oid NT_USER_NAME = new Oid("1.2.840.113554.1.2.1.1");  
  public static final Oid NT_MACHINE_UID_NAME =
                          new Oid("1.2.840.113554.1.2.1.2");  
  public static final Oid NT_STRING_UID_NAME =
                          new Oid("1.2.840.113554.1.2.1.3");  
  public static final Oid NT_ANONYMOUS = new Oid("1.3.6.1.5.6.3");  
  public static final Oid NT_EXPORT_NAME = new Oid("1.3.6.1.5.6.4");

  // Kerberos V specific
  public static final Oid KRB5_NT_PRINCIPAL_NAME =
                          new Oid("1.2.840.113554.1.2.2.1");

  private String nameStr;
  private Oid nameType;
  private Oid mechOid;

  // Kerberos V specific
  private PrincipalName kerberosName;

  /**
   * Converts a contiguous string name to a GSSName object of the
   * specified type. The nameStr parameter is interpreted based on the
   * type specified.  In general, the GSSName object created will not be
   * an MN; the exception to this is if the type parameter indicates
   * NT_EXPORT_NAME.
   */
  public GSSName(String nameStr, Oid type) throws GSSException
  {
    // One block for each supported name type
    if (type.equals(NT_HOSTBASED_SERVICE)
        || type.equals(NT_HOSTBASED_ALT))
    {
      this.makeHostBasedName(nameStr);
    }
    else if (type.equals(NT_USER_NAME))
    {
      this.nameStr = nameStr;
      this.nameType = NT_USER_NAME;
    }
    else if (type.equals(KRB5_NT_PRINCIPAL_NAME))
    {
      this.makeKrbName(nameStr);
    }
    else
      throw new GSSException(GSSException.BAD_NAMETYPE);
  }

  /**
   * Converts a contiguous byte name to a GSSName object of the specified
   * type. The name parameter is interpreted based on the type specified.
   * This constructor is provided for use with names that aren't expressed
   * as printable strings (for example, names of type NT_EXPORT_NAME).  In
   * general, the GSSName object created will not be an MN.
   */
  public GSSName(byte[] name, Oid type) throws GSSException
  {
    // One block for each supported name type
    if (type.equals(NT_HOSTBASED_SERVICE)
        || type.equals(NT_HOSTBASED_ALT))
    {
      this.makeHostBasedName(new String(name));
    }
    else if (type.equals(NT_USER_NAME))
    {
      this.nameStr = new String(name);
      this.nameType = NT_USER_NAME;
    }
    else if (type.equals(KRB5_NT_PRINCIPAL_NAME))
    {
      this.makeKrbName(new String(name));
    }
    else if (type.equals(NT_EXPORT_NAME))
    {
      try
      {
        // Assumes importing Kerberos V mechanism name
        // Decode imported name and get components
        this.kerberosName = new PrincipalName(name);
        Vector components = this.kerberosName.getNameString();

        // Format printable name
        StringBuffer sb = new StringBuffer();
        sb.append(components.elementAt(0));
        
        // Append additional components if necessary
        if (this.kerberosName.getNameType() == PrincipalName.NT_SRV_HST)
        {
          sb.append("/");
          sb.append(components.elementAt(1));
        }
        this.makeKrbName(sb.toString());
      }
      catch (Exception e)
      {
        throw new GSSException(GSSException.BAD_NAME);
      }
    }
    else
      throw new GSSException(GSSException.BAD_NAMETYPE);
  }

  /**
   * Converts a contiguous string name to a GSSName object of the
   * specified type. The nameStr parameter is interpreted based on the
   * type specified. This constructor is provided to allow the creation of
   * mechanism-specific names without having to call canonicalize.
   */
  public GSSName(String nameStr, Oid nameType, Oid mechType)
                 throws GSSException
  {
    this(nameStr, nameType);
    this.canonicalize(mechType);
  }

  /**
   * Converts a contiguous byte name to a GSSName object of the specified
   * type. The name parameter is interpreted based on the type specified.
   * This constructor is provided to be used with names that aren't
   * expressed as printable strings.  It allows the creation of
   * mechanism-specific names without having to call canonicalize.
   */
  public GSSName(byte[] name, Oid nameType, Oid mechType) throws GSSException
  {
    this(name, nameType);
    this.canonicalize(mechType);
  }

  /**
   * Compares two GSSName objects to determine whether they refer to the
   * same entity.  If either of the names is of the NT_ANONYMOUS type,
   * this call will return "false".
   */
  public boolean equals(Object another)
  {
    if (!(another instanceof GSSName) || this.isAnonymous()
        || ((GSSName)another).isAnonymous())
    {
      return false;
    }
    else
      return (this.nameStr.equals(((GSSName)another).nameStr));
  }

  /**
   * A variation of equals method which may throw a GSSException when the
   * names cannot be compared. If either of the names represents an
   * anonymous entity, the method will return "false".
   */
  public boolean equals(GSSName another) throws GSSException
  {
    if (this.isAnonymous() || another.isAnonymous())
      return false;
    else
    {
      // Check that names are of same type
      if (this.nameType.equals(another.nameType))
        return (this.nameStr.equals(another.nameStr));
      else
        throw new GSSException(GSSException.BAD_NAMETYPE);
    }
  }

  /**
   * Creates a mechanism name (MN) from an arbitrary internal name. This
   * is equivalent to using a constructor which takes the mechanism name
   * as one of its parameters.
   */
  public GSSName canonicalize(Oid mechOid) throws GSSException
  {
    try
    {
      // One block for each supported mechanism
      if (mechOid.equals(GSSManager.KRB5))
      {
        // One block for each supported name type
        if (this.nameType.equals(NT_HOSTBASED_SERVICE))
        {
          this.makeKrbName(this.nameStr.replace('@', '/'));
        }
        else if (this.nameType.equals(NT_USER_NAME))
        {
          this.makeKrbName(this.nameStr);
        }
        else
          throw new GSSException(GSSException.BAD_NAMETYPE);

        return this;
      }
      else
        throw new GSSException(GSSException.BAD_MECH);
    }
    catch (GSSException e)
    {
      throw e;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  /**
   * Returns a canonical contiguous byte representation of a mechanism
   * name (MN), suitable for direct, byte by byte comparison by
   * authorization functions. The name must a MN before calling this
   * method. The format of the header of the outputted buffer is specified
   * in RFC 2078.
   */
  public byte[] export() throws GSSException
  {
    // Check that name is mechnism name
    if (this.mechOid != null)
    {
      // Export depending on mechanism type
      // One block for each supported mechanism
      if (this.mechOid.equals(GSSManager.KRB5))
        return this.kerberosName.encode();
      else
        throw new GSSException(GSSException.BAD_NAME);
    }
    else
      throw new GSSException(GSSException.NAME_NOT_MN);
  }

  /**
   * Returns a textual representation of the GSSName object. To retrieve
   * the printed name format, which determines the syntax of the returned
   * string, the getStringNameType method can be used.
   */
  public String toString()
  {
    return this.nameStr;
  }

  /**
   * Returns the oid representing the type of name returned through the
   * toString method. Using this oid, the syntax of the printable name can
   * be determined.
   */
  public Oid getStringNameType() throws GSSException
  {
    return this.nameType;
  }

  /**
   * Creates a duplicate copy of this name.
   */
  public Object clone() throws CloneNotSupportedException
  {
    try
    {
      GSSName clone = new GSSName(this.nameStr, this.nameType);

      // Canonicalize if name is mechanism name
      if (this.mechOid != null)
        clone.canonicalize(this.mechOid);

      return clone;
    }
    catch (Exception e)
    {
      throw new CloneNotSupportedException();
    }
  }

  /**
   * Tests if this name object represents an anonymous entity. Returns
   * "true" if this is an anonymous name.
   */
  public boolean isAnonymous()
  {
    return (this.nameType.equals(NT_ANONYMOUS));
  }

  private void makeHostBasedName(String nameStr) throws GSSException
  {
    try
    {
      // Append local hostname if not given
      if (nameStr.indexOf('@') == -1)
        this.nameStr = nameStr + "@" + InetAddress.getLocalHost().getHostName();
      else
        this.nameStr = nameStr;

      this.nameType = NT_HOSTBASED_SERVICE;
    }
    catch (Exception e)
    {
      throw new GSSException(GSSException.FAILURE);
    }
  }

  private void makeKrbName(String nameStr) throws GSSException
  {
    try
    {
      int compStart = nameStr.indexOf('/');
      int realmStart = nameStr.indexOf('@');
      Vector components = new Vector();

      // Check if realm is given
      if (realmStart == -1)
      {
        // Check if more than one component
        if (compStart != -1)
        {
          // Split and add components
          components.addElement(nameStr.substring(0, compStart));
          components.addElement(nameStr.substring(compStart + 1));
          this.kerberosName = new PrincipalName(PrincipalName.NT_SRV_HST,
                                            components);
        }
        else
        {
          components.addElement(nameStr);
          this.kerberosName = new PrincipalName(PrincipalName.NT_PRINCIPAL,
                                            components);
        }

        // Append local realm name
        this.nameStr 
          = nameStr + "@" + 
              Kerberos.getDefault().getKerberosContext().getRealm();
      }
      else
      // realm is given
      {
        // Check if more than one component
        if (compStart != -1)
        {
          // Split and add components
          components.addElement(nameStr.substring(0, compStart));
          components.addElement(nameStr.substring(compStart + 1, realmStart));
          this.kerberosName = new PrincipalName(PrincipalName.NT_SRV_HST,
                                            components);
        }
        else
        {
          components.addElement(nameStr.substring(0, realmStart));
          this.kerberosName = new PrincipalName(PrincipalName.NT_PRINCIPAL,
                                            components);
        }
        this.nameStr = nameStr;
      }

      this.nameType = KRB5_NT_PRINCIPAL_NAME;
      this.mechOid = GSSManager.KRB5;
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new GSSException(GSSException.FAILURE);
    }
  }
}
