// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib;

import java.io.*;
import krb5.lib.asn1.*;
import krb5.lib.ccache.*;

/**
 * Initial Authentication Request
 */
public class KrbAsReq {

    /**
	 * Principal Name
	 * @see krb5.lib.PrincipalName
	 */    
	PrincipalName princName;
	
    /**
	 * Initial Authentication Request
	 * @see krb5.lib.ASReq
	 */		
	ASReq as_req;
	
	/**
	 * Output buffer
	 */	
	byte[] obuf;
	
	/**
	 * Input buffer
	 */	
	byte[] ibuf;
	
	/**
	 * List of credentials
	 * @see krb5.lib.KrbCreds
	 */	
	Credentials creds;

	/**
	 * Class constructor
	 *
	 * @param password is of type StringBuffer
	 * @param options is of type KDCOptions
	 * @param cname is of type PrincipalName
	 * @param sname is of type PrincipalName
	 * @param from is of type KerberosTime
	 * @param till is of type KerberosTime
	 * @param rtime is of type KerberosTime
	 * @param eTypes is of type int[]
	 * @param addresses is of type HostAddresses
	 * @param additionalTickets is of type Ticket[]
	 * @exception KrbException an exception
	 * @exception IOException an exception
	 * @see java.lang.StringBuffer
	 * @see krb5.lib.KDCOptions
	 * @see krb5.lib.PrincipalName
	 * @see krb5.lib.KerberosTime
	 * @see krb5.lib.HostAddress
	 * @see krb5.lib.Ticket
	 * @see krb5.lib.KrbException
	 * @see java.io.IOException
	 */
	public KrbAsReq(
		StringBuffer password,
		KDCOptions options,
		PrincipalName cname,
		PrincipalName sname,
		KerberosTime from,
		KerberosTime till,
		KerberosTime rtime,
		int[] eTypes,
		HostAddresses addresses,
		Ticket[] additionalTickets
	) throws KrbException, IOException {
		EncryptionKey key = null;
		if (password != null)
			key = new EncryptionKey(password + cname.getSalt());
		try {
			init(
				key,
				options,
				cname,
				sname,
				from,
				till,
				rtime,
				eTypes,
				addresses,
				additionalTickets
			);
		}
		finally {
			if (key != null)
				key.destroy();
		}
	}

	/**
	 * Class constructor
	 *
	 * @param key is of type EncryptionKey
	 * @param options is of type KDCOptions
	 * @param cname is of type PrincipalName
	 * @param sname is of type PrincipalName
	 * @param from is of type KerberosTime
	 * @param till is of type KerberosTime
	 * @param rtime is of type KerberosTime
	 * @param eTypes is of type int[]
	 * @param addresses is of type HostAddresses
	 * @param additionalTickets is of type Ticket[]
	 * @exception KrbException an exception
	 * @exception IOException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KDCOptions
	 * @see krb5.lib.PrincipalName
	 * @see krb5.lib.KerberosTime
	 * @see krb5.lib.HostAddress
	 * @see krb5.lib.Ticket
	 * @see krb5.lib.KrbException
	 * @see java.io.IOException
	 */
	public KrbAsReq(
		EncryptionKey key,
		KDCOptions options,
		PrincipalName cname,
		PrincipalName sname,
		KerberosTime from,
		KerberosTime till,
		KerberosTime rtime,
		int[] eTypes,
		HostAddresses addresses,
		Ticket[] additionalTickets
	) throws KrbException, IOException {
		try {
			init(
				key,
				options,
				cname,
				sname,
				from,
				till,
				rtime,
				eTypes,
				addresses,
				additionalTickets
			);
		}
		finally {
			if (key != null)
				key.destroy();
		}
	}

	/**
	 * Class constructor
	 *
	 * @param options is of type KDCOptions
	 * @param cname is of type PrincipalName
	 * @param sname is of type PrincipalName
	 * @param from is of type KerberosTime
	 * @param till is of type KerberosTime
	 * @param rtime is of type KerberosTime
	 * @param eTypes is of type int[]
	 * @param addresses is of type HostAddresses
	 * @param additionalTickets is of type Ticket[]
	 * @exception KrbException an exception
	 * @exception IOException an exception
	 * @see krb5.lib.KDCOptions
	 * @see krb5.lib.PrincipalName
	 * @see krb5.lib.KerberosTime
	 * @see krb5.lib.HostAddress
	 * @see krb5.lib.Ticket
	 * @see krb5.lib.KrbException
	 * @see java.io.IOException
	 */
	public KrbAsReq(
		KDCOptions options,
		PrincipalName cname,
		PrincipalName sname,
		KerberosTime from,
		KerberosTime till,
		KerberosTime rtime,
		int[] eTypes,
		HostAddresses addresses,
		Ticket[] additionalTickets
	) throws KrbException, IOException {
		init(
			null,
			options,
			cname,
			sname,
			from,
			till,
			rtime,
			eTypes,
			addresses,
			additionalTickets
		);
	}


