/*
 * Created on 26/01/2005
 * Created by Paul Duran
 * Copyright (C) 2004 Aelitis, All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * AELITIS, SARL au capital de 30,000 euros
 * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France.
 *
 */

package org.gudy.azureus2.ui.telnet;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.Set;

import org.gudy.azureus2.core3.util.AEThread2;
import org.gudy.azureus2.ui.common.UIConst;
import org.gudy.azureus2.ui.console.UserProfile;
import org.gudy.azureus2.ui.console.multiuser.UserManager;

/**
 * this class is used to receive incoming connections for the telnet UI and
 * then authenticate and create a console session for the connection  
 * @author pauld
 */
final class SocketServer implements Runnable
{
	private final ServerSocket serverSocket;
	private final Set allowedHosts;
	private final int maxLoginAttempts;
	private final UserManager userManager;
	private final UI ui;
	
	public SocketServer(UI ui, int port, Set allowedHosts, UserManager userManager, int maxLoginAttempts) throws IOException
	{
		this.ui = ui;
		this.allowedHosts = allowedHosts;
		this.userManager = userManager;
		serverSocket = new ServerSocket(port);
		this.maxLoginAttempts = maxLoginAttempts;
	}
	
	/**
	 * start up the server socket and when a new connection is received, check that
	 * the source address is in our permitted list and if so, start a new console input
	 * on that socket.
	 */
	public void run()
	{
		int threadNum = 1;
		System.out.println("Telnet server started. Listening on port: " + serverSocket.getLocalPort());
		
		new AEThread2( "AZCoreStartup", true )
		{
			public void
			run()
			{
				UIConst.getAzureusCore();
			}
		}.start();
		
		while( true ) {
			try {
				Socket socket = serverSocket.accept();
				
				InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
				
				if( addr.isUnresolved() || ! isAllowed(addr) ) {
					System.out.println("TelnetUI: rejecting connection from: " + addr + " as address is not allowed");
					socket.close();
				}
				else {
					System.out.println("TelnetUI: accepting connection from: " + addr);
					int loginAttempts = 0;
					
					while( true ) {
						// TODO: might want to put this in another thread so the port doesnt block while the user logs in
						
						//System.out.println("TelnetUI: starting login" );
						
						UserProfile profile = login( socket.getInputStream(), socket.getOutputStream() );
						
						//System.out.println("TelnetUI: login profile obtained" );
						
						if( profile != null ) {
							
							//System.out.println("TelnetUI: creating console input" );
							
							ui.createNewConsoleInput("Telnet Console " + threadNum++, socket.getInputStream(), new PrintStream(socket.getOutputStream()), profile);
							break;
						}
						
						//System.out.println("TelnetUI: failed to obtain login profile" );
						
						loginAttempts++;
						
						if( loginAttempts >= maxLoginAttempts ) {
							System.out.println("TelnetUI: rejecting connection from: " + addr + " as number of failed connections > max login attempts (" + maxLoginAttempts + ")");
							socket.close();
							break;
						}
					}
				}
			}
			catch (Throwable t) {
				t.printStackTrace();
				break;
			}
		}
	}
	/**
	 * if usermanager is null (ie: multi user is not enabled), returns the default user profile
	 * otherwise, requests username and password and authenticates user before returning 
	 * the user profile for this user
	 * @param in input stream to read from
	 * @param out stream to write messages to
	 * @return username if login was successful, null otherwise
	 * @throws IOException
	 */
	private UserProfile login(InputStream in, OutputStream out) throws IOException 
	{
		if( userManager == null )
			return UserProfile.DEFAULT_USER_PROFILE;
		
		PrintStream ps = new PrintStream(out);
		BufferedReader br = new BufferedReader(new InputStreamReader(in));
		ps.print("Username: ");
		String username = br.readLine();
		ps.print("Password: ");
		String password = br.readLine();
		UserProfile userProfile = userManager.authenticate(username, password);
		if( userProfile != null )
		{
			ps.println("Login successful");
			return userProfile;
		}
		ps.println("Login failed");
		return null;
	}
	
	/**
	 * check that the specified host/ip is allowed
	 * @param addr
	 * @return
	 */
	private boolean isAllowed(InetSocketAddress addr) {
		InetAddress address = addr.getAddress();
		if( checkHost(address.getHostAddress()) )
			return true;
		else if( checkHost(address.getHostName()))
			return true;
		else
			return false;
	}
	/**
	 * compare the specified host (might be a hostname or an IP - dont really care) 
	 * and see if it is a match against one of the allowed hosts
	 * @param hostName
	 * @return true if this hostname matches one in our allowed hosts
	 */
	private boolean checkHost(String hostName) {
		if( hostName == null )
			return false;
		hostName = hostName.toLowerCase();
//			System.out.println("checking host: " + hostName);
		for (Iterator iter = allowedHosts.iterator(); iter.hasNext();) {
			String allowedHost = (String) iter.next();
			if( hostName.equals(allowedHost) )
				return true;
		}
		return false;
	}
}