package beacon;

import java.net.*;
import java.io.*;
import java.lang.*;
import java.util.*;
import java.security.*;
import mkgray.adopted.*;

/** 
  The BServer handles client transactions.
  Currently, it handles:
  <ul>
  <li>REGISTER
  <li>SETLOC
  <li>REQLOC
  <li>REMLOC
  <li>CHPW
  </ul>

  All transactions except REQLOC are authenticated.

  <p>

  It takes a port number and a database file as arguments.  For example,
  <pre>
  java beacon.BServer 80 userdatabase.dat
  </pre>

  It creates a new database if one does not already exist and starts
  with an empty user location database.

  <p> It starts a <a
  href="beacon.ServerMaintenanceThread.html">ServerMaintentanceSthread</a>
  which expires locations which are not ponging, periodically dumps
  the database, and expires aliases that haven't been used in over a
  certain amount of time (currently infinite).
  
**/

public class BServer implements Runnable {
  Socket con;
  static BUserInfoDatabase infoDB;
  static BUserLocationDatabase locDB;
  static long ALIAS_EXPIRE = 60*60*24*30;

  public static void main(String args[]) {
    ServerSocket ss;

    infoDB = BUserInfoDatabase.loadDB(args[1]);
    locDB = new BUserLocationDatabase();
    (new ServerMaintenanceThread(infoDB, locDB, args[1])).start();
    try {
      ss = new ServerSocket(Integer.parseInt(args[0]));
      while(true){
	try {
	  Socket s = ss.accept();
	  System.out.println("Accepted connection!");
	  new BServer(s);
	} catch (Exception e) { System.out.println("Accept: "+e); }
      }
    } catch (Exception e){
      System.out.println("Uhoh: "+e);
    }
  }

  BServer (Socket s){
    con = s;
    (new Thread(this)).start();
  }

