// Copyright 1999 Memesoft.  All rights reserved.

package krb5.lib.util;

import java.io.*;
import krb5.lib.Krb5;
import krb5.lib.KrbException;
import krb5.lib.PrincipalName;
import krb5.lib.EncryptionKey;

/**
 * Keytab file
 */
public class KeytabFile implements Keytab {

	/**
	 * Closed ?
	 */
    boolean closed = true;

	/**
	 * File pointer
	 */
    File file;

	/**
	 * Random access file pointer
	 */
    RandomAccessFile f;

	/**
	 * Keytab version
	 */
    int kt_vno;

	/**
	 * Class constructor
	 *
	 * @param path is of type String
	 */
	public KeytabFile(String path) {
	    file = new File(path);
	}

	/**
	 * Class constructor
	 *
	 * @param _file is of type File
	 */
	public KeytabFile(File _file) {
	    file = _file;
	}

	/**
	 * Opens the keytab
	 *
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbException
	 */
	public synchronized void open() throws KrbException {
	    if (!closed)
	        return;
	    if (!file.exists())
	        throw new KrbException(Krb5.KT_FILE_NOT_FOUND);
	    try {
	        f = new RandomAccessFile(file, "r");
	        kt_vno = f.readShort() & 0x0000ffff;
	        if (kt_vno != Krb5.KT_VNO_1 && kt_vno != Krb5.KT_VNO_2)
    	        throw new KrbException(Krb5.KT_FILE_VERSION_UNSUPPORTED);
	    } catch (IOException e) {
	        throw new KrbException(Krb5.KT_FILE_CORRUPT);
	    }
	    closed = false;
	}

	/**
	 * Closes the file
	 */
	public synchronized void close() {
	    if (!closed) {
	        if (f != null) {
	            try {
	                f.close();
	            } catch(IOException e) {}
	            f = null;
	        }
	        closed = true;
	    }
	}

	/**
	 * Finalize
	 */
	public void finalize() {
	    close();
	}

	/**
	 * Returns encryption key
	 *
	 * @return EncryptionKey is a result
	 * @param princName is of type PrincipalName
	 * @exception KrbException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.PrincipalName
	 * @see krb5.lib.KrbException
	 */
	public synchronized EncryptionKey getEncryptionKey(PrincipalName princName)
	    throws KrbException {
	    KeytabEntry prevEntry = null;
	    try {
	        open();
	        int prev_key_vno = Integer.MIN_VALUE;
	        KeytabEntry entry = getFirstEntry();
	        while (entry != null) {
	            if (princName.equals(entry.princName)) {
	                if (entry.key_vno > prev_key_vno) {
	                    prev_key_vno = entry.key_vno;
	                    prevEntry = entry;
	                }
	            }
	            entry = getNextEntry();
	        }
	        close();
	        if (prevEntry == null)
	            throw new KrbException(Krb5.KT_FILE_ENTRY_NOT_FOUND);
	    } finally {
	        close();
	    }
	    return prevEntry.encKey;
	}

	/**
	 * Print
	 *
	 * @exception KrbException an exception
	 * @see krb5.lib.KrbException
	 */
	public synchronized void print() throws KrbException {
	    try {
	        open();
	        KeytabEntry entry = getFirstEntry();
	        while (entry != null) {
	            entry.print();
	            entry = getNextEntry();
	            if (entry != null)
	                System.out.println();
	        }
	    } finally {
	        close();
	    }
	}

	/**
	 * Returns first entry
	 *
	 * @return KeytabEntry is a result
	 * @exception KrbException an exception
	 * @see krb5.lib.util.KeytabEntry
	 * @see krb5.lib.KrbException
	 */
	public synchronized KeytabEntry getFirstEntry() throws KrbException {
	    if (closed)
	        return null;
	    try {
	        f.seek(2);
	    } catch (IOException e) {
	        throw new KrbException(Krb5.KT_FILE_CORRUPT);
	    }
	    return getNextEntry();
	}

	/**
	 * Returns next entry
	 *
	 * @return KeytabEntry is a result
	 * @exception KrbException an exception
	 * @see krb5.lib.util.KeytabEntry
	 * @see krb5.lib.KrbException
	 */
	public synchronized KeytabEntry getNextEntry() throws KrbException {
	    if (closed)
	        return null;
	    while (true) {
	        int len = 0;
	        try {
	            len = f.readInt();
	        } catch (EOFException e) {
	            break;
	        } catch (IOException e) {
	            throw new KrbException(Krb5.KT_FILE_CORRUPT);
	        }
	        len = toInt(len);
	        boolean used = isUsed(len);
	        len = toInt31(len);
	        try {
	            if (used) {
	                byte buf[] = new byte[len];
    	            f.readFully(buf);
        	        return new KeytabEntry(kt_vno, buf);
    	        } else
    	            f.seek(f.getFilePointer() + len);
	        } catch (EOFException e) {
	            break;
	        } catch (IOException e) {
	            throw new KrbException(Krb5.KT_FILE_CORRUPT);
	        }
	    }
	    return null;
	}

	/**
	 * Converts to int
	 *
	 * @return int is a result
	 * @param s is of type int
	 */
	int toInt(int s) {
	    return kt_vno == Krb5.KT_VNO_2 ? s : SysUtil.htonl(s);
	}

	/**
	 * Converts to int31
	 *
	 * @return int is a result
	 * @param x is of type int
	 */
	static int toInt31(int x) {
	    return 0x07fffffff & x;
	}

	/**
	 * Checks to see whether it is in used
	 *
	 * @return boolean is a result
	 * @param x is of type int
	 */
	static boolean isUsed(int x) {
	    return (0x80000000 & x) == 0;
	}

	/**
	 * Main
	 *
	 * @param args is of type String[]
	 */
	public static void main(String args[]) {
	    try {
	        KeytabFile f = new KeytabFile("d:\\jkrb5\\foo7.keytab");
	        f.print();
	        PrincipalName p = new PrincipalName("jkrbsrv2/phage@MEMESOFT.COM");
	        EncryptionKey key = f.getEncryptionKey(p);
	        System.out.println();
	    } catch (KrbException e) {
			e.printStackTrace();
			System.out.println(e.krbErrorMessage());
	    }
    }	
}