/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials 
 * are made available under the terms of the Common Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.team.internal.ccvs.ssh;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Vector;

import org.eclipse.core.boot.BootLoader;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.team.internal.ccvs.ssh.Policy;

/**
 * I represent a database of known hosts usually placed in ~/.ssh/known_hosts
 * on Unix/Linux systems.
 * Currently, only RSA keys are supported, as these are the only keys we
 * have to deal with during SSH1 key exchange.
 */
public class KnownHosts {

	private String filename;
	
	public KnownHosts(String filename) {
		this.filename = filename;
	}
	
	public KnownHosts() {
		this.filename = KnownHosts.defaultFilename();
	}
	
	static String defaultFilename() {
		if (!BootLoader.getOS().equals(BootLoader.OS_LINUX)) return internalFilename();
		String HOME = System.getProperty("user.home"); //$NON-NLS-1$
		if (HOME==null) return internalFilename();
		return HOME+"/.ssh/known_hosts"; //$NON-NLS-1$
	}

	private static String internalFilename() {
		return SSHPlugin.getPlugin().getStateLocation().append("known_hosts").toOSString(); //$NON-NLS-1$
	}
	
	/**
	 * Verify if the public key for the specified host is known.
	 * If the public key matches, return true.
	 * If the key does not match, return false.
	 * If the key is not listed in <code>known_hosts</code>, or
	 * <code>known_hosts</code> does not exist, assume we are connecting
	 * to the authentic server, add the key, and return true.
	 * @param e key exponent
	 * @param n key modulus
	 * @return boolean whether the key is correct
	 */
	public boolean verifyKey(String hostname, byte[] host_key_bits, BigInteger e, BigInteger n) {
		FileReader f;
		BigInteger nbits = new BigInteger(1, host_key_bits);
		try {
			f= new FileReader(filename);
		} catch (FileNotFoundException ex) {
			createHostFile();
			addHost(hostname, nbits, e, n);
			return true;
		}
		BufferedReader r = new BufferedReader(f);
		try {
			boolean found = false;
			String line;
			while ((line = r.readLine()) != null) {
				if (line.trim().length()==0) continue;
				if (line.startsWith("#")) continue; //$NON-NLS-1$
				String[] tokens=subStrings(line);
				if (tokens.length==4 && Character.isDigit(tokens[1].charAt(0)) && tokens[0].equalsIgnoreCase(hostname)) {
					if (nbits.equals(new BigInteger(tokens[1])) && e.equals(new BigInteger(tokens[2])) && n.equals(new BigInteger(tokens[3]))) {
						f.close();
						return true;
					} else {
						f.close();
						return false;
					}
				}
			}
			f.close();
			addHost(hostname, nbits, e, n);
			return true;
		} catch (IOException ex) {
			SSHPlugin.log(IStatus.ERROR, Policy.bind("KnownHosts.8"), ex); //$NON-NLS-1$
			return false;
		}
	}
	
	/*
	 * Append the host key information to known_hosts.
	 * Always assume the file exists.
	 */
	void addHost(String hostname, BigInteger key_bits, BigInteger e, BigInteger n) {
		try {
			FileWriter w = new FileWriter(defaultFilename(), true);
			w.write(Character.LINE_SEPARATOR);
			w.write(hostname + " " + key_bits.toString(10) + " " + e.toString(10) + " " + n.toString(10)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			w.close();
			String message = Policy.bind("Client.addedHostKey", new String[] {hostname, defaultFilename()}); //$NON-NLS-1$
			SSHPlugin.log(IStatus.INFO, message, null);
		} catch (IOException ex) {
			SSHPlugin.log(IStatus.ERROR, Policy.bind("KnownHosts.9"), ex); //$NON-NLS-1$
		}
	}
	
	/*
	 * Create the known_hosts file in the default location.
	 * Fail if the file can not be created (issue a warning in the log).
	 */
	void createHostFile() {
		try {
			File file = new File(defaultFilename());
			// Ensure the parent directory exists
			File parentDir = file.getParentFile();
			parentDir.mkdirs();
			// Create the file
			file.createNewFile();
		} catch (IOException ee) {
			SSHPlugin.log(IStatus.ERROR, Policy.bind("KnownHosts.10"), ee); //$NON-NLS-1$
		}

	}
	private static String[] subStrings(String s) {
		Vector v = subStringsVector(s);
		String[] substrings = new String[v.size()];
		v.copyInto(substrings);
		return substrings;
	}
	private static Vector subStringsVector(String s) {
		Vector v = new Vector();
		s = s.trim();
		if (s.length()==0) return v;
		int first1 = s.indexOf(' ');
		int first2 = s.indexOf('\t');
		int first;
		if ((first1==-1)&&(first2==-1)) first=-1;
		else if ((first1!=-1)&&(first2!=-1)) first = Math.min(first1, first2);
		else if (first1!=-1) first=first1; else first=first2;
		if (first==-1) {
			v.add(s);
			return v;
		}
		v.add(s.substring(0,first));
		v.addAll(subStringsVector(s.substring(first+1)));
		return v;
	}
}