  public void run() {
    boolean havedata = true;
    InputStream is;
    OutputStream os;
    InputStreamReader isr;
    BufferedReader read;
    PrintWriter write;
    SMTPmail mail;

    String command = new String("");
    String user = new String("");
    String newpw = new String("");
    String contact = new String("");
    String port = new String("");
    String authstring = new String("");
    String req = new String("");

    String fieldname;
    String value;
    String response = new String("");

    System.out.println("Handling connection");

    // Create the Streams to read and write from
    try {
      is = con.getInputStream();
      os = con.getOutputStream();
      
      read = new BufferedReader(isr = new InputStreamReader(is));
      write = new PrintWriter(os);
    } catch (Exception e) { return; }


    // Main loop
    // Handle the request
    while(havedata){
      try {
	// Read a line of data
	String line = read.readLine();

	// If it is null, close the connection
	if(line == null){
	  havedata = false;
	  con.close();
	  System.out.println("Closed a connection");
	}
	// Otherwise, handle it
	else{
	  // Show us what it is
	  System.out.println("Read: "+line);
	  // If it is the authentication header, store it
	  if((line.length() >= 5) && (line.substring(0,5)).equals("BHA: ")){
	    authstring = line.substring(5, line.length());
	  }
	  // If it is the GET line, munch it up
	  else if((line.length() >= 3) && (line.substring(0,3)).equals("GET")){
	    // Strip of the HTTP thing at the end
	    line = line.substring(0, line.lastIndexOf(" "));
	    // Identify the request for later authentication purposes
	    req = line.substring(5);
	    // Get the command
	    command = line.substring(5, line.indexOf("?"));

	    // Snarf up all the command attributes and store them
	    int ptr = line.indexOf("?");
	    while((ptr < line.length()) && (ptr != -1)){
	      int end;
	      fieldname = line.substring(ptr+1, ptr=line.indexOf("=", ptr+1));
	      end = (line.indexOf("&", ptr+1) > 0) ? line.indexOf("&", ptr+1):line.length();
	      value = line.substring(ptr+1, end);
	      ptr = line.indexOf("&", ptr+1);
	      if(fieldname.equals("user"))
		 user = HTTPClient.Codecs.URLDecode(value);
	      if(fieldname.equals("newpw"))
		newpw = HTTPClient.Codecs.URLDecode(value);
	      if(fieldname.equals("contact"))
		contact = HTTPClient.Codecs.URLDecode(value);
	      if(fieldname.equals("port"))
		port = HTTPClient.Codecs.URLDecode(value);
	    }
	  }
	  // End of request, time to respond
	  else if(line.equals("")){
	    // Output the headers
	    write.print("HTTP/1.0 200 OK\r\n");
	    write.print("Content-type: application/x-bha\r\n");
	    write.println("\r\n");
	    System.out.println("Processing command "+command);

	    // REGISTER Command
	    if(command.equals("REGISTER")){
	      String spw;
	      mail = new SMTPmail("pca.mit.edu");
	      mail.addTo(user, contact);
	      mail.setFrom("BHA Server", "bha@mit.edu");
	      mail.setSubject("BHA Registration");
	      mail.addText("Your password is "+(spw = randomPassword()));
	      infoDB.set(user, new BUser(user, contact, spw, (System.currentTimeMillis()/1000)+ALIAS_EXPIRE));
	      try { mail.sendMail(); } catch (Exception e){ 
		System.out.println("Failed to send mail.  User not added to database");
	      }
	    }

	    // Otherwise, I better know who they are
	    else if(infoDB.get(user) == null){
	      write.println("2003 Unknown user");
	    }
	    // SETLOC Command
	    else if(command.equals("SETLOC")){
	      System.out.println("Doing Setloc for user "+user);
	      System.out.println(authstring+" "+req+" "+infoDB.get(user).password+" "+port);
	      if(authentic(authstring, req, infoDB.get(user).password)){
		System.out.println("Authentic! "+locDB);
		locDB.set(user, new String(con.getInetAddress()+":"+port));
		System.out.println("Okdokey");
		write.println("1000 OK");
	      }
	      else{
		write.println("2002 Authentication failure");
	      }
	    }

	    // REQLOC Command
	    else if(command.equals("REQLOC")){
	      System.out.println("Requesting location for "+user);
	      Vector locs = locDB.get(user);
	      System.out.println("Got location list "+locs);
	      if(locs == null)
		write.println();
	      else
		for(Enumeration ls = locs.elements(); ls.hasMoreElements();){
		  BUserLocation l = (BUserLocation) ls.nextElement();
		  write.println(l.location);
		}
	    }

	    // REMLOC command
	    else if(command.equals("REMLOC")){
	      if(authentic(authstring, req, infoDB.get(user).password)){
		locDB.remove(user, new String(con.getInetAddress()+":"+port));
		write.println("1000 OK");
	      }
	      else
		write.println("2002 Authentication failure");
	    }

	    // CONPONG command
	    else if(command.equals("CONPONG")){
	      if(authentic(authstring, req, infoDB.get(user).password)){
		locDB.pong(user, new String(con.getInetAddress()+":"+port));
		write.println("1000 OK");
	      }
	    }
	      
	    // CHPW command
	    else if(command.equals("CHPW")){
	      if(authentic(authstring, req, infoDB.get(user).password)){
		infoDB.set(user, new BUser(user, infoDB.get(user).email, newpw, (System.currentTimeMillis()/1000)+ALIAS_EXPIRE));
		write.println("1000 OK");
	      }
	      else
		write.println("2002 Authentication failure");
	    }

	    // Update alias expiry time
	    if((infoDB.get(user) != null) && (authentic(authstring, req, infoDB.get(user).password))){
	      (infoDB.get(user)).expire = (System.currentTimeMillis()/1000)+ALIAS_EXPIRE;
	    }

	    // Flush the buffers and go home
	    write.flush();
	    havedata = false;
	    read.close();
	    con.close();
	  }
	}
      } catch (Exception e) { System.out.println("Whoa: "+e); }
    }
    System.out.println("Done with that connection...\n");
    Thread.currentThread().stop();
  }

  boolean authentic(String compare, String req, String pass) {
    byte hash[];
    String tohex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
		      "a", "b", "c", "d", "e", "f"};
    try {
      MessageDigest md5 = MessageDigest.getInstance("MD5");
      String digest = new String(hash = md5.digest((new String(req+pass)).getBytes()));
      String digeststr = new String ("");
      for(int ct=0;ct<hash.length;ct++){
	int val;
	if(hash[ct] < 0)
	  val = 256+((int)hash[ct]);
	else
	  val = (int) hash[ct];
	digeststr = new String(digeststr+(tohex[val/16])
			       +(tohex[val%16]));
      }
      if(digeststr.equals(compare))
	return true;
      else
	return false;
    } catch (Exception e){
      System.out.println("Dammit2: "+e);
      return false;
    }

  }

  String randomPassword() {
    Random r = new Random();
    return new String(""+r.nextLong());
  }
}




