//////////////////////////////////////////////////////////////////////////// 
// 
// 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.creds;

import java.io.*;
import java.util.Date;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Calendar;
import java.util.TimeZone;
import java.text.DateFormat;
 
import com.dstc.security.kerberos.crypto.KerberosCipher;
import com.dstc.security.kerberos.crypto.KeyMaterial;
import com.dstc.security.kerberos.v5.base.PrincipalName;
import com.dstc.security.kerberos.v5.base.Realm;
import com.dstc.security.kerberos.v5.base.KerberosTime;
import com.dstc.security.kerberos.v5.base.TicketFlags;
import com.dstc.security.kerberos.v5.base.HostAddress;
import com.dstc.security.kerberos.v5.base.HostAddresses;
import com.dstc.security.kerberos.v5.creds.Ticket;
import com.dstc.security.kerberos.v5.crypto.EncryptionKey;

/**
 * A representation of a credential entry in the credential cache
 * 
 * @version 0.98, 98/11/01
 * @author Ming Yung
 * @author Nam Tran
 */
public class Credential implements KeyMaterial
{
  private CredentialInfo credInfo;

  private CredentialName client;
  private CredentialName server;
  private byte sKey;
  private byte[] authData;
  private KerberosTicket ticket;
  private KerberosTicket ticket2;

  /**
   * Default constructor
   */
  public Credential() 
  { 
    client = new CredentialName();
    server = new CredentialName();
  }

  /**
   * Constructs a Credential from a CredentialInfo and a Ticket
   */
  public Credential(CredentialInfo credInfo, KerberosTicket ticket) 
  {
    this();
    this.credInfo = credInfo;
    this.ticket = ticket;
    
    sKey = (byte) 0x00;
    authData = new byte[0];

    if ((credInfo.getClientName() != null) 
        && (credInfo.getClientRealm() != null))
      client = new CredentialName(credInfo.getClientName().getNameType(),
credInfo.getClientRealm().getName(),
credInfo.getClientName().getNameString());
    if ((credInfo.getServerName() != null) 
        && (credInfo.getServerRealm() != null))
      server = new CredentialName(credInfo.getServerName().getNameType(),
credInfo.getServerRealm().getName(),
credInfo.getServerName().getNameString());
  }