	/**
	 * Sets the value in a request
	 *
	 * @param key is of type EncryptionKey
	 * @param options is of type KDCOptions
	 * @param cname is of type PrincipalName
	 * @param sname is of type PrincipalName
	 * @param from is of type KerberosTime
	 * @param till is of type KerberosTime
	 * @param rtime is of type KerberosTime
	 * @param eTypes is of type int[]
	 * @param addresses is of type HostAddresses
	 * @param additionalTickets is of type Ticket[]
	 * @exception KrbException an exception
	 * @exception IOException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KDCOptions
	 * @see krb5.lib.PrincipalName
	 * @see krb5.lib.KerberosTime
	 * @see krb5.lib.HostAddress
	 * @see krb5.lib.Ticket
	 * @see krb5.lib.KrbException
	 * @see java.io.IOException
	 */
	private void init(
		EncryptionKey key,
		KDCOptions options,
		PrincipalName cname,
		PrincipalName sname,
		KerberosTime from,
		KerberosTime till,
		KerberosTime rtime,
		int[] eTypes,
		HostAddresses addresses,
		Ticket[] additionalTickets
	) throws KrbException, IOException {
		princName = cname;

		PAData[] paData = null;
		if (Config.PA_ENC_TIMESTAMP_REQUIRED) {
			PAEncTSEnc ts = new PAEncTSEnc();
			byte[] temp = ts.asn1Encode();
			if (key != null) {
				EncryptedData encTs = new EncryptedData(key, temp);
				paData = new PAData[1]; 
				paData[0] = new PAData(
					Krb5.PA_ENC_TIMESTAMP,
					encTs.asn1Encode()
				);
			}
		}

		as_req = ApiUtil.as_req(
			paData,
			options,
			cname,
			cname.getRealm(),
			sname,
			from,
			till,
			rtime,
			eTypes,
			addresses,
			additionalTickets
		);
		obuf = as_req.asn1Encode();
	}

	/**
	 * Sends the given string to the given port
	 *
	 * @param kdc is of type String
	 * @param port is of type int
	 * @exception IOException an exception
	 * @see java.lang.String
	 * @see java.io.IOException
	 */
	public void send(String kdc, int port) throws IOException {
		UDPClient kdcClient = new UDPClient(kdc, port);
		kdcClient.send(obuf);
		ibuf = kdcClient.receive();
	}

	/**
	 * Sends the given string to the default port - KDC_INET_DEFAULT_PORT
	 *
	 * @param kdc is of type String
	 * @exception IOException an exception
	 * @see java.io.IOException
	 * @see java.lang.String
	 * @see krb5.lib.Krb5
	 */
	public void send(String kdc) throws IOException {
		send(kdc, Krb5.KDC_INET_DEFAULT_PORT);
	}


	/**
	 * Returns initial authentication response
	 *
	 * @return KrbAsRep is a result
	 * @param password is of type StringBuffer
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbAsRep
	 * @see java.lang.StringBuffer
	 * @see krb5.lib.KrbException
	 */
	public KrbAsRep getKrbAsRep(StringBuffer password) throws KrbException {
		if (password == null)
			throw new KrbException(Krb5.API_INVALID_ARG);
		KrbAsRep temp = null;
		EncryptionKey key = null;
		try {
			try {
				key = new EncryptionKey(password + princName.getSalt());
			}
			finally {
				int len = password.length();
				for (int i = 0; i < len; i++)
					password.setCharAt(i, '\0');
				password.setLength(0);
				password = null;
			}
			temp = getKrbAsRep(key);
		}
		finally {
			if (key != null);
				key.destroy();
		}
		return temp;
	}

	/**
	 * Returns initial authentication response
	 *
	 * @return KrbAsRep is a result
	 * @param key is of type EncryptionKey
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbAsRep
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KrbException
	 */
	public KrbAsRep getKrbAsRep(EncryptionKey key) throws KrbException {
		if (key == null)
			throw new KrbException(Krb5.API_INVALID_ARG);
		EncodeRef ref = new EncodeRef(ibuf);
		ASRep rep = null;
		try {
			rep = new ASRep(ref);
    	} catch (Asn1Exception e) {
			rep = null;
    		KRBError err = new KRBError(ref);
    		String eText;
    		if (err.eText.charAt(err.eText.length() - 1) == 0)
    		    eText = err.eText.substring(0, err.eText.length() - 1);
    		else
    		    eText = err.eText;
			throw new KrbException(err.errorCode, eText);
    	}

		byte[] enc_as_rep_part = rep.encPart.decrypt(key);
		ref = new EncodeRef(enc_as_rep_part);
		EncASRepPart enc_part = new EncASRepPart(ref);
		rep.ticket.sname.setRealm(rep.ticket.realm);
		rep.encKDCRepPart = enc_part;

		ApiUtil.krb_as_tgs_rep_common_checks(
			as_req,
			rep
		);

		creds = new Credentials(
			rep,
			null,
			null,
			false
		);
		return new KrbAsRep(ibuf, creds);
	}
}
