package com.runjva.sourceforge.jsocks.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.net.ConnectException;
import java.net.Socket;
import java.util.StringTokenizer;

/**
 * Class Ident provides means to obtain user name of the owner of the socket on
 * remote machine, providing remote machine runs identd daemon.
 * <p>
 * To use it: <tt><pre>
   Socket s = ss.accept();
   Ident id = new Ident(s);
   if(id.successful) goUseUser(id.userName);
   else handleIdentError(id.errorCode,id.errorMessage)
   </pre></tt>
 */
public class Ident {

	/** Error Message can be null. */
	public String errorMessage;

	/** Host type as returned by daemon, can be null, if error happened */
	public String hostType;

	/** User name as returned by the identd daemon, or null, if it failed */
	public String userName;

	/**
	 * If this is true then userName and hostType contain valid values. Else
	 * errorCode contain the error code, and errorMessage contains the
	 * corresponding message.
	 */
	public boolean successful;

	/** Error code */
	public int errorCode;

	/** Identd on port 113 can't be contacted */
	public static final int ERR_NO_CONNECT = 1;

	/** Connection timed out */
	public static final int ERR_TIMEOUT = 2;

	/**
	 * Identd daemon responded with ERROR, in this case errorMessage contains
	 * the string explanation, as send by the daemon.
	 */
	public static final int ERR_PROTOCOL = 3;

	/**
	 * When parsing server response protocol error happened.
	 */
	public static final int ERR_PROTOCOL_INCORRECT = 4;

	/**
	 * Maximum amount of time we should wait before dropping the connection to
	 * identd server.Setting it to 0 implies infinit timeout.
	 */
	public static final int connectionTimeout = 10000;

	/**
	 * Constructor tries to connect to Identd daemon on the host of the given
	 * socket, and retrieve user name of the owner of given socket connection on
	 * remote machine. After constructor returns public fields are initialised
	 * to whatever the server returned.
	 * <p>
	 * If user name was successfully retrieved successful is set to true, and
	 * userName and hostType are set to whatever server returned. If however for
	 * some reason user name was not obtained, successful is set to false and
	 * errorCode contains the code explaining the reason of failure, and
	 * errorMessage contains human readable explanation.
	 * <p>
	 * Constructor may block, for a while.
	 * 
	 * @param s
	 *            Socket whose ownership on remote end should be obtained.
	 */
	public Ident(Socket s) {
		Socket sock = null;
		successful = false; // We are pessimistic

		try {
			sock = new Socket(s.getInetAddress(), 113);
			sock.setSoTimeout(connectionTimeout);
			final byte[] request = ("" + s.getPort() + " , " + s.getLocalPort() + "\r\n")
					.getBytes();

			sock.getOutputStream().write(request);

			final BufferedReader in = new BufferedReader(new InputStreamReader(
					sock.getInputStream()));

			parseResponse(in.readLine());

		} catch (final InterruptedIOException iioe) {
			errorCode = ERR_TIMEOUT;
			errorMessage = "Connection to identd timed out.";
		} catch (final ConnectException ce) {
			errorCode = ERR_NO_CONNECT;
			errorMessage = "Connection to identd server failed.";

		} catch (final IOException ioe) {
			errorCode = ERR_NO_CONNECT;
			errorMessage = "" + ioe;
		} finally {
			try {
				if (sock != null) {
					sock.close();
				}
			} catch (final IOException ioe) {
				//log.warn("Could not close socket", ioe);
			}
		}
	}

	private void parseResponse(String response) {
		if (response == null) {
			errorCode = ERR_PROTOCOL_INCORRECT;
			errorMessage = "Identd server closed connection.";
			return;
		}

		final StringTokenizer st = new StringTokenizer(response, ":");
		if (st.countTokens() < 3) {
			errorCode = ERR_PROTOCOL_INCORRECT;
			errorMessage = "Can't parse server response.";
			return;
		}

		st.nextToken(); // Discard first token, it's basically what we have send
		final String command = st.nextToken().trim().toUpperCase();

		if (command.equals("USERID") && (st.countTokens() >= 2)) {
			successful = true;
			hostType = st.nextToken().trim();
			userName = st.nextToken("").substring(1);// Get all that is left
		} else if (command.equals("ERROR")) {
			errorCode = ERR_PROTOCOL;
			errorMessage = st.nextToken();
		} else {
			errorCode = ERR_PROTOCOL_INCORRECT;
			System.out.println("Opa!");
			errorMessage = "Can't parse server response.";
		}

	}

	// /////////////////////////////////////////////
	// USED for Testing
	/*
	 * public static void main(String[] args) throws IOException{
	 * 
	 * Socket s = null; s = new Socket("gp101-16", 1391);
	 * 
	 * Ident id = new Ident(s); if(id.successful){
	 * System.out.println("User: "+id.userName);
	 * System.out.println("HostType: "+id.hostType); }else{
	 * System.out.println("ErrorCode: "+id.errorCode);
	 * System.out.println("ErrorMessage: "+id.errorMessage);
	 * 
	 * }
	 * 
	 * if(s!= null) s.close(); } //
	 */

}