  /**
   * Constructs a Credential from the InputStream
   */
  public Credential(CCInputStream cis) throws IOException 
  {
    this();

    //Start of credential - client info
    client = new CredentialName(cis);
    PrincipalName cname = 
new PrincipalName(client.getNameType(), client.getNameComponents());
    Realm crealm = new Realm(client.getRealm());

    // Server info
    server = new CredentialName(cis);
    PrincipalName sname = 
new PrincipalName(server.getNameType(), server.getNameComponents());
    Realm srealm = new Realm(server.getRealm());

    //Encryption Key
    int sessionKeyType = cis.readShort();
    sessionKeyType = cis.readShort();
    byte[] sessionKey = cis.readBinaryString();
    EncryptionKey key = new EncryptionKey(sessionKeyType, sessionKey);

    //Ticket times
    KerberosTime authTime = 
new KerberosTime(new Date(((long)cis.readInt())*1000));
    KerberosTime startTime = 
new KerberosTime(new Date(((long)cis.readInt())*1000));
    KerberosTime endTime = 
new KerberosTime(new Date(((long)cis.readInt())*1000));
    KerberosTime renewTill = 
new KerberosTime(new Date(((long)cis.readInt())*1000));

    //S/Key
    sKey = cis.readByte();

    //TicketFlags
    TicketFlags ticketFlags = new TicketFlags(cis.readInt());

    //Client addresses
    Vector addresses = readAddresses(cis);
    HostAddresses caddr = new HostAddresses(addresses);

    //Auth data
    authData = cis.readBinaryString();

    //Ticket
    byte[] ticketBytes = cis.readBinaryString();
    ticket = null;
    if ((ticketBytes != null) && (ticketBytes.length > 0)) {
      try {
        ticket = new KerberosTicket(ticketBytes);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }

    //Second ticket
    byte[] ticket2Bytes = cis.readBinaryString();
    ticket2 = null;
    if ((ticket2Bytes != null) && (ticket2Bytes.length > 0)) {
      try {
        ticket2 = new KerberosTicket(ticket2Bytes);
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }

    // Group things together in CredentialInfo
    credInfo = new CredentialInfo(key, crealm, cname, ticketFlags, authTime,
  startTime, endTime, renewTill, srealm, sname,
  caddr);
  }

  /**
   * Writes the Credential out to the OutputStream
   */
  public void write(CCOutputStream cc)
    throws IOException
  {
    //Client info
    client.write(cc);

    //Server
    server.write(cc);

    //Session key
    cc.writeShort(credInfo.getSessionKey().getKeyType());
    cc.writeShort(credInfo.getSessionKey().getKeyType());
    cc.writeBinaryString(credInfo.getSessionKey().getKeyBytes());

    //Ticket times
    KerberosTime ktime;
    Date date;

    ktime = credInfo.getAuthTime();
    if (ktime != null) date = ktime.getDate(); else date = new Date(0);
    cc.writeInt((int) (date.getTime()/1000));

    ktime = credInfo.getStartTime();
    if (ktime != null) date = ktime.getDate(); else date = new Date(0);
    cc.writeInt((int) (date.getTime()/1000));

    ktime = credInfo.getEndTime();
    if (ktime != null) date = ktime.getDate(); else date = new Date(0);
    cc.writeInt((int) (date.getTime()/1000));

    ktime = credInfo.getRenewTill();
    if (ktime != null) date = ktime.getDate(); else date = new Date(0);
    cc.writeInt((int) (date.getTime()/1000));

    //Skey
    cc.writeByte(sKey);

    //Ticket Flags
    TicketFlags flags = credInfo.getTicketFlags();
    int flagsMask = (flags != null) ? flags.getFlagsMask() : 0;
    cc.writeInt(flagsMask);

    //Addresses
    Vector addresses;
    if (credInfo.getClientAddress() != null) 
      addresses = credInfo.getClientAddress().getAddresses();
    else
      addresses = new Vector();
    cc.writeInt(addresses.size());
    for (int i = 0; i < addresses.size(); i++) {
      cc.writeShort(((HostAddress) addresses.elementAt(i)).getAddressType());
      cc.writeBinaryString(((HostAddress) addresses.elementAt(i)).getAddress());
    }

    //Auth data
    cc.writeBinaryString(authData);

    //Ticket
    cc.writeBinaryString(ticket.getEncoded());

    //Ticket2
    if (ticket2 == null)
      cc.writeBinaryString(new byte[0]);
    else
      cc.writeBinaryString(ticket2.getEncoded());
  }

  /**
   * Returns the Ticket for this Credential
   */
  public KerberosTicket getTicket() 
  {
    return ticket;
  }

  /**
   * Returns the CredentialInfo for this Credential
   */
  public CredentialInfo getCredentialInfo() 
  {
    return credInfo;
  }

  /**
   * Returns the skey
   */
  public byte getSKey() 
  {
    return sKey;
  }

  /**
   * Returns the authentication data
   */
  public byte[] getAuthData() 
  {
    return authData;
  }

  /**
   * Returns the second ticket
   */
  public KerberosTicket getSecondTicket() 
  {
    return ticket2;
  }

  public byte[] getKeyBytes()
  {
    return getSessionKey();
  }

  public int getKeyType()
  {
    return credInfo.getSessionKey().getKeyType();
  }

  /**
   * Returns the session key for this Credential
   */
  public byte[] getSessionKey() 
  {
    return credInfo.getSessionKey().getKeyBytes();
  }

  /**
   * Returns the client name for this Credential
   */
  public byte[] getClientName() 
  {
    return credInfo.getClientName().encode();
  }

  /**
   * Returns the service name for this Credential
   */
  public String getServiceName() 
  {
    return server.toString();
  }

  /**
   * Returns the expiry time for this Credential
   */
  public Date getExpiryTime() 
  {
    KerberosTime endTime = credInfo.getEndTime();
    if (endTime != null)
      return endTime.getDate();
    else
      return new Date(0);
  }

  /**
   * Returns a String representation of this Credential
   */
  public String toString()
  {
    // client, server, key!!!
    StringBuffer sb = new StringBuffer();
    sb.append("\n\nClient: ");
    sb.append(client.toString());
    sb.append("\nServer: ");
    sb.append(server.toString());
    sb.append("\nSession key type: " + credInfo.getSessionKey().getKeyType());
    sb.append("\nSession key: "+toHexString(credInfo.getSessionKey().getKeyBytes()));

    // times
    DateFormat fmt = 
DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
    fmt.setTimeZone(TimeZone.getTimeZone("AEST"));

    Date date;
    if (credInfo.getAuthTime() != null)
      date = credInfo.getAuthTime().getDate();
    else
      date = new Date(0);
    sb.append("\nStart time: " + fmt.format(date));

    if (credInfo.getEndTime() != null) 
      date = credInfo.getEndTime().getDate();
    else
      date = new Date(0);
    sb.append("\nEnd time: " + fmt.format(date));

    // flags
    int flagMask 
      = (credInfo.getTicketFlags() != null) ? 
         credInfo.getTicketFlags().getFlagsMask() : 0;
    sb.append("\nTicket Flags: " + flagMask);

    // client addresses
    sb.append("\nValid for:");
    if (credInfo.getClientAddress() != null) {
      Enumeration addrs = credInfo.getClientAddress().getAddresses().elements();
      byte[] addr;

      if (!addrs.hasMoreElements())
sb.append(" local host");

      while (addrs.hasMoreElements()) {
addr = ((HostAddress) addrs.nextElement()).getAddress();
sb.append(ipToString(addr));
      }
    }
    else
      sb.append(" local host");

    return sb.toString();
  }

  private StringBuffer ipToString(byte[] addr) {
    StringBuffer sb = new StringBuffer(" ");

    for (int i = 0; i < addr.length; i++) {
      if (i > 0) sb.append(".");
      int j = (int) addr[i];
      if (j < 0) j += 256;
      sb.append(j);
    }

    return sb;
  }

  /**
   * Reads the addresses from the InputStream
   */
  private Vector readAddresses(CCInputStream cis)
    throws IOException
  {
    Vector retval = new Vector();
    int numAddr = cis.readInt();
    for (int i=0; i<numAddr; i++)
    {
      retval.addElement(
        new HostAddress(cis.readShort(), cis.readBinaryString()));
    }
    return retval;
  }

  private String toHexString(byte bytes[])
  {
    StringBuffer sb = new StringBuffer();
    for (int i=0; i<bytes.length; i++)
    {
      sb.append(java.lang.Integer.toHexString(bytes[i] & 0xff));
      sb.append("  ");
    }
    return sb.toString();
  }
}
