// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib;

import java.net.InetAddress;
import krb5.lib.asn1.*;
import krb5.lib.crypto.*;
import krb5.lib.ccache.*;
import krb5.lib.rcache.*;

/**
 * Kerberos Application Request
 */
public class KrbApReq {
    
	/**
	 * Output buffer
	 */
	byte[] obuf;

	/**
	 * Time
	 */
	KerberosTime _ctime;

	/**
	 * Authentication information
	 * @see krb5.lib.Authenticator
	 */		
	Authenticator authenticator;
	
	/**
	 * Sender name
	 * @see krb5.lib.PrincipalName
	 */
	PrincipalName sname;
	
	/**
	 * S Realm
	 * @see krb5.lib.Realm
	 */
	Realm srealm;
	
	/**
	 * Credential information
	 * @see krb5.lib.KrbCreds
	 */		
	KrbCreds creds;
	
	/**
	 * Application request
	 * @see krb5.lib.APReq
	 */	
	APReq ap_req;

	/**
	 * Class constructor 
	 *
	 * @param options is of type APOptions
	 * @param tgs_creds is of type Credentials
	 * @param cksum is of type Checksum
	 * @param subKey is of type EncryptionKey
	 * @param seqNumber is of type SeqNumber
	 * @param authorizationData is of type AuthorizationData
	 * @exception KrbException an exception
	 * @see krb5.lib.APOptions
     * @see krb5.lib.Credentials
     * @see krb5.lib.Checksum
     * @see krb5.lib.EncryptionKey
     * @see krb5.lib.SeqNumber
     * @see krb5.lib.AuthorizationData
     * @see krb5.lib.KrbException
	 */
	public KrbApReq(
		APOptions options,
		Credentials tgs_creds,
		Checksum cksum,
		EncryptionKey subKey,
		SeqNumber seqNumber,
		AuthorizationData authorizationData
	) throws KrbException {

        sname = tgs_creds.sname;
        srealm = tgs_creds.srealm;

		_ctime = new KerberosTime(KerberosTime.NOW);

		obuf = ApiUtil.ap_req(
			options,
			tgs_creds.ticket,
			tgs_creds.key,
			tgs_creds.crealm,
			tgs_creds.cname,
			cksum,
			_ctime,
			subKey,
			seqNumber,
			authorizationData
		);
	}

    /**
     * Returns credentials
     * @return KrbCreds as a result
     * @see krb5.lib.KrbCreds
     */
	public KrbCreds creds() {
		return creds;
	}

	/**
	 * Class constructor
	 *
	 * @param msg is of type byte[]
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbException
	 */
	public KrbApReq(
		byte[] msg
	) throws KrbException {
		obuf = msg;
	}

	/**
	 * This returns the output buffer
	 *
	 * @return byte[] is a result
	 */
	public byte[] msg() {
		return obuf;
	}

