package jcsi;

import java.util.Properties;
import java.io.*;
import java.net.*;

import com.dstc.security.kerberos.Kerberos;
import com.dstc.security.kerberos.KerberosContext;
import com.dstc.security.kerberos.creds.KeyTab;
import com.dstc.security.kerberos.gssapi.*;

public class GSSServer implements Runnable
{
  private int port;
  private ServerSocket serverSock;
  private String service;
  private GSSCredential serverCred;

  /**
   * Constructs a GSSServer to listen on a specified port
   */
  public GSSServer(int port, String service)
  {
    this.port = port;
    this.service = service;
    
    try
    {
      this.serverSock = new ServerSocket(this.port);
    }
    catch (IOException e)
    {
      System.out.println(e.toString());
    }

    try
    {
      KerberosContext ctx = Kerberos.getDefault().getKerberosContext();
      KeyTab keyTab = KeyTab.getDefault();

      GSSName serviceName = new GSSName(this.service,
                                        GSSName.NT_HOSTBASED_SERVICE);
      String serverName = serviceName.toString().replace('@', '/')
                          + "@" + ctx.getRealm();

      if (keyTab.getKeyTabEntry(serverName) == null)
      {
        System.out.println("Service not found in keytab");
        System.exit(0);
      }

      // obtain server credentials
      this.serverCred 
        = new GSSCredential(serviceName, GSSCredential.ACCEPT_ONLY);
    }
    catch (Exception e)
    {
      System.out.println(e.getMessage());
    }
  }

  public void run()
  {
    do
    {
      try
      {
        Socket sock = this.serverSock.accept();
        OutputStream out = sock.getOutputStream();
        InputStream in = sock.getInputStream();

        // create acceptor GSS-API context
        GSSContext aCtxt = new GSSContext(serverCred);

        do
        {
          byte[] inTok = readToken(in);
          byte[] outTok = aCtxt.accept(inTok, 0, inTok.length);

          // possibly send token to peer
          if (outTok != null)
            sendToken(out, outTok);

          // check if local context establishment is complete
          if (aCtxt.isEstablished())
            break;

        } while (true);

        System.out.println("\nAccepted connection: "
                           + aCtxt.getSrcName().toString());

        // display context flags
        if (aCtxt.getCredDelegState())
          System.out.println("context flag: DELEG_FLAG");
        if (aCtxt.getMutualAuthState())
          System.out.println("context flag: MUTUAL_FLAG");
        if (aCtxt.getReplayDetState())
          System.out.println("context flag: REPLAY_FLAG");
        if (aCtxt.getSequenceDetState())
          System.out.println("context flag: SEQUENCE_FLAG");
        if (aCtxt.getConfState())
          System.out.println("context flag: CONF_FLAG");
        if (aCtxt.getIntegState())
          System.out.println("context flag: INTEG_FLAG");

        if (aCtxt.getCredDelegState())
          System.out.println("Received delegated credential from \""
                             + aCtxt.getDelegCred().getGSSName().toString()
                             + "\"");

        byte[] tok = readToken(in);
        MessageProp mProp = new MessageProp();
        byte[] msg = aCtxt.unwrap(tok, 0, tok.length, mProp);

        if (mProp.isUnseqToken())
          System.out.println("Out-of-sequence token received.");
        if (mProp.isGapToken())
          System.out.println("Gap token received.");
        if (!mProp.getPrivacy())
          System.out.println("Warning! Message not encrypted.");

        System.out.println("Received message: " + new String(msg));

        mProp = new MessageProp();
        tok = aCtxt.getMIC(msg, 0, msg.length, mProp);
        sendToken(out, tok);

      }
      catch (GSSException e)
      {
        System.out.println("GSS-API error: " + e.getMessage());
        e.printStackTrace();
      }
      catch (Exception e)
      {
        System.out.println(e.getMessage());
        e.printStackTrace();
      }
    } 
    while (true);
  }

  public static void main(String args[])
  {
    int port = 4445;
    String service = null;
    
    if ((args.length == 3) && args[0].equals("-port"))
    {
      port = java.lang.Integer.parseInt(args[1]);
      service = args[2];
    }
    else if (args.length == 1)
    {
      service = args[0];
    }
    else
    {
      System.out.println("Usage: jgss-server [-port port] service");
      System.exit(0);
    }

    GSSServer server = new GSSServer(port, service);
    server.run();
  }

  /**
   * Writes a supplied Token in the form of a byte array to an output stream
   */
  private void sendToken(OutputStream out, byte[] data) throws IOException
  {
    int len = data.length;

    byte[] lengthBytes = new byte[4];
    lengthBytes[0] = (byte)((len >>> 24) & 0xff);
    lengthBytes[1] = (byte)((len >>> 16) & 0xff);
    lengthBytes[2] = (byte)((len >>>  8) & 0xff);
    lengthBytes[3] = (byte)(len & 0xff);

    out.write(lengthBytes);
    out.write(data);
    out.flush();
  }

  /**
   * Reads a Token from an input stream and returns it as a byte array
   */
  private byte[] readToken(InputStream in) throws IOException
  {
    byte[] lengthBytes = new byte[4];
    in.read(lengthBytes);
    int length =  ((int)(lengthBytes[0] & 0xff) << 24) +
                  ((int)(lengthBytes[1] & 0xff) << 16) +
                  ((int)(lengthBytes[2] & 0xff) << 8) +
                  ((int)(lengthBytes[3] & 0xff) << 0);

    byte[] retval = new byte[length];
    in.read(retval);
    return retval;
  }

  public static void print (byte[] bytes)
  {
    for (int i = 0; i< bytes.length; i++)
      System.out.print (java.lang.Integer.toHexString(bytes[i] & 0xff) + " ");

    System.out.println (" ");
  }
}