	/**
	 * Decodes the message
	 *
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbException
	 */
	public void decode() throws KrbException {
		EncodeRef ref = new EncodeRef(obuf);
		ap_req = null;
		try {
			ap_req = new APReq(ref);
    	} catch (Asn1Exception e) {
			ap_req = 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);
   		}
	}

	/**
	 * Authenticates with the specified key
	 *
	 * @param key is of type EncryptionKey
	 * @exception KrbException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KrbException
	 */
	public void authenticate(EncryptionKey key) throws KrbException {
		authenticate(key, null);
	}

	/**
	 * Authenticates with the specified key for a given sender
	 *
	 * @param key is of type EncryptionKey
	 * @param sender is of type HostAddress
	 * @exception KrbException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.HostAddress
	 * @see krb5.lib.KrbException
	 */
	public void authenticate(EncryptionKey key, HostAddress sender)
		throws KrbException {
		if (ap_req == null)
			decode();
		byte[] temp = ap_req.ticket.encPart.decrypt(key);
		EncTicketPart enc_ticketPart = new EncTicketPart(temp);

		byte[] temp2 = ap_req.authenticator.decrypt(enc_ticketPart.key);
		authenticator = new Authenticator(temp2);
		authenticator.ctime.setMicroSeconds(authenticator.cusec);
		authenticator.cname.setRealm(authenticator.crealm);
		ap_req.ticket.sname.setRealm(ap_req.ticket.realm);
		enc_ticketPart.cname.setRealm(enc_ticketPart.crealm);

		if (!authenticator.cname.equals(enc_ticketPart.cname))
			throw new KrbApErrException(Krb5.KRB_AP_ERR_BADMATCH);

		if (sender != null || !Config.KDC_EMPTY_ADDRESSES_ALLOWED) {
			//XXX Microsoft doesn't include caddr in ticket
			//if (sender == null || enc_ticketPart.caddr == null)
			//	throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
			//if (!enc_ticketPart.caddr.inList(sender))
			//	throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
			//XXX Replacement for the above
			if (enc_ticketPart.caddr != null) {
    			if (sender == null)
    				throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
    			if (!enc_ticketPart.caddr.inList(sender))
    				throw new KrbApErrException(Krb5.KRB_AP_ERR_BADADDR);
			}
		}

		if (!authenticator.ctime.inClockSkew())
			throw new KrbApErrException(Krb5.KRB_AP_ERR_SKEW);

		//check for repeated authenticator
        if (Config.replayCache != null) {
            if (Config.replayCache.found(sname, srealm, authenticator))
		        throw new KrbApErrException(Krb5.KRB_AP_ERR_REPEAT);
		    else
		        Config.replayCache.save(sname, srealm, authenticator);
		}

		KerberosTime now = new KerberosTime(KerberosTime.NOW);

		if ((enc_ticketPart.starttime != null &&
			enc_ticketPart.starttime.greaterThanWRTClockSkew(now)) ||
			enc_ticketPart.flags.get(Krb5.TKT_OPTS_INVALID))
			throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_NYV);

		if (enc_ticketPart.starttime != null &&
			now.greaterThanWRTClockSkew(enc_ticketPart.starttime))
			throw new KrbApErrException(Krb5.KRB_AP_ERR_TKT_EXPIRED);

		creds = new KrbCreds(
			ap_req.ticket,
			authenticator.cname,
			ap_req.ticket.sname,
			enc_ticketPart.key
		);
	}

	/**
	 * Returns the time
	 *
	 * @return KerberosTime is a result
	 * @see krb5.lib.KerberosTime
	 */
	public KerberosTime ctime() {
		if (_ctime != null)
			return _ctime;
		return authenticator.ctime;
	}

	/**
	 * Returns application options
	 *
	 * @return APOptions is a result
	 * @exception KrbException an exception
	 * @see krb5.lib.APOptions
	 * @see krb5.lib.KrbException
	 */
	public APOptions getAPOptions() throws KrbException {
		if (ap_req == null)
			decode();
		if (ap_req != null)
			return ap_req.apOptions;
		return null;
	}

	/**
	 * Determines whether mutual authentication is required
	 *
	 * @return boolean is a result
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbException
	 */
	public boolean mutualAuthenticationRequired() throws KrbException {
		if (ap_req == null)
			decode();
		if (ap_req != null)
			return ap_req.apOptions.get(Krb5.AP_OPTS_MUTUAL_REQUIRED);
		return false;
	}

	/**
	 * Determines whether session key is used
	 *
	 * @return boolean is a result
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbException
	 */
	public boolean useSessionKey() throws KrbException {
		if (ap_req == null)
			decode();
		if (ap_req != null)
			return ap_req.apOptions.get(Krb5.AP_OPTS_USE_SESSION_KEY);
		return false;
	}

	/**
	 * Returns subject key
	 *
	 * @return EncryptionKey is a result
	 * @see krb5.lib.EncryptionKey
	 */
	public EncryptionKey getSubKey() {
		if (authenticator != null) {
			return authenticator.subKey;
		}
		return null;
	}

	/**
	 * Returns sequence number
	 *
	 * @return Integer is a result
	 * @see java.lang.Integer
	 */
	public Integer getSeqNumber() {
		if (authenticator != null) {
			return authenticator.seqNumber;
		}
		return null;
	}

}
